Open hujiejeff opened 4 years ago
编译阶段,词法作用域根据函数定义位置来决定作用域。
这样每个函数的作用域嵌套也就确定下来了。
标志符查找从当前作用域开始,向上查找,一旦查找到了就会停止,也即内部优先。
但是js有两个部分会临时产生作用域,即eval
和with
。
function show(str, b) {
eval(str);
console.log({a,b});
}
var a = 10;
show('var a = 20', 100);
这个部分运行时调用eval,声明了一个a=20
,屏蔽了外层作用域的a
,这个修改时发生在运行期间。原本的行为是会加载外出作用域的a的。
使用某个对象作为作用域,但是如果没有查找到则会,发生之前的作用域的查找失败动作。
let obj = {a: 'ff'};
with(obj) {
a = 10;
b = 20;//此作用域不存b,非严格模式下将在外层查找,直到没找到,隐式创建全局。
}
一般来说,作用域分为,全局作用域和函数作用域,正常情况下,包括其它的编程语言都存在块作用域,出了这个就无法访问块作用域,片片JS的var
就不是这样的23333,比如
var a = true;
if (a) {
var b = 'hello';
}
console.log(b);//hello
原因就是var没有把标志放到块作用域中,而是外层的作用域,所以导致这样的结果。
在ES6,出现了let
和const
这两个显示指定放入块作用域的声明关键字,这样声明的标志就会只属于当前的块作用域,而不会流至外层。
那么ES6之前要怎么样实现块作用域,通过throw catch语句可以实现
try {
throw undefined;
} catch(aa) {
aa = 6;
console.log(aa)
}
console.log(aa);//这里就访问不到了
考虑下面的代码
console.log(a);
var a = 9;
console.log(a);
会出现什么结果? 答案是
undefined
9
这就牵扯到了,编译器到底对于声明和赋值语句做了什么样的动作了,也就是所有声明会放在开头,而赋值则原地不动。 上述代码等同于
var a;
console.log(a);
a = 9;
console.log(a);
这样以来也就能对应我们的输出结果了。
编译器会将代码的声明提升,而赋值不动
var a = 5
实际上在编译器理解下是一个声明语句加上一个赋值语句,记住这一点。
这两种声明都会提升,但是函数声明会被优先提升
foo();
var foo = 10;
function foo() {
console.log('xxx');
}
这种情况下可以正常运行输出xxx
,不会提示TypeError,因为函数优先(一等公民不是盖的😀)
函数可以记住并访问所在词法作用域时,就产生了闭包效果,即使是函数是在当前词法作用域之外运行
即函数引用了外部作用域,即使这个外部作用域的执行环境已经不存在。这就叫闭包
于是闭包比我们想像中要常见得多
我们喜欢拿来当作闭包例子,函数返回函数引用。这可能是看到的教程中都会用这样类似的例子,告诉这里就有一个闭包效果。
function exampel() {
let msg = 'hello';
return function() {
console.log(msg);
}
}
let show = exampel();
show();
其实还有其它的例子
function foo() {
var a = 2;
function baz() {
console.log(a); // 2 }bar( baz );
}
bar(baz)
}
function bar(fn) {
fn(); // 妈妈快看呀,这就是闭包!
}
以及我们经常用到的timer
function delayShow(msg, delay) {
setTimeout(function() {
console.log(msg)
}, delay);
}
匿名函数对于外部的msg
引用着,即使离开delayShow
这个函数执行环境,在下一次的事件轮询队列中,匿名函数仍然能够访问msg
,这也是一个闭包。确实这个不太容易注意到,因为太常见了呃呃
以及其它的回调其实都在使用闭包,而不是只是我们第一个例子的返回函数闭包
new绑定>显示绑定>隐式绑定>默认绑定 在同时使用了两种绑定的情况下,this的指向问题取决于绑定方式的优先级,即使是显式绑定的bind绑定也会this变成new的this
function show() {
console.log(this.a)
}
var a = '默认绑定';
默认绑定给了window,或者全局对象globalThis
function show() {
console.log(this.a);
}
let obj = {a: '隐式绑定'};
obj.show = show;
obj.show();//隐式绑定
function show() {
console.log(this.a);
}
let obj = {a: '显式绑定'};
show.call(obj);
function show() {
this.a = 'aa';
console.log(a);
}
let obj = new show();
当绑定的this是null或者undefined是,会忽略绑定,进而走向默认绑定
作用域是什么?
首先JS脚本执行过程分为:编译,运行。但是编译不会生成像Java编译结果,而是进行一些词法分析优化等 作用域是一套规则,用于帮助JS引擎查询标识符在何出、以及如何查询,通俗理解为一个领域。 JS脚本编译期间会识别声明语句,将声明的标识符放在适当作用域中。执行期间JS引擎则根据作用域去查找标志符,这也就是函数声明写在开头结尾都是可以,因为被编译器进行识别了。
LHS
左查询,查找某个标识符即将赋值,执行期间JS引擎遇见赋值语句比如:
此时JS引擎会去作用域中查找
a
这个标识符,查询从当前作用域开始、一直查询到全局作用域。 如果没有查询到,如果在非严格模式下LHS查询会自动隐式的创建一个变量并且加入作用域,而严格模式下则并不会隐式创建,抛出ReferenceError错误。 这样也就能解释原来的一些怪异现象,隐式赋值到window对象。RHS
右查询,查询某个标志符,获取内容
同样JS引擎会去作用域找RHS标识符,也是同样查询到全局作用域。接下这个查询过程的结果,会遇见我们很熟悉的两个错误。