JavaScript中this的五种绑定规则

javascript中的this的绑定规则

this的绑定规则总共有下面5种。

1、默认绑定(严格/非严格模式)

2、隐式绑定

3、显式绑定

4、new绑定

5、箭头函数绑定

1.调用位置

调用位置就是函数在代码中被调用的位置(而不是声明的位置)。

分析调用栈:调用位置就是当前正在执行的函数的前一个调用中

function baz() {
    // 当前调用栈是:baz
    // 因此,当前调用位置是全局作用域
    
    console.log( "baz" );
    bar(); // <-- bar的调用位置
}

function bar() {
    // 当前调用栈是:baz --> bar
    // 因此,当前调用位置在baz中
    
    console.log( "bar" );
    foo(); // <-- foo的调用位置
}

function foo() {
    // 当前调用栈是:baz --> bar --> foo
    // 因此,当前调用位置在bar中
    
    console.log( "foo" );
}

baz(); // <-- baz的调用位置

使用开发者工具得到调用栈:

设置断点或者插入debugger;语句,运行时调试器会在那个位置暂停,同时展示当前位置的函数调用列表,这就是调用栈。找到栈中的第二个元素,这就是真正的调用位置。

2 绑定规则

2.1 默认绑定

​ this是在执行函数时绑定的,而不是在声明的时候绑定的,他的上下文取决于函数调用时的各种条件,和声明的位置没有关系,只和调用的位置有关。

​ 独立函数调用,可以把默认绑定看作是无法应用其他规则时的默认规则,this指向全局对象。
​ 严格模式下,不能将全局对象用于默认绑定,this会绑定到undefined。只有函数运行在非严格模式下,默认绑定才能绑定到全局对象。在严格模式下调用函数则不影响默认绑定。

function foo() { // 运行在严格模式下,this会绑定到undefined
    "use strict"
    console.log( this.a );
}
var a = 5;
// 调用
foo(); // TypeError: Cannot read property 'a' of undefined
// --------------------------------------
function foo() { // 运行
    console.log( this.a );
}
var a = 3;
(function() { // 严格模式下调用函数则不影响默认绑定
    "use strict"
    foo(); // 3
})();
// --------------------------------------
var a = 9;
function abc() {
    "use strict"
    console.log(this.a)//this.a被解析成了全局的a,等同于window.a
}
abc() // 9

2.2 隐式绑定

当函数引用有上下文对象时,隐式绑定规则会把函数中的this绑定到这个上下文对象。对象属性引用链中只有上一层或者说最后一层在调用中起作用。

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

并且,对象属性引用链中,this指向离调用函数最近的上下文对象

function abc() {
    console.log(this.a)
}
var obj1 = {
    a: 56,
    abc: abc
}
var obj2 = {
    a: 33,
    obj1 : obj1 
}
obj2.obj1.abc() // 56

隐式丢失:被隐式绑定的函数特定情况下会丢失绑定对象,应用默认绑定,把this绑定到全局对象或者undefined上。

// 虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身。
// bar()是一个不带任何修饰的函数调用,应用默认绑定。
function foo() {
    console.log( this.a );
}
var obj = {
    a: 2,
    foo: foo
};
var bar = obj.foo; // 函数别名
var a = "oops, global"; // a是全局对象的属性
bar(); // "oops, global"
//-----------------------------------------------------
//参数传递就是一种隐式赋值,传入函数时也会被隐式赋值。回调函数丢失this绑定是非常常见的。
function foo() {
    console.log( this.a );
}
function doFoo(fn) {
    // fn其实引用的是foo  
    fn(); // <-- 调用位置!
}
var obj = {
    a: 2,
    foo: foo
};
var a = "oops, global"; // a是全局对象的属性
doFoo( obj.foo ); // "oops, global"
//----------------------------------------------------
var a = 9 // 全局变量
function abc() {
    console.log(this.a)
}
var obj = {
    a: 99,
    abc: abc
}
var baz = obj.abc
baz() // 9

2.3 显式绑定

要达到隐式绑定的效果,必须在一个对象内部包含一个指向函数的属性,通过调用这个属性间接引用函数,从而把this间接绑定到这个对象上。如果我们不想在对象内包含函数引用,而想在某个对象上强制调用函数,达到把this绑定到该对象上,那就要用到显式绑定。

显式绑定依赖于javascript给所有函数提供的两个方法:call()和apply()

通过call(…) 或者 apply(…)方法。第一个参数是一个对象,在调用函数时将这个对象绑定到this。因为直接指定this的绑定对象,称之为显示绑定。

function foo() {
    console.log( this.a );
}
var obj = {
    a: 2
};
foo.call( obj ); // 2 通过.call()方法,在调用函数foo时强制把foo的this绑定到obj上

如果传入的是一个原始值,比如字符串、数字或者布尔类型,那么调用的时候回自动转换成其对应的对象形式:new String()、new Number()、new Boolean()

解决方案:

1、硬绑定
创建函数bar(),并在它的内部手动调用foo.call(obj),强制把foo的this绑定到了obj。这种方式让我想起了借用构造函数继承,没看过的可以点击查看 JavaScript常用八种继承方案

function foo() {
    console.log( this.a );
}
var obj = {
    a: 2
};
var bar = function() {
    foo.call( obj );
};
bar(); // 2
setTimeout( bar, 100 ); // 2
// 硬绑定的bar不可能再修改它的this
bar.call( window ); // 2
//典型应用场景是创建一个包裹函数,负责接收参数并返回值。
function foo(something) {
    console.log( this.a, something );
    return this.a + something;
}
var obj = {
    a: 2
};
var bar = function() {
    return foo.apply( obj, arguments );
};
var b = bar( 3 ); // 2 3
console.log( b ); // 5
//创建一个可以重复使用的辅助函数。
function foo(something) {
    console.log( this.a, something );
    return this.a + something;
}
// 简单的辅助绑定函数
function bind(fn, obj) {
    return function() {
        return fn.apply( obj, arguments );
    }
}
var obj = {
    a: 2
};
var bar = bind( foo, obj );

var b = bar( 3 ); // 2 3
console.log( b ); // 5

每次调用bar()函数的时候,内部都会显示的将this指向obj,bar.call()是无法修改this指向的,这种显式的强制绑定称为硬绑定

//ES5内置了Function.prototype.bind,bind会返回一个硬绑定的新函数,把你指定的参数设置为this的上下文并调用原始函数,用法如下。
function foo(num) {
    console.log(this.a, num)
    return this.a + num
}
var obj = {
    a: 4
}
var bar = foo.bind(obj)
var b = bar(3) // 4 3
console.log(b) // 7

2、API调用的“上下文”
JS许多内置函数提供了一个可选参数,被称之为“上下文”(context),其作用和bind(…)一样,确保回调函数使用指定的this。这些函数实际上通过call(…)和apply(…)实现了显式绑定。

function foo(el) {
	console.log( el, this.id );
}

var obj = {
    id: "awesome"
}

var myArray = [1, 2, 3]
// 调用foo(..)时把this绑定到obj
myArray.forEach( foo, obj );
// 1 awesome 2 awesome 3 awesome

2.4 new绑定

在JS中,构造函数只是使用new操作符时被调用的普通函数,他们不属于某个类,也不会实例化一个类。
包括内置对象函数(比如Number(…))在内的所有函数都可以用new来调用,这种函数调用被称为构造函数调用。
实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”。
使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。

1、创建(或者说构造)一个新对象。
2、这个新对象会被执行[[Prototype]]连接。
3、这个新对象会绑定到函数调用的this。
4、如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
使用new来调用foo(…)时,会构造一个新对象并把它(bar)绑定到foo(…)调用中的this。

function foo(a) {
    this.a = a;
}

var bar = new foo(2); // bar和foo(..)调用中的this进行绑定
console.log( bar.a ); // 2

手写一个new实现

function create() {
	// 创建一个空的对象
    var obj = new Object(),
	// 获得构造函数,arguments中去除第一个参数
    Con = [].shift.call(arguments);
	// 链接到原型,obj 可以访问到构造函数原型中的属性
    obj.__proto__ = Con.prototype;
	// 绑定 this 实现继承,obj 可以访问到构造函数中的属性
    var ret = Con.apply(obj, arguments);
	// 优先返回构造函数返回的对象
	return ret instanceof Object ? ret : obj;
};

function create() {
	// 创建一个空的对象
    var obj = new Object(),
	// 获得构造函数,arguments中去除第一个参数
    Con = [].shift.call(arguments);
	// 链接到原型,obj 可以访问到构造函数原型中的属性
    obj.__proto__ = Con.prototype;
	// 绑定 this 实现继承,obj 可以访问到构造函数中的属性
    var ret = Con.apply(obj, arguments);
	// 优先返回构造函数返回的对象
	return ret instanceof Object ? ret : obj;
};

function Person() {
    console.log(a)
}
// 使用内置函数new
var person = new Person(a=5)    //5                    
// 使用手写的new,即create
var person = create(Person, a)   //5
var a=1;

代码原理解析:

1、用new Object()的方式新建了一个对象obj

2、取出第一个参数,就是我们要传入的构造函数。此外因为 shift 会修改原数组,所以 arguments会被去除第一个参数

3、将 obj的原型指向构造函数,这样obj就可以访问到构造函数原型中的属性

4、使用apply,改变构造函数this 的指向到新建的对象,这样 obj就可以访问到构造函数中的属性

5、返回 obj

2.5es6箭头函数

es6中用箭头定义函数=>,不遵循this的四条规则,而是根据外层作用域来决定this:

function foo() {
    return (a) => {
        console.log(this.a)
    }
}
var obj = {
    a: 7
}
var obj1 = {
	a: 9
}
var obj2 = {
	a: 11
}
var bar = foo.call(obj)
bar.call(obj1) // 7
bar.call(obj2) //7

结果是7,而不是9,这是因为foo()内部创建的箭头函数会捕获调用时foo()的this,由于调用时this绑定了obj,bar(引用箭头函数)的this也会绑定到obj,箭头函数的绑定无法被修改

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值