人类在白色的底色上描绘图画,地球在黑色的底色上创造生命。
变量、作用域与内存
JavaScript的变量可以说是独树一帜。只需要一个(或两个等)关键字(const,let)就可以创建变量,创建时不考虑变量的类型,这是其他语言少有的强大功能。当然强大的功能总是伴随着问题。
值
原始值:Undefined,Null,Boolean,Number,String,Symbol。它们都是按值访问的,因此我们操作的就是储存在变量中的实际值。
引用值:保存在内存中的对象。引用值是按引用访问的(类似指针),我们操作的实际上是引用而不是对象本身。
两者的不同:
- 原始值没有属性,引用值有,但如果使用
new
关键字,就会为原始值创造对象,从而使他们可以获得属性。(实际上字符串原始值,即使不用构造函数创建也有length等属性和方法。但是想要添加属性和方法就必须要使用包装类型对象) - 原始值在复制的时候会获得一个独立的副本,而引用值只是复制了这个引用,它们指向同一个对象。(类似指针那样理解)
传递参数
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语句:在前端添加指定对象
作用域
变量如果不使用任何关键字声明,会被添加到全局上下文。
- var的函数作用域声明
使用var会将变量自动添加到最近的执行上下文中。 - let的块级作用域声明
作用域由最近一对花括号界定(对于for循环等,在循环条件中let声明的对象,{}内可以使用) - const的常量声明(也是块作用域)
使用const声明变量时必须同时进行初始化。并且此变量不允许修改,但如果变量是对象,则对象中的属性或方法可以修改。这个特点让我联想到Vue中的props配置,同样是不允许修改,但是如果props传递了对象,则可以通过v-model对这个对象内部进行修改。不知道是Vue开发者留下的问题,还是原生js的const声明的问题(现在看来关系不大,虽然两者看起来还挺像)。
- 这个问题今天面试官给我做了部分解释:不允许修改是因为方便数据的维护,一个父组件会有多个子组件,如果多个子组件同时修改数据会导致数据紊乱。虽然没有解释Vue开发人员为什么留下一个小“漏洞”。(这个问题等我入职的时候再问(手动狗头))
- 写上面这句话的时候,我又觉得和OS中独木桥类问题有些类似。多个线程走一个独木桥时,必须通过原语保证过桥顺序不乱,当只有当一个线程放开对桥的使用时,下一个线程才可以上桥。这个方法就实现了多个线程过一个桥的方法。如果组件也可以自由修改props数据,那就要采取类似方法,保证修改时没有其他组件正在使用,也不允许其他组件“插队”。
无论JavaScript是否对变量声明进行提升,都不建议在变量未声明的时候就使用变量。
垃圾回收
对代码中不再使用的变量进行及时销毁,释放其内存。其中的关键就是对于变量的追踪与标记。浏览器的发展史上主要使用过两种标记策略:标记清理和引用计数
标记清理:这也是JavaScript最常用的垃圾回收策略。使用标记方法对不再使用的变量进行标记。
引用计数:变量每次被引用,值+1,当没有变量引用该变量时(值为0),则进行回收。如果两个对象对彼此互相引用,则会造成循环引用的问题
性能提升
多使用const和let声明变量
共享和删除:避免“先创建再补充”式的动态属性赋值,在构造函数中一次性声明所有属性,同时避免delete动态删除属性,而是将不在使用的属性值设置为null
内存泄漏:闭包中的回调会引用外部的变量,导致外部变量一直被引用无法回收。文章来源:https://www.toymoban.com/news/detail-480771.html
静态分配:在函数中创建对象,如果函数不断使用,就会不断有对象内存需要回收,从而不断调用垃圾回收程序。可以在外部创建好对象,再将其以参数的形式传入函数,从而进行静态分配。文章来源地址https://www.toymoban.com/news/detail-480771.html
到了这里,关于前端进化笔记-JavaScript(三)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!