前端进化笔记-JavaScript(三)

人类在白色的底色上描绘图画,地球在黑色的底色上创造生命。 #变量、作用域与内存 JavaScript的变量可以说是独树一帜。只需要一个(或两个等)关键字(const,let)就可以创建变量,创建时不考虑变量的类型,这是其他语言少有的强大功能。当然强大的功能总是伴随着问题。 ###值 > 原始值:Un …

人类在白色的底色上描绘图画,地球在黑色的底色上创造生命。


变量、作用域与内存

JavaScript的变量可以说是独树一帜。只需要一个(或两个等)关键字(const,let)就可以创建变量,创建时不考虑变量的类型,这是其他语言少有的强大功能。当然强大的功能总是伴随着问题。

原始值:Undefined,Null,Boolean,Number,String,Symbol。它们都是按值访问的,因此我们操作的就是储存在变量中的实际值。

引用值:保存在内存中的对象。引用值是按引用访问的(类似指针),我们操作的实际上是引用而不是对象本身。

两者的不同:

  1. 原始值没有属性,引用值有,但如果使用new关键字,就会为原始值创造对象,从而使他们可以获得属性。(实际上字符串原始值,即使不用构造函数创建也有length等属性和方法。但是想要添加属性和方法就必须要使用包装类型对象)
  2. 原始值在复制的时候会获得一个独立的副本,而引用值只是复制了这个引用,它们指向同一个对象。(类似指针那样理解)

传递参数

ECMAscript中所有的函数参数都是按值传递的,也就说明函数内的参数和函数外的变量没有什么关系,只是值的赋值。即使是引用值也只是传值(虽然他的赋值是传址的),这里我的理解是传入指针本身的内容而非指向的地址本身。实际上,函数内部的参数是一个局部变量。
下面的例子,可以帮助我们理解这个问题:

function setName(obj){
  obj.name = 'jake';  //obj这个"指针"指向的地址(假设地址为1,则person的地址也为1)的name为jake,则1地址内的name被修改
  obj = new Object(); //obj不再指向1,而是指向其他地址(假设为2),1中的name值obj取不到了
  obj.name = 'james'; //2中的name为james,与1无关,也就与person无关
}
let person = new Object();
setName(person);
console.log(person.name);//jake
console.log(obj);//obj没有定义,他在函数执行结束的时候就被销毁了

instance用来确定对象的类型

上下文与作用域

执行上下文

红宝书中对于执行上下文的解释并没有那么深入,我在学习的过程中也在网上阅读了一些相关文章,对于JavaScript的执行上下文逻辑有了一点浅显的理解。日后在对js有更深入了解之后,会详细介绍,现在先简单说一下我的理解,如果后续发现不严谨的地方会随时修改,也欢迎指正。

执行上下文本质是一个变量对象,包含了上下文中定义的所有变量和函数。JavaScript在运行代码之前,会对整个代码进行一次扫描,进行一些配置(提升,配置全局上下文等),在配置全局上下文的时候,会将整个代码的变量和函数声明全部放入其中,但不会进行语句操作(赋值等),同时,全局上下文会永远放在执行上下文栈的最底部。在完成这些后,才会运行每一个语句。当代码执行流进入函数时,就会创建函数上下文,当然它也是个对象,它会将当前函数中的变量和函数声明放入其中,同时它也会被放入执行上下栈中。函数上下文和全局上下文的一些操作是相通,如果函数中还有函数,那就是不断往上下文栈中push的套娃过程。当函数执行完毕,它的上下文就会被销毁。因此外部无法访问内部的变量,但是内部却可以访问外部的。

当你在函数内部使用变量时,如果它并没有在该函数内部定义,那么JavaScript引擎会帮你找该函数的外层函数,这样一层层的找下去直到最外层。这和对象中属性和方法的搜索机制很像(一个是作用域链一个是原型链)。作用域链就是把上下文栈串了起来,前面的内部的(栈上层的)上下文,可以使用后面的外部的(栈下层的)上下文中的一切,反之则不行。

除了上面两种执行上下文外,eval()调用内部存在第三种上下文(在这里就不细说了)。还有一些语句会在作用域链前端临时添加一个上下文。

  • try/catch语句中的catch块:在前端创建一个新的对象,包含要抛出的错误对象声明
  • with语句:在前端添加指定对象

作用域

变量如果不使用任何关键字声明,会被添加到全局上下文。

  1. var的函数作用域声明
    使用var会将变量自动添加到最近的执行上下文中。
  2. let的块级作用域声明
    作用域由最近一对花括号界定(对于for循环等,在循环条件中let声明的对象,{}内可以使用)
  3. const的常量声明(也是块作用域)
    使用const声明变量时必须同时进行初始化。并且此变量不允许修改,但如果变量是对象,则对象中的属性或方法可以修改。这个特点让我联想到Vue中的props配置,同样是不允许修改,但是如果props传递了对象,则可以通过v-model对这个对象内部进行修改。不知道是Vue开发者留下的问题,还是原生js的const声明的问题。

无论JavaScript是否对变量声明进行提升,都不建议在变量未声明的时候就使用变量。

垃圾回收

对代码中不再使用的变量进行及时销毁,释放其内存。其中的关键就是对于变量的追踪与标记。浏览器的发展史上主要使用过两种标记策略:标记清理和引用计数

标记清理:这也是JavaScript最常用的垃圾回收策略。使用标记方法对不再使用的变量进行标记。

引用计数:变量每次被引用,值+1,当没有变量引用该变量时(值为0),则进行回收。如果两个对象对彼此互相引用,则会造成循环引用的问题

性能提升

多使用const和let声明变量

共享和删除:避免“先创建再补充”式的动态属性赋值,在构造函数中一次性声明所有属性,同时避免delete动态删除属性,而是将不在使用的属性值设置为null

内存泄漏:闭包中的回调会引用外部的变量,导致外部变量一直被引用无法回收。

静态分配:在函数中创建对象,如果函数不断使用,就会不断有对象内存需要回收,从而不断调用垃圾回收程序。可以在外部创建好对象,再将其以参数的形式传入函数,从而进行静态分配。

                       

点击阅读全文

上一篇 2023年 6月 14日 am12:56
下一篇 2023年 6月 14日 am2:42