new绑定 > 显示绑定 > 隐式绑定 > 默认绑定
this是在调用时绑定的
如果要判断一个运行中函数的this绑定,就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面这四条规则来判断this的绑定对象。——你不知道的JavaScript(上卷)
- 由new调用?绑定到新创建的对象。
- 由call或者apply(或者bind)调用?绑定到指定的对象。
- 由上下文对象调用?绑定到那个上下文对象。
- 默认:在严格模式下绑定到undefined,否则绑定到全局对象。
/**
* 优先级
* new绑定和隐式绑定的优先级
*/
function foo(something) {
this.a = something;
}
const obj1 = { foo };
const obj2 = {};
obj1.foo(2); // 此时foo的this指向obj1,所以foo执行时,this.a=2相当于执行了obj1.a=2
console.log(obj1.a);// 2
obj1.foo.call(obj2, 3); // 此时foo的this指向obj2,所以foo执行时,this.a=2相当于执行了obj1.a=2
console.log(obj2.a);// 3
const bar = new obj1.foo(4);
console.log(obj1.a);// 2
console.log(bar.a);// 4
setTimeout/setInterval中函数内的this是指向了window对象,这是由于调用的代码运行在与所在函数完全分离的执行环境上。这会导致这些代码中包含的 this 关键字会指向 window (或全局)对象。
new绑定
- 构造函数new一个对象实例的过程
- 创建一个新对象实例;
- 将构造函数的作用域赋给新对象实例
- 执行构造函数中的代码,为新对象实例添加属性
- 返回新对象实例
默认绑定
在不能应用其它绑定规则时使用的默认规则,通常是独立函数调用。
function sayHi(){
console.log('Hello,', this.name);
}
var name = 'YvetteLau';
sayHi();
// 在调用 Hi() 时,应用了默认绑定,this 指向全局对象(非严格模式下),
// 严格模式下,this 指向 undefined,undefined 上没有 this 对象,会抛出错误。
隐式绑定
函数的调用是在某个对象上触发的,即调用位置上存在上下文对象。典型的形式为 XXX.fun().
对象属性链中只有最后一层会影响到调用位置。 eg :person1.friend.sayHi();
显式绑定
就是通过 call,apply,bind 的方式
call 和 apply的功能相同,都是在调用函数,并修改this指向第一个参数;区别在于传参的方式不一样:
- fn.call(obj, arg1, arg2, ...),调用一个函数, 具有一个指定的this值和分别地提供的参数(参数的列表)。
- fn.apply(obj, [argsArray]),调用一个函数,具有一个指定的this值,以及作为一个数组(或类数组对象)提供的参数。
// node中执行
global.a = 3;
function foo() {
console.log(this.a);
}
const obj = { a: 2 };
foo.call(obj); // 2
// 如果将第一个参数传为一个基本类型2 此时this指向Number引用类型
// 例如 Boolean,String,Number,这个将基本类型转为引用类型的操作成为“装箱”
foo.call(2);
foo.call(null); // 如果把undefined和null作为绑定对象传给call或者apply,此时应用的是默认绑定规则
如果把undefined和null作为绑定对象传给call或者apply,此时应用的是默认绑定规则;
bind
bind(..)会返回一个硬编码的新函数,它会把参数设置为this的上下文并调用原始函数。——你不知道的JavaScript(上卷)
MDN: bind()方法创建一个新的函数,在调用时设置this关键字为提供的值。并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项。
bind返回一个原函数的拷贝,并拥有指定的this值和初始参数。
下面代码是MDN提供的一种bind实现
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
//与ECMAScript5最接近的
//内部IsCallable函数
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 doesn't have a prototype property
fNOP.prototype = this.prototype;
}
// 下行的代码使fBound.prototype是fNOP的实例,因此
// 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
fBound.prototype = new fNOP();
return fBound;
};
}
硬绑定
应用场景:创建一个包裹函数,传入所有的参数并返回接收到的所有值;这就是ES5中bind的由来
/**
* 硬绑定
* 应用场景:创建一个包裹函数,传入所有的参数并返回接收到的所有值
*/
function foo() {
console.log(`foo: ${this.a}`);
}
global.a = 3; // node
window.a = 3; // 浏览器
const obj = { a: 2 };
function bar() {
// 强制将foo的this绑定到obj,对于bar函数的调用方式不会影响foo函数this的指向,
// 这种显式的强制绑定,成为硬绑定
foo.call(obj);
console.log(`bar: ${this.a}`);
}
bar(); // foo: 2 bar: 3
setTimeout(bar, 100); // foo: 2 bar: node环境中是undefined,浏览器中是3
// 硬绑定的bar不可能再修改它的this
bar.call(global); // foo: 2 bar: 3
bar.call(window); // foo: 2 bar: 3
箭头函数
- 箭头函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象,this的指向是不可变的;
- 函数体内的 this 对象,继承的是外层代码块的 this。
- 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
- 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
- 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。