Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

从一道前端笔试题分析 javascript 中 this 的使用陷阱 #21

Open
wengjq opened this issue Jan 24, 2018 · 0 comments
Open

从一道前端笔试题分析 javascript 中 this 的使用陷阱 #21

wengjq opened this issue Jan 24, 2018 · 0 comments

Comments

@wengjq
Copy link
Owner

wengjq commented Jan 24, 2018

相信大家都有看过这样的一道 javascript 的笔试题,具体如下:

var length = 10;
function fn () {
  console.log(this.length);
}
var obj = {
    length: 5,
    method: function (fn) {
      fn();
      arguments[0]();
    }
}
obj.method(fn, 1);

这道题主要考察的是 this 的指向和 arguments 对象,第二个输出为 2 ,这里我们不讨论这个话题。第一个输出的如果对 this 理解模糊的话容易答出 5 的答案,其实答案为 10 。

分析:javascript 判断 this 绑定的判定规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含,先思考如下代码:

function fn () {
  console.log(this.a);
}  
var obj = {
  a: 2,
  fn: fn
}
obj.fn(); //2

首先需要注意的是 fn() 的声明方式,及其之后是如何被当作引用属性添加到 obj 中的。但是无论是直接在 obj 定义还是先定义再添加为引用属性,这个函数严格来说都不属于 obj 对象。然而,调用位置会使用 obj 的上下文来引用函数,因此你可以说函数被调用时 obj 对象“拥有”或者“包含”它。

无论你如何称呼这个模式,但 fn() 被调用时,它的前面确实加上了对 obj 的引用。当函数引用有上下文对象时,函数调用中的 this 会绑定到这个上下文对象。因此调用 fn() 时 this 被绑定到 obj ,因此 this.a 和 obj.a 时一样的。

一种最常见的 this 绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把 this 绑定到全局对象或者 undefined 上。
思考下面的代码:

function fn () {
  console.log(this.a);
}
var obj = {
  a: 2,
  fn: fn
}
var bar = obj.fn; //函数别名
bar();//undefined

虽然 bar 是 obj.fn 的一个引用,但是实际上,它引用的是 fn 函数本身,因此此时的 bar() 其实是一个不带任何修饰的函数调用,因此为 undefined 。

回到之前的笔试题,参数传递其实就是一种隐式的赋值,下面的例子的参数传递和上面例子的 var bar = obj.fn 其实是一样的。所以答案为 10 。

var length = 10;
function fn () {
  console.log(this.length);
}
var obj = {
    length: 5,
    method: function (fn) {//fn其实引用的是function fn()
      fn();//这里才是真正的调用位置
      arguments[0]();
    }
}
obj.method(fn,1);

那如果把函数传入到语言内置的函数而不是传入你自己声明的函数,会发生什么呢?结果是一样的,没有区别:

function fn () {
  console.log(this.a);
}  
var obj = {
  a: 2,
  fn: fn
}
var a = "global";
setTimeout(obj.fn,100);

javascript 环境中内置的 setTimeout() 函数的实现和下面的伪代码类似:

function setTimeout (fn,delay) {
  //等待delay毫秒
  fn();//调用位置
}

就像我们看到的那样,回调函数丢失 this 绑定是很常见的。除此之外,还有一种情况 this 的行为出乎我们的意料:调用回调函数的函数可能会修改 this 。在一些流行的 javascript 框架中的事件处理器常会把回调函数的 this 强制绑定到触发事件的 dom 元素上。最后来一道测试题:

function fn () {
  console.log(this.a);
}
var a = 2;
var o = { a:3,fn:fn};
var p = { a:4}
o.fn();//输出?
(p.fn = o.fn)();//输出?

答案:3和2。

最后,分享下分析这种隐式绑定的原则:如果在一个对象内包含一个指向函数的属性,并通过这个属性间接引用函数,只有在这种情况下的 this 才会绑定到这个对象上。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant