毕业参加工作半年了,一直都深陷业务中,很多基础都没有好好掌握。有句话说的很好,就怕你的顿悟只是别人的基础,脱离业务的学习只会是昙花一现,脱离学习沉迷业务也不会有所进步,学习之路任重而道远。
立个Flag,学习+总结+产出。 有时候回过头来看看,也许会比一味的向前出发来的更有意义,加油,打工人!
第一部分:作用域和闭包
第一章:作用域是什么
小结:
1、【 var a=2 】 变量的赋值操作会执行两个动作,首先编译器会在当前作用域声明一个变量(如果之前没有声明过),然后在运行时,引擎会在作用域查找该变量,如果能够找到就会对其进行赋值操作。
2、作用域是一套规则,用于确定在何处以及如何查询变量(标识符),如果查找的目的是对变量进行赋值,那么就进行LHS查询,如果目的是为了获取变量的值,那么就进行RHS查询。
注意:不成功的RHS引用会导致抛出ReferenceError异常。 不成功的LHS引用会自动隐式的创建一个全局变量(非严格模式)、或者抛出ReferenceError异常(严格模式)
第二章:词法作用域
第一章中,我们将“作用域”定义为一套规则,这套规则用来管理引擎如何在作用域以及嵌套的子作用域中根据标识符名称进行变量查询。
2.1词法作用域
概念:
词法作用域就是定义在词法阶段的作用域。词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的,因此词法分析器出炉代码时会保持作用域不变(多数情况下)
遮蔽效应
在多层嵌套的作用域中可以定义同名的标识符,这就叫做“遮蔽效应”(内部的同名变量“遮蔽”了外部的同名变量)
作用域查找:
抛开“遮蔽效应”,作用域查找始终从运行时所处的最内部的作用域开始,逐级向外或者说向上查找,知道遇见第一个匹配的标识符停止
- 全局变量会自动成为全局对象(例如浏览器中的window对象)的熟悉,因此 window.a可以冲出内部同名变量的遮蔽。全局变量可以通过这种技术被访问到,非全局变量如果被遮蔽了无论如何都不能被访问到。
- 函数的词法作用域只跟函数被声明时所处的位置决定,无关哪里被调用、如何被调用
- 此法作用域只会查询一级标识符
- 词法作用域意味着作用域是由书写代码时函数声明的位置决定的。编译的词法分析阶段基本能够知道全部的标识符在哪里以及如何声明,从而能够预测在执行过程中如何对他进行查找。
2.2欺骗词法
eval()
js中eval(...)函数可以接受一个字符串作为参数,并将其视为好像就书写在那里一样。eval可以在运行时期修改书写时期的额词法作用域
with()
with声明实际上是根据你传递给他的对象凭空创建一个全新的词法作用域。
缺点:javascript引擎会在编译阶段进行数项的性能优化,其中有些依赖能够根据代码的词法进行静态分析,并预先确定所有变量和函数的定义位置,才能执行过程中快速找到标识符。 引擎在代码中发现eval和with,可能优化会变得没有意义,最坏的情况是完全不做任何优化。大量的eval和with存在的时候,运行会非常慢。
第三章:函数作用域和代码块
3.1 函数中的作用域
JavaScript具有基于函数的作用域。所以属于这个函数的全部变量都可以在整个函数范围内使用和复用。
3.2 隐藏内部实现
为什么隐藏? 符合软件设计的原则,应该最小限度的暴露必要的内容,而将其他内容都“隐藏”起来,类似于模块或者对象API的设计。
“隐藏”作用域中的变量和函数所带来的的好处: 避免同名标识符之间的冲突。还有当程序引入第三方库的时候,如果没有妥善的将内部私有的函数或者变量隐藏起来,容易引发冲突
3.3 函数作用域
区分函数声明和函数表达式最简单的方法就是看function关键词出现在声明中的位置,出现在整个声明的第一个词就是函数声明,否者就是函数表达式。
3.3.1 匿名和具名
setTimeout(function(){
console.log("i waited 1 second")
},1000)
这就叫做匿名函数表达式,因为function()没有名称标识符。函数表达式可以是匿名的,而函数声明只能是具名的 匿名函数书写简单快捷,很多库会让工具推荐这种风格。缺点如下:
- 匿名函数在栈追踪中没有有意义的函数名,加大调试难度
- 没有函数名,例如递归时引用自身只能选择 argument.callee引用,这种用法已经过期,再例如事件触发后,事件监听器解绑自身。
- 削弱代码可读性,一个有意义的函数名,可以让代码不言自明
总结
1、函数是js中最常见的作用域单元。本质上,声明在一个函数内部的变量或函数会在所处的额作用域中“隐藏”起来,这是一种良好软件的设计原则。
2、函数不是唯一作用域单元。
3、ES3开始 try/catch结构的catch分句中具有块级作用域
4、有人认为块级作用域不能完全作为替代函数作用域,两者应该同时存在,开发者可以按需选择,创造可读、可维护的优良代码
第四章 提升
我们习惯上将 var a=2 看作是一个声明,但实际上javascript引擎并不这么认为,他将var a 和a=2看作是两个单独声明,第一个是编译阶段任务,而第二个则是执行阶段的任务。
第五章 作用域闭包
function foo(){
var a=2
function bar(){
console.log(a)
}
return bar
}
var baz=foo()
baz() //2 函数bar的词法作用域能够访问foo的内部作用域。
//foo执行后,其返回值也就是内部的bar,赋值给了变量baz并且调用baz(),实际上只是通过不同的标识符引用调用了内部的函数bar
总结:
1、当函数可以记住并访问所在的词法作用域,即使是在当前词法作用域外执行,这时就产生了闭包 2、模块的两个主要特征:(1)为创建内部作用域而调用一个包装函数 (2)包装函数的返回值必须至少包括一个对内部函数的引用,这样就创建涵盖整个包装函数内部作用域的闭包。
附录A 动态作用域
首先明确的是,JavaScript的作用域就是词法作用域。JavaScript并不具备动态作用域。但是JavaScript的this机制某种程度上很像动态作用域。
区别:词法作用域是在写代码或者说定义时确定的,而动态作用域是在运行时确定的 词法作用域关注函数在何处声明,动态作用域关注函数从何处调用
附录B 块作用域替代方案
catch分句具有块级作用域,因此它可以在ES6之前的环境中作为块作用域的替代方案。 IIFE和try/catch并不是完全等价的,IIFE并不是一个普适的解决方案,它只适合在某些情况下进行手动操作。
第二部分 this和原型对象
第一章:关于this
当函数被调用时,会创建一个活动记录(有时候也被成为执行上下文)。这个记录会包含函数在哪里被调用、函数的调用方法、传入的参数等信息。this就是记录中的其中一个属性,会在函数执行过程中被用到。
第二章:全面认识this
function foo(){
console.log(this.a)
}
var a=2
foo() //默认绑定
细节 只有foo函数运行在非严格模式下,默认绑定才能绑定到全局对象,虽然调用位置处于严格模式,但是不影响默认绑定。与调用位置所处模式无关!
隐式丢失 虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。
除此之外,回调函数丢失this绑定是非常常见的。
第三章 对象
总结
1、对象就是键/值对的集合。可以通过.propName或者["propName"]语法来获取属性值。访问属性时,引 擎实际上会调用内部的默认[[Get]]操作(在设置属性值时是[[Put]]),[[Get]]操作会检查对象本 身是否包含这个属性,如果没找到的话还会查找[[Prototype]]链
2、属性不一定包含值——它们可能是具备getter/setter的“访问描述符”。此外,属性可以是可枚举或 者不可枚举的,这决定了它们是否会出现在for..in循环中。
3、你可以使用ES6的for..of语法来遍历数据结构(数组、对象,等等)中的值,for..of会寻找内置或 者自定义的@@iterator对象并调用它的next()方法来遍历数据值。
第四章 混个对象 “类”
总结
类是一种设计模式。许多语言提供了对于面向类软件设计的原生语法。JavaScript也有类似的语 法,但是和其他语言中的类完全不同。
类意味着复制。
传统的类被实例化时,它的行为会被复制到实例中。类被继承时,行为也会被复制到子类中。 多态(在继承链的不同层次名称相同但是功能不同的函数)看起来似乎是从子类引用父类,但是本质上引用的其实是复制的结果。
JavaScript并不会(像类那样)自动创建对象的副本。
混入模式(无论显式还是隐式)可以用来模拟类的复制行为,但是通常会产生丑陋并且脆弱的语 法,比如显式伪多态(OtherObj.methodName.call(this, ...)),这会让代码更加难懂并且难以维护。
此外,显式混入实际上无法完全模拟类的复制行为,因为对象(和函数!别忘了函数也是对象)只能 复制引用,无法复制被引用的对象或者函数本身。忽视这一点会导致许多问题。
总地来说,在JavaScript中模拟类是得不偿失的,虽然能解决当前的问题,但是可能会埋下更多的 隐患。