this 绑定是面试题常考的类型,同时,它和原型链、闭包结合在一块儿,能够实现不少复杂的功能。本文将 this 绑定涉及相关知识作了一个概括整理,固然,也从中收获了不少。javascript
这里直接列出 this 绑定的四大规则,这些规则都是《你不知道的JavaScript》一书中提到的关键词,我的以为比较好理解。由于这里只涉及常识性的介绍,若是已经了解的话能够快速跳过,进入下一小节。若是你还不熟悉,可能会碰到不少陌生又让你心痒的概念。这里建议你先暂时放下,我会在后文详细说明,或贴上我以为不错的博客。html
又能够叫函数调用。正如其名,它指的是在没有其它规则的状况下默认使用的绑定规则,通常就是直接调用函数的状况。在通常状况下,默认绑定的对象是全局对象 window
;当函数内部处于严格模式下时,它绑定的是 undefined
。java
case1.1.1面试
// 这里是一般状况下的默认绑定
var a = 0;
function foo() {
var a = 10;
console.log(“a=” + this.a); // window.a
}
foo();
// a=0
复制代码
case1.1.2segmentfault
// 严格模式下的默认绑定(注意 use strict 位置)
var a = 0;
function foo() {
"use strict";
var a = 10;
console.log(“a=” + this.a); // window.a
}
foo();
// a=0
复制代码
能够看到,这里的 this.a
绑定的对象是全局对象。bash
隐式绑定又能够叫对象方法调用,即做为对象方法使用。既然是对象调用,那么就少不了对象的使用。具体的使用状况以下:数据结构
case1.2闭包
var a = 0;
var b = {
a: 10,
foo: function() {
var a = 20;
console.log("a=" + this.a); // b.a
}
};
b.foo();
// a=10
复制代码
其实挺好理解,这里面 this 的绑定对象是 b。可是,使用隐式绑定时,咱们仍然很容易踩到一些坑,这个坑后文会提到。app
显式绑定看似和上面的隐式绑定相对应,其实没太大关系。它又能够叫作 apply/call 调用,其实就是使用了 JavaScript 中很强大的两个方法。这两个方法使用效果,简而言之是强行为所指定函数绑定所指定对象。这里贴上相关的补充连接,不过即便你不看也不影响浏览本文:JS 中的 call、apply、bind 方法详解函数
假如你差很少了解清楚了,看看下面的例子。
case1.3
var a = 0;
var b = {
a: 10
};
var c = {
a: 20, // c.a === 20
foo: function() {
console.log("a=" + this.a);
}
};
c.foo.call(b); // 为foo的this指定绑定对象b
// a=10
复制代码
上面是显式绑定的一个例子。能够看到,首先 foo
是由 c
做为对象方法进行调用的,而后使用了 call
方法显式绑定到 b
上,最后的输出结果就如上。你们也能够猜到,这里显式绑定的优先级高于隐式绑定,至于其它几种组合方式,也等到后文再详细介绍。
也叫构造函数调用。不过,对于 JS 的构造函数,不熟悉的同窗千万别将之与 C/Java 之类的构造函数相提并论,二者实质是彻底不一样的(虽然下例看起来好像很类似)!这里先上代码,更深层次的内容后文再说~
case1.4
var a = 0;
function Foo() {
this.a = 10; // 看成为构造函数时,这里能够看做 a: 10 的声明形式
}
var foo = new Foo();
console.log(foo.a); // foo 此时是一个对象而不是函数
// 10
console.log(Foo.a); // 将 Foo 做为对象调用试试看?
// undefined
复制代码
看起来和上面那三种毫无联系,实际上它的原理与显式绑定紧密相关。若是你看到这里还没懵的话,那就接着下一节看吧~(若是懵的话,能够将上面四个示例在 codepen 敲一遍)
上面总结的四个绑定规则,在实际应用中有各类各样的变式出现。若是对这些意外状况进行仔细分析,也能帮助咱们更好地理解 JS。
上文其实部分涉猎了绑定优先级的问题。很显然,默认绑定的优先级必定是最低的;在 case1.3 中,咱们又知道了显式绑定优先级高于隐式绑定。可是,咱们如何肯定 new 绑定的优先级呢?
在 case1.4 中,咱们了解到构造函数 new 返回的是一个对象,但须要注意的是,这个返回对象是新构造的,即不与原来的任何函数或对象产生联系。怎么理解这一点呢?看下面这个例子
case2.2.1
var obj = {
a: 10,
foo: function(a) {
this.a = a;
}
}
obj.foo(20);
console.log(obj.a);
// 20
var bar = new obj.foo(30);
console.log(bar.a);
// 30
console.log(obj.a);
// 20
复制代码
case2.2.1 能够印证以前所说的。同时,经过这个例子咱们也能够看到,对于 bar
接受的返回值来讲,new 绑定的优先级高于隐式绑定。
那么,new 绑定与显式绑定的优先级如何对比呢?《你不知道的 JavaScript》一书提到一个例子,但这个例子我的以为还不足以说明问题,因此这里将原文的例子进行了改进和补充。
case2.2.2
function foo(a) {
this.a = a;
}
var obj = {};
var bar = foo.bind(obj); // bind 和 apply/call 功能相似,但返回的是一个函数引用
bar(2); // 执行了 this.a = 2
console.log(obj.a);
// 2
var baz = new bar(3); // 将显式绑定返回的函数做为构造函数使用,看看结果怎样?
console.log(obj.a); // 前面提到的,构造函数返回的对象不影响原来的对象或函数
// 2
console.log(baz.a); // 这里说明,new 绑定的结果不受显式绑定的影响
// 3
bar(4); // 假如再执行一次呢?若是无效,说明 new 绑定优先级比较高
console.log(obj.a); // 这里说明,两种绑定实际上互不干扰
// 4
复制代码
其实上面绕来绕去的,理解问题的一个关键就是 new 绑定的原理究竟是什么?这是一个写起来又能撑一篇的问题,老惯例,贴出我认为写得比较好的博客。
这是《你不知道的 JavaScript》中提到的例子,这里将之收集起来,作一个统一汇总。
case2.1.1
var b = {
a: 10,
foo: function () {
console.log(this.a);
}
}
var fn = b.foo;
fn();
// undefined
复制代码
case2.1 中,fn 引用的是一个 foo 函数,而不是 b.foo 的对象方法。也就是说,在赋值操做后,foo 所处的环境是全局域而不是 b 的词法做用域内。
case2.1.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
复制代码
case2.1.2 的缘由与 case2.1.1 是同样的。看到此处,有些人可能对 JS 中奇怪的各类做用域产生疑惑,为何一个赋值操做会改变所处做用域呢?
这里涉及的问题实际上是 JS 的内存数据结构设计的问题。具体的能够点下面连接,看完这个连接,相信你以前的一些疑惑都会获得解答。
这篇文章攒了比较久,说实话本身想写的不少点都还没能写出来。一方面是由于避免写得太杂,因此有一些问题我会贴出博客,若是仍是不能解决能够私信我;另外一方面也是能力有限,即便只是作一些整理性的工做,仍然耗费了我大量的时间,并且再拖下去也没太大意义。因此,这篇我认为还算有深度,勉强算原创的文章就写到这吧。
今天是劳动节,做者没有放假而是一下午肝了这篇,由于这算是上周欠下的了。同时,我计划会在劳动节假期再更一篇,要写哪些方向已经想好了,到时候会从下面两条中挑一条(我但愿我能挑战好第二条,因此给个鼓励吧~)