这个记录(或说笔记),参照一名【合格】前端工程师的自检清单这篇文章,根据里面所提的某些问题,进行归纳和回答。总结内容,有很多来自各位前辈的文章,在这谢谢各位!
- JavaScript基础自检之原型和原型链
- JavaScript基础自检之变量和类型
- JavaScript基础自检之作用域和闭包
理解词法作用域和动态作用域
可以把作用域理解为变量查找的一套规则,它的工作模型一共有2种:词法作用域和动态作用域。我所知道的大多数语言采用的是词法作用域(C#、JavaScript 和Java),动态作用域相对用的比较少(Bash)。
词法作用域
词法作用域,从名称来看,可以理解为是在「词法阶段」形成的作用域。 那么何为词法阶段? 大部分语言在编译的时候都会有如下的3个阶段:
- 分词/词法分析
- 解析/语法分析
- 生成代码
所谓的词法阶段,就是在「分词/词法分析」时而确定的作用域。由于可以理解为,它是有变量位置以及函数作用域决定。
动态作用域
动态作用域是在运行时确定的,它与词法作用域不同的是,它更关注「在何处调用」,而词法作用域更关注「声明的在何处」。
理解JavaScript的作用域和作用域链
JavaScript的作用域
JavaScript的作用域模型采用的是「词法作用域」,也就是说它更多的关注「变量」声明在何处。这一切都是在「词法阶段」就已经确定了,而不是在「运行时」确定。
JavaScript作用域链
作用域链,我将它理解为:变量查找过程中,依次经历的「作用域」的集合。举个例子:
// var c = '李运华'
function foo() {
var a = '孙婧';
test() {
console.log(a);
console.log(b);
}
test();
}
foo();
在test的作用域中,并没有变量a的声明,就会去它的父级作用域(也即foo的作用域)去找,共经历了「test作用域」和「foo作用域」。而对于变量b而言,foo作用域中也没有,它就会向「顶层作用域」去找,而没有找到,共经历了「test作用域」、「foo作用域」以及「顶层作用域」。「经历的这些作用域」就被成为作用域链,也可以认为是「作用域的嵌套」。
this的原理以及几种不同场景的取值
this是什么?
「this」是在函数被调用时发生的绑定,它的指向完全取函数在哪里被调用(和动态作用域很像)。
this的绑定规则(不同场景的取值)<《你不知道的JavaScript》>
- 默认绑定
默认绑定就是发生在函数独立调用时(或者是其他绑定规则不能用时)。这个例子的foo是独立的函数调用,所以此时this绑定到了全局。而在严格模式下,全局this为undefined,所以会报TypeError。'use strict'; var a = '孙婧'; function foo() { console.log(this.a); // Uncaught TypeError: Cannot read property 'a' of undefined } foo(); - 隐式绑定
隐式绑定的发生与否,取决于调用时是否有上下文对象。以上面的例子来说,obj.foo(),也就意味着,调用foo时,this绑定到了obj这个对象,所以就输出'孙婧';注意观察「var _foo = obj.foo」这句话,将obj.foo的引用赋值给 _foo 这个变量,此时,调用 _foo(),此时发生的是默认绑定(绑定到全局对象<正常模式>或undefined<严格模式>),由于是严格模式,console.log(undefined.a),也就会报 TypeError了。'use strict'; var a = '李运华'; var obj = { foo: function() { console.log(this.a); }, a: '孙婧', } obj.foo(); // 输出:'孙婧' var _foo = obj.foo; _foo(); // 输出:TypeError - 显式绑定
显示绑定,就是使用call或apply进行绑定。具体的使用,可以点击call、apply转到MDN上查看相关文档,这里就不再说了。'use strict'; var a = '李运华'; var obj = { foo: function() { console.log(this.a); }, a: '孙婧', } var _foo = obj.foo; _foo.call(obj); // 输出:
我们继续说上面的例子,调用call,将obj绑定到了_foo方法中的this,所以输出'孙婧' - new绑定
我在JavaScript基础自检之原型和原型链实现过一个类似new操作符功能的函数(newPolyfill),该方法详细描述了new操作符的功能。总的来说,new操作符也是调用apply或call来实现this的上下文绑定。'use strict'; function foo() { this.name = '孙婧'; } var fooInstance = new foo(); console.log(fooInstance.name); // 输出 '孙婧'
闭包的实现原理和作用
闭包的实现原理
什么是闭包?
function foo() {
// foo函数的作用域
var a = '孙婧';
return function() {
// 匿名函数的作用域
cosole.log('a');
}
}
var _foo = foo();
_foo(); // 输出 '孙婧'
从上面的这个例子来看,foo函数返回一个匿名函数,该匿名函数有引用foo词法作用域中的变量a,并且该匿名函数在全局作用域中被调用。
从上面的例子看出,闭包就是函数对所在的词法作用域的引用, 并且不论该函数在何处调用,都可访问其所在的词法作用域。
实现的原理
- 函数对其所在的词法作用域有引用。
- 函数在其所在词法作用域之外被调用。
闭包的作用
我们平常写的JavaScript代码中,多多少少都会用到闭包。比如我们平常的ajax调用、事件的回调、定时器或其他的异步任务中,都会使用到闭包。
写在最后
这篇文章只是归纳了作用域的基础知识,并没有展开去论述。想要去深入学习,推荐大家去学习《你不知道的JavaScript》这本书。