inner = 'window';
function say() {
console.log(inner);
console.log(this.inner);
}
var obj1 = (function() {
var inner = '1-1';
return {
inner: '1-2',
say: function() {
console.log(inner);
console.log(this.inner);
}
}
})();
var obj2 = (function() {
var inner = '2-1';
return {
inner: '2-2',
say: function() {
console.log(inner);
console.log(this.inner);
}
}
})();
say();
obj1.say();
obj2.say();
obj1.say = say;
obj1.say();
obj1.say = obj2.say;
obj1.say();
蛮有意思的一道题,在对照答案之前,一定要先自己分析一下每一步的执行结果!
// 答案:
window
window
1-1
1-2
2-1
2-2
window
1-2
2-1
1-2
我们按照顺序一个一个来分析
1. say();
inner = 'window';
function say() {
console.log(inner); // window
console.log(this.inner); // window
}
say();
直接为 inner 赋值,那么就相当于为 window 对象添加一个 inner 属性,并赋值


函数被直接调用,等价于由 window 来调用 say 函数,say() → window.say()。所以 say 函数体内 this 也就指向 window,也就是函数体内的 this.inner → window.inner。
直接访问 inner 属性时,先在函数体内寻找该属性,没有,就向上一层作用域寻找,在全局作用域中找到了该属性,打印。
2. obj1.say();
var obj1 = (function() {
console.log( '1: ', this ); // 1: Window{window: Window,...}
var inner = '1-1';
return {
inner: '1-2',
say: function() {
console.log( '2: ', this ); // 2: {inner: '1-2', say: ƒ}
console.log('3: ', inner); // 3: 1-1
console.log('4: ', this.inner); // 4: 1-2
}
}
})();
obj1.say();
首先,分析 obj1,obj1 保存一个自调用的匿名函数(IIFE),IIFE的调用者是 window,所以函数体内打印的 this 自然就是一个 Window 类型的对象。
2 处打印的 this 也很容易理解,obj1 等于被 return 的对象 { inner: '1-2', say: function(){...} } ,obj1 调用 say 方法,也就相当于 { inner: '1-2', say: function(){...} } 调用 say 方法,所以打印的 this 自然也就是这个对象了。
3 处打印 inner,say 函数体中没有 inner 属性,向上层寻找,这个 “上层” ,指代的是上层作用域,可以产生作用域的语句包括 if、while、switch 等判断、循环语句,以及函数。也就是说,对象是不会产生作用域的,因此,say 函数体的上层作用域,其实对应的是外层的匿名函数 函数体。那么直接打印 inner,找到的也就是在匿名函数中通过 var 定义的 inner 属性。
4 处很简单,上面我们已经分析过对象中 say 函数的 this 指向,这里打印 this.inner 也就可以很直观的看出来,也就等价于 { inner: '1-2', say: function(){...} }.inner。
3. obj2.say();
它和内部的代码和 obj1.say() 没有实质性的区别,分析方法和上面是一样的。
4. obj1.say = say; obj1.say();
为了提升代码整体的可读性,我们对代码进行一个小小的调整
inner = 'window';
function say() {
console.log('1: ', inner); // 1: window
console.log('2: ', this.inner); // 2: 1-2
}
var obj1 = (function() {
var inner = '1-1';
return {
inner: '1-2',
say: function() {
console.log('3: ', inner);
console.log('4: ', this.inner);
}
}
})();
obj1.say = say;
obj1.say();
这里考察的核心,其实就只有一点,就是函数究竟属于什么类型?
如果可以肯定答案是引用类型,那么恭喜你,这道题基本已经被你攻破了!

这么一来,我们就可以确定,对 obj1.say 进行重新赋值后,通过 obj1.say() 调用的say函数,就应该是调用的全局作用域中的 say 函数。
能把这一点分析出来,基本上这道题最难的点就已经被我们拿下了。
把上面的点分析出来后,千万不能迷糊!我们是调用全局作用域中的 say 方法,并不是把全局作用域的 say 方法放到 obj1 里面。
分析出来上一点后,下面就是常规的套路,函数体内没有 inner,就去上一层作用域中查找,找到了全局作用域中的 inner。
由 obj1 调用的say方法,那么 this 就指向 obj1,this.inner 自然就是 { inner: '1-2', say: function() {...} } 对象的 inner 属性。
5. obj1.say = obj2.say; obj1.say();
var obj1 = (function() {
var inner = '1-1';
return {
inner: '1-2',
say: function() {
console.log('1: ', inner);
console.log('2: ', this.inner);
}
}
})();
var obj2 = (function() {
var inner = '2-1';
return {
inner: '2-2',
say: function() {
console.log('3: ', inner); // 3: 2-1
console.log('4: ', this.inner); // 4: 1-2
}
}
})();
obj1.say = obj2.say;
obj1.say();
这个时候,在来看这一题,是不是就发现太简单了。
把 obj2 中的 say 方法赋给 obj1 的 say 属性,前面已经说过了,函数是引用类型。那么,在赋值后,我们通过 obj1.say() 调用的 say 方法,依旧是 obj2 的 say 方法。因此直接打印 inner 属性时,向上层寻找,找的是 obj2 指向的匿名函数体的上下文,打印的自然就是 2-1。
由于是 obj1 调用的 say 方法,那么 this 指向的也是 obj1 中被返回的对象:{ inner: '1-2', say: function() {...} },所以打印出来的自然也就是 1-2 了。