前言:虽然敲代码有一段时间了,但是都是在前辈早已写好的基础上进行代码完善的,调用的属性、变量都是被提前封装好的。而自己也发现了对js的知识有点欠缺,从今天开始抽空看这本书,并更新自己的学习笔记。
目录
上册
Ⅰ.作用域和闭包
一、作用域:
1.引擎:负责js程序的编译及执行过程
2.编译器:负责语法分析及代码生成
3.作用域:负责收集并维护由所有声明的标识符组成的一系列查询。
- LHS查询:赋值操作的目标是谁
- RHS查询:谁是赋值操作的源头
遍历嵌套作用域链的规则:引擎从当前执行作用域开始查找变量,若未找到则向上一级继续查找,当抵达最外层的全局作用域时,不管是否找到,都会停止查找。
4.严格模式:严格模式禁止自动或隐式地创建全局变量。(也就是说,若对一个变量没有提前声明而是直接赋值的话会抛出异常)
5.异常:ReferenceError同作用域判别失败有关;TypeError代表作用域判别成功,但是对结果的操作是非法的或者不合理的(例如对一个非函数类型的值进行函数调用)
二、词法作用域
1.词法作用域:定义在词法阶段的作用域;
2.欺骗词法:运行时修改作用域(不推荐,会降低性能,且在严格模式下eval和with都无意义)
- eval(...)
- with(...)
三、函数作用域和块作用域
1.函数作用域:属于这个函数的全部变量都可以在整个函数的范围内使用及复用。
function foo(){
function bar(a){
i=3;//这里的i将for中的i覆盖掉了,导致for循环成为了一个无限循环
console.log(a+i);
}
for(var i=0;i<10;i++){
bar(i*2);
}
}
foo();
上述代码片段是个无限循环,函数bar中的i将for循环中的i重新赋值了,导致for循环中的i一直为3且小于10,解决方法是:给bar中的i加上关键字var,或者将bar中的i改为j,或者将for中的var改为关键字let
(var没有自己的作用域,而在ES6中关键字let是有自己的作用域的)
2.块作用域:相当于在“块”声明的变量只能在“块”中读取它的值
- try/catch:catch分句会创建一个块作用域,但不影响静态检查工具对在不同catch中的相同变量名发起警告
- let:有自己的作用域,因此同var不同,不能进行作用域声明提升。
- const:与let相同的是能创建块作用域,与let不同的是它创建的是常量,不能修改其值。
四、提升
1.声明提升:只有声明本身会提升,而赋值或其他运行逻辑不会。
2.函数声明:函数声明会被提升但是函数表达式不会。
foo();
function foo(){
console.log(a); //undefined
var a=2;
}
==>相当于
function foo(){
var a;
console.log(a);
a=2;
}
foo()
因而第一段的写法不会报错
注意:函数表达式是不能进行声明提升的
foo(); //TypeError
var foo=function bar(){
...
}
==>相当于
var foo;
foo();
foo=function bar(){
..
}
3.优先级:函数声明的优先级高于变量声明
foo(); /
var foo;
function foo(){
console.log(1);
}
foo=function(){
console.log(2);
}
//最终的输出结果为1
==>相当于
function foo(){
console.log(1);
}
foo();
foo=function(){
console.log(2);
}
五、作用域闭包
1.闭包:在函数词法作用域以外的地方被调用。(外部函数在被调用后其作用域应该被销毁,但由于闭包的存在阻止了它的销毁,使函数的内部作用域仍然存在,可以让我们继续访问外部函数的内部变量)
2.闭包的应用:定时器、事件监听器、Ajax请求、跨窗口通信、Web Workers或者其他异步或同步任务中使用了回调函数。
3.模块模式的必备条件:
- 必须有外部的封闭函数,且该函数至少被调用一次;
- 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。
模块模式可以用来命名将要作为公共API返回的对象:
var foo=(function CoolModule(id){
function change(){
//修改公共API
publicAPI.identify=identify2;
}
function identify1(){
console.log(id);
}
function identify2(){
console.log(id.toUpperCase());
}
var publicAPI={
change:change,
identify:identify1
}
return publicAPI;
})('foomodule');
foo.identify();//'foomodule'
foo.change();
foo.identify();//'FOOMODULE'
通过在模块示例的内部保留对公共API对象的内部引用,可以从内部对模块实例进行修改,包括增删改查等方法和属性,以及修改他们的值。
ES6的模块没有“行内”格式,必须被定义在单独的文件中(一个文件一个模块)。浏览器能够在导入模块时同步地加载模块文件。
- import:可以将一个模块中的一个或多个API导入到当前作用域中,并分别绑定在一个变量上(eg.import xx from 'bar');
- module会将整个模块的API导入并绑定到一个变量上(eg.module foo from 'foo');
- export会将当前模块的一个标识符(变量、函数)导出为公共API
模块的特征:
- 为创建内部作用域而调用了一个包装函数;
- 包装函数的返回值必须匹配一个对内部函数的引用,这样就会创建涵盖整个包装函数内部作用域的闭包。
六、补充
1.词法作用域与动态作用域:词法作用域关注函数在何处声明;动态作用域关注函数从何处调用。
function foo(){
console.log(a);
}
function bar(){
var a=3;
foo();
}
var a=2;
bar();
//最终foo()的输出结果为2
注意:JavaScript并不具有动态作用域,它只有词法作用域,所以上述示例的最终打印结果为2,而不是3。但是this机制在某种程度上很像动态作用域。
2.箭头函数:()=>{}
箭头函数放弃了所有普通this绑定的规则,用当前的词法作用域覆盖了this本来的值。
Ⅱ.this和对象原型
一、this
1.关于this的误解:
①this指向函数自身(误解)
function foo(num){
console.log('foo:'+num);
this.count++;
}
foo.count=0;
for(var i=0;i<10;i++){
if(i>8){
foo(i);
}
}
console.log('count:'+foo.count);
最终执行结果为: foo:9
count:0
foo()虽然调用了一次,但是count仍然为0,所以this指向函数自身是不准确的。
②this指向函数的作用域:某些情况是正确的,明确的是this永远不会指向函数的词法作用域。
2.this:this是在运行时绑定的,并不是在编写时绑定的,它的上下文取决与函数调用时的各种条件。也就是说,this的绑定和函数声明的位置没有关系,只取决于函数的调用方式。
- 调用位置:函数在代码中被调用的位置(不是声明的位置)
- 调用栈:为了达到当前执行位置所调用的所有函数
3.绑定规则:
①默认绑定:绑定到全局对象上(简单的函数调用)
function foo(){
console.log(this.a) //这里的this为默认绑定
}
var a=2;
foo(); //2
//严格模式下
function foo(){
'use strict';
console.log(this.a)
}
var a=2;
foo(); //TypeError:this is undefined
注意:严格模式下,不能将全局对象用于默认绑定
②隐式绑定:调用位置是否有上下文对象。在对象属性引用链中只有上一层或者说最后一层在调用位置起作用。
function foo(){
console.log(this.a);
}
var obj1={
a:2,
foo:foo
};
var obj2={
a:4,
obj1:obj1
}
obj2.obj1.foo(); //2
③显示绑定:用call、apply方法指定this的绑定对象;ES5中提供了一个内置方法Function.prototype.bind。
bind返回一个硬编码的新韩淑,会把指定的参数设置为this的上下文并调用原始函数。
④new绑定:在JavaScript中的构造函数只是使用new操作符调用的普通函数。
使用new来调用函数时,会自动执行以下操作:
- 创建一个全新的对象;
- 这个新对象会被Prototype连接;
- 这个新对象会绑定到函数调用的this
- 若函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
function foo(a){
this.a=a;
}
var bar=new foo(2);
console.log(bar.a);//2
4.绑定优先级:new绑定>显示绑定>隐式绑定>默认绑定