理解this绑定优先级的前提是理解this的绑定规则,理解绑定规则以前,咱们先来了解一下函数"调用位置"。编程
一般来讲,要想找到调用位置,最重要的是分析调用栈(在有的编程模式下,真正的调用位置可能被隐藏起来了,经过调用栈来分析调用位置最为直接)。bash
来个梨子:app
function baz() {
// 当前调用栈是:baz
// 调用位置:全局做用域
console.log('baz');
bar();
}
function bar() {
// 当前调用栈是:baz -> bar
// 调用位置:baz中
console.log('bar');
foo();
}
function bar() {
// 当前调用栈是:baz -> bar -> foo
// 调用位置:bar中
console.log('foo');
}
baz(); // 全局调用
复制代码
如咱们在梨子中标注的同样,你能够把调用栈理解成一个函数链式调用。其实咱们有一种更为简单的方式查找调用栈,那就是JavaScript开发者工具。如图。函数
接下来咱们就看看函数在运行的过程当中调用位置如何决定this的绑定对象。工具
var number = 1;
function baz() {
console.log(this.number);
}
baz(); // 1
复制代码
当函数baz被调用时,this.number被解析成全局变量number。函数在调用时,进行默认绑定,此时的this指向全局对象(非严格模式),严格模式下this为undefined。ui
var number = 1;
function baz() {
"use strict"
console.log(this.number);
}
baz();
复制代码
function baz() {
console.log(this.number);
}
var object = {
number: 1,
baz: baz
};
object.baz(); // 1
复制代码
函数baz()的声明方式,严格来讲是不属于object对象的,可是调用位置会使用object上下文来引用函数。因此咱们能够说object对象"拥有"或者"包含"baz()函数的引用。this
function baz() {
console.log(this.number);
}
var object = {
number: 1,
baz: baz
};
var bar = object.baz();
var number = 2;
bar(); // 2
复制代码
虽然bar是object.baz的一个引用,可是它是引用foo函数自己,因应用了默认绑定。this指向全局变量。spa
function baz() {
console.log(this.number);
}
function loadBaz(fn){
// fn其实就是引用的baz
fn(); // 回调函数的调用位置
}
var object = {
number: 1,
baz: baz
};
var number = 2;
loadBaz(object.baz); // 2
复制代码
参数传递其实也是一种隐式的赋值,所以咱们在传入函数时也会被隐藏赋值,因此,梨子2和梨子1是同样的结果。prototype
function baz() {
console.log(this.number);
}
var object = {
number: 1,
baz: baz
};
baz.call(object); // 1
// 或者baz.apply(object); // 1
复制代码
使用new来调用函数,或者说发生构造函数调用时,会执行下面的操做。code
function baz(number) {
this.number = number;
}
var bar = new baz(1);
console.log(bar.a); // 1
复制代码
使用new来调用baz()时,咱们会构造一个新的对象并绑定到baz()调用中的this上。
前面简单讲解了this绑定的四条规则,你须要作的就是找到调用位置,判断使用那一条规则。可是,有时候,在一个调用位置可能使用了多条规则,应该若是判断了。这里就须要判断规则的优先级(如CSS的权重同样)。
function foo() {
console.log(this.a);
}
var obj1 = {
a:2,
foo: foo
};
var obj2 = {
a:3,
foo: foo
};
obj1.foo(); // 2
obj2.foo(); // 3
obj1.foo.call(obj2); // 3
obj2.foo.call(obj1); // 2
复制代码
function foo(a) {
this.a = a;
}
var obj1 = {
foo: foo
};
var obj2 = {};
obj1.foo(2);
console.log(obj1.a); // 2
obj1.foo.call(obj2, 3);
console.log(obj2.a); // 3
var bar = new obj1.foo(4);
console.log(obj1.a); // 2
console.log(bar.a); // 4
复制代码
function foo(a) {
this.a = a;
}
var obj1 = {};
var bar = foo.bind(obj1);
bar(2);
console.log(obj1.a); // 2
var baz = new bar(3);
console.log(obj1.a); // 2
console.log(baz.a); // 3
复制代码
what?出乎意料呀。bar被绑定到obj1上,可是new bar(3) 并无像咱们预计的那样把obj1.a修改成3相反,new修改了绑定调用bar()中的this。那到底显示绑定和new绑定谁的优先级高?
咱们来看看ES5内置的Function.prototype.bind()(显示绑定-强绑定)的实现。
MDN:Function.prototype.bind()的实现
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
// this instanceof fBound === true时,说明返回的fBound被当作new的构造函数调用
return fToBind.apply(this instanceof fBound
? this
: oThis,
// 获取调用时(fBound)的传参.bind 返回的函数入参每每是这么传递的
aArgs.concat(Array.prototype.slice.call(arguments)));
};
// 维护原型关系
if (this.prototype) {
// 当执行Function.prototype.bind()时, this为Function.prototype
// this.prototype(即Function.prototype.prototype)为undefined
fNOP.prototype = this.prototype;
}
// 下行的代码使fBound.prototype是fNOP的实例,所以
// 返回的fBound若做为new的构造函数,new生成的新对象做为this传入fBound,新对象的__proto__就是fNOP的实例
fBound.prototype = new fNOP();
return fBound;
};
}
复制代码
在这段代码中,会判断绑定函数是否被new调用,
。
。
。 之因此要在new中绑定函数,缘由是预先设置函数的一些参数,这样在使用时,只须要传入剩余的参数。
根据上面的梨子:总结一下:
new绑定优先级 > 显示绑定优先级 > 隐式绑定优先级 > 默认绑定优先级
但愿这个文章能对阅读的你有所帮助。让咱们一块儿成长吧。谢谢!
参考:《你不知道的JavaScript》