js只有词法作用域没有动态作用域
词法作用域意味着作用域是由书写代码时函数声明的位置来决定的。编译的词法分析阶段基本都够知道全部标识符在哪里以及是如何声明的,从而能够预测在执行过程中如何对它进行查找。
eval(),with可以“欺骗“词法作用域,会导致代码运行变慢,不要使用它们
需要明确的是,事实上,javascript并不具有动态作用域,它只有词法作用域,简单明了,但是this机制某种程度上很像动态作用域
另外let声明的变量的块作用域实际上用es3也是可以实现的,就是比较难看而已
{
let a = 2;
console.log(a)
}
console.log(a) //ReferenceError
try{throw 2;}catch(a){
console.log(a); //2
}
console.log(a) //ReferenceError
或者是
{
try{
throw undefined;
}catch(a){
a = 2;
console.log(a); //2
}
}
console.log(a) //ReferenceError
这一切都是因为catch分句具有块作用域,也已在es6之前的环境作为块作用域的替代方案
下面我们来说一下this
学习this的第一步是明白this既不指向函数自身也不指向函数的词法作用域,你也许被这样的解释误导过,但其实他们都是错误的。
this实际上是在函数被调用时发生的绑定,它只想什么完全取决于函数在哪里被调用。
this是运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决月函数的调用方式。
当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个纪录会包含函数在那里被调用(调用栈),函数的调用方法,传入的参数等信息,this就是记录的其中一个属性,会在函数执行的过程中用到。
this全面解析
调用位置
在理解this的绑定之前,首先要理解调用位置:
调用位置就是函数在代码中被调用的位置(而不是声明的位置)。只有仔细分析调用位置才能回答这个问题:这个this到底引用的是什么?
通常来说,寻找调用位置就是寻找“函数被调用的位置“,但是做起来并没有那么简单,因为某些编程模式可能会隐藏真正的调用位置。
最重要的是要分析调用栈(就是为了到达当前执行为之所调用的所有函数)。我们关心的调用位置就在当前正在执行的函数的前一个调用中。
下面我们来看看到底什么是调用栈和调用位置:
function baz(){
// 当前调用栈是:baz
// 因此,当前调用位置是全局作用域
console.log("baz");
bar(); //<-- bar调用的位置
}
function bar() {
// 当前调用栈是:baz -> bar
// 因此,当前调用位置在baz中
console.log("bar");
foo(); //<-- foo调用的位置
}
function foo() {
// 当前调用栈是:baz -> bar ->foo
// 因此,当前调用位置在bar中
console.log("foo");
}
baz(); //<-- baz调用的位置
绑定规则
优先级:
现在我们可以根据优先级来判断函数在某个调用位置应用的是哪条规则。可以按照下面的顺序进行判断:
1.函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象。
var bar = new foo()
2.函数是否通过call,apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象。
var bar = foo.call(obj2)
3.函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。(我认为只要看函数调用时.前面的对象是谁this就指向谁)
var bar = obj1.foo()
4.如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。
var bar = foo()
绑定例外
1.被忽略的this
function foo() {
console.log(this.a);
}
var a=2;
foo.call(null); // 2
2.间接引用
你有可能(有意或者无意地)创建一个函数的“间接引用“,在这种情况下,调用这个函数应该会应用默认绑定规则。
间接引用最容易在赋值时发生:
function foo() {
console.log(this.a);
}
var a = 2;
var o = {
a: 3,
foo: foo
};
var p = {
a: 4
};
o.foo(); // 3
(p.foo = o.foo)(); // 2
p.foo = o.foo;
p.foo(); // 4
o.foo这个的话可以认为foo前面是o,所以this指向了o,然后o.a自然就是3
复制表达式(p.foo = o.foo)()的返回值是目标函数的饮用,因此调用位置是foo()而不是p.foo()或者o.foo()根据我们之前说过的,这里会应用默认绑定。
p.foo = o.foo;
p.foo();
这块的话实际上最后还是p来调用,this指向了p,所以输出4
箭头函数
我们之前介绍的四条规则已经可以包含所有正常的函数。但是es6中介绍了一种无法使用这些规则的特殊函数类型:箭头函数
下面来看接下来的例子
function foo() {
return function (a) {
console.log(this.a);
}
}
var obj1 = {
a:2
}
var obj2 = {
a:3
}
var bar = foo.call(obj1);
bar.call(obj2); // 3
function foo() {
return (a) => {
console.log(this.a);
}
}
var obj1 = {
a:2
}
var obj2 = {
a:3
}
var bar = foo.call(obj1);
bar.call(obj2); // 2 ,这个居然是2,不是3,根据之前讲的这块应该是3才对啊,这是为什么呢
foo()内部创建的箭头函数会捕获调用时foo()的this。由于foo()的this绑定到obj1,bar(引用箭头函数)的this也会绑定到obj1,箭头函数的绑定无法被修改。(new也不行)