① this 指针
首先,想要搞懂指向问题,首先要晓得 this 指针是什么,它存在的意义是什么。
this 指针是什么?
能够认为 this 是以后函数运行环境的上下文。它是一个指针型的变量,咱们把它了解成一个动静的对象,这个动静的对象就是以后运行环境的上下文。
this 指针存在的意义?
复用!
this 的呈现就是为了进步函数的复用性,调用函数时能够应用不同的上下文:用不同的 this 调用同一个函数,能够产生不同的后果,就像面向对象编程中的 多态 思维。
那么问题来了,既然这个 this 指针是一个动静的变量,咱们在调用函数的时候,要依据什么规定来断定它的指向?
this 指针的调用规定
一般函数(箭头函数另讲)在调用时遵循以下几种规定:
- 默认绑定
- 隐式绑定
- 显式绑定
- new 绑定
且它们之间的优先级是:new 绑定 > 显示绑定 > 隐式绑定 > 默认绑定
① 默认绑定:函数独立调用,不带任何润饰的函数援用
让咱们先看一道经典的面试题。
var name = 'Jack'
var obj = {
name : 'Tom',
foo : function(){console.log(this.name)
}
}
var bar = obj.foo
bar()
在浏览器的严格模式下会输入:Uncaught TypeError
在浏览器非严格模式下会输入:Jack
当函数被独立调用时,默认绑定就会失效。在这个例子中,函数是在全局环境中被调用的,所以 this 会指向全局对象,而严格模式中不容许 this 指向全局对象。
默认绑定中,有一个点须要留神,当函数作为参数传递时,如 setTimeout,setInterval, 非严格模式下 this 仍旧指向全局对象。
var name = 'Jack'
var obj = {
name: 'Tom',
foo: function () {setTimeout(function () {console.log(this.name)
},1000)
}
}
obj.foo()
仍旧输入:Jack
② 隐式绑定:函数在调用时用到了润饰
比方上面这段函数被调用时,是由对象发动的,则 this 指向该对象。
var name = 'Jack'
var obj = {
name: 'Tom',
foo: function () {console.log(this.name)
}
}
obj.foo()
当遇到隐式绑定的链式调用时,遵循 就近准则 。
该准则指的是:间隔函数被调用的最近一个对象,为 this 的指向。
看一个例子:
var name = 'Jack'
var person1 = {
name: 'Tom',
foo: function () {console.log(this.name)
}
}
var person2 = {
name: 'Harry',
friend:person1,
}
person2.friend.foo()
依据就近准则,咱们很容易的就推出了会输入 Tom。
因为在这个例子中,调用 foo()的是 person2.friend,那么 this 就指向了 person1,打印的 name 就是 person1 的 name。
③ 显式绑定:通过 bind/apply/call 扭转指向
所有函数都能够用到 bind/apply/call 是因为这三个办法是挂载在 Function 原型下。
call 和 apply 都是扭转 this 指向后立刻执行,它们的差异就是接管参数的类型不同:call 函数接管的是⼀个参数列表,apply 函数接管的是⼀个参数数组。
func.call(this, arg1, arg2, ...)
func.apply(this, [arg1, arg2, ...])
bind 接管参数列表,且在扭转 this 指向后,返回一个新函数,并不会立刻执行。
(在非严格模式下,如果第一个参数 this 不传,则默认绑定到全局对象上。)
var person = {name:'Jack'}
function addInfo(age,work) {
this.age = age;
this.work = work
}
addInfo.apply(person,['25','HR'])
addInfo.call(person, '28', '前端')
console.log(person.age)
console.log(person.work)
如果在调用显式绑定的办法时,咱们传入的 this 是一个 number 或者 string,那么这个办法会把它转换为对象。
function getType() {console.log(this,typeof this)
}
getType.apply('hi')
getType.apply(123)
输入:
[String: ‘hi’] object
[Number: 123] object
④ new 绑定
new 的时候具体做了什么:
- 创立一个空对象
- 将空对象的 proto 指向原对象的 prototype
- 执行构造函数中的办法
- 返回这个新对象
function study(name){this.name = name;}
var obj = new study('Jack')
console.log(obj.name)
依据 new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定的规定能够试着看一下接下来的例子会返回什么?
function foo() {console.log(this.a) }
var obj1 = {
a: 1,
foo: foo
}
var obj2 = {
a: 2,
foo: foo
}
obj1.foo();
obj2.foo();
obj1.foo.call(obj2);
obj2.foo.call(obj1);
依据显示绑定 > 隐式绑定的规定,输入:1 2 2 1
接下来难度降级
function foo(something) {this.a = something}
var obj1 = {foo: foo}
var obj2 = {}
obj1.foo(2);
console.log(obj1.a);
obj1.foo.call(obj2, 3);
console.log(obj2.a);
var bar = new obj1.foo(4);
console.log(obj1.a);
console.log(bar.a);
输入 2 3 2 4。
首先第一个是输入 obj1.a 的值,显然 obj1.foo(2)是隐式调用,this 指向 obj1, 所以 obj1 中的 a 的值为 2。
obj2.a 的值通过显式绑定的办法赋值的,显式会大于隐式优先级,所以 obj2.a 的值是 3。
当 new obj1.foo(4)时,实质是创立了一个新的对象,且把这个新对象返回了进来,所以对 obj1 自身并没有什么影响。obj1.a 的值仍旧是 2。
bar 的值是一个被 new 进去的新对象,a 的值为 4。
再练一道:
function foo(something) {this.a = something}
var obj1 = { }
var bar = foo.bind(obj1);
bar(2);
console.log(obj1.a);
var baz = new bar(3);
console.log(obj1.a);
console.log(baz.a);
输入:2 2 3
⑤ 箭头函数
以上讲的四种绑定形式都是针对一般函数来说的,ES6 引进的箭头函数则不受后面规定的束缚,它比拟非凡,须要独自来剖析。
一般函数的 this 指向都是在调用的时候决定的,而箭头函数的指向是在定义的时候就决定了。
它有以下几个特色:
- 箭头函数没有 arguments
- 箭头函数没有构造函数
- 箭头函数没有原型对象
- 箭头函数没有本人的 this
好了接下来,看几道面试题坚固一下方才的内容。
var name = '123';
var obj = {
name: '456',
print: function () {function a() {console.log(this.name);
}
a();}
}
obj.print(); // 输入 123
function Foo() {Foo.a = function () {console.log(1);
}
this.a = function () {console.log(2)
}
}
Foo.prototype.a = function () {console.log(3);
}
Foo.a = function () {console.log(4);
}
Foo.a(); // 4
let obj = new Foo();
obj.a(); //2
Foo.a(); //1
var length = 10;
function fn() {console.log(this.length);
}
var obj = {
length: 5,
method: function(fn) {fn(); //10
arguments[0](); //2}
};
obj.method(fn, 1);