this的绑定规则

375 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第14天,点击查看活动详情

this是指向当前函数的运行环境的上下文, 可以当做是一个指针, 可以理解为一个动态的对象, 普通函数中的this是在调用时确认指向. this的出现使我们在使用函数时候可以使用不同的上下文, 通过不同的this调用同一个函数, 可以产出不同的结果。下面我们来说说this的绑定规则

this的绑定规则

1. 默认绑定

指函数独立调用的时候,不带任何修饰的函数引用。

  • 非严格模式下 指向全局对象(浏览器下指向 Window,Node.js 环境是 Global )
  • 严格模式下,严格模式不允许this指向全局对象。会显示undefined , 比如如下代码, 如果我们将代码在浏览器环境下执行, 严格模式和非严格模式的结果是不同的:
  • 非严格模式会输出 hello
  • 严格模式会出一个Uncaught TypeError: Cannot read properties of undefined的错误
var a = 'aaa'
var obj = {
    a: 'bbb',
    foo: function() {
        // 'use strict';
        console.log(this.a)
    }
}
var bar = obj.foo
bar() // aaa

普通函数做为参数传递的情况, 比如setTimeout, setInterval, 非严格模式下的this指向全局对象

var name = 'aaa';
var person = {
    name: 'hahahahahah',
    sayHi: sayHi
}
function sayHi(){
    console.log(this); // { name: hahahahhah, sayHi: Fn }
    setTimeout(function(){
        console.log('Hello,', this.name); // Hello, aaa
    })
}
person.sayHi();

2. 隐式绑定

与默认绑定相反, 谁调用则指向谁,比如说某个对象调用的函数则指向某个对象。 比如下面这段代码, foo 方法是作为对象的属性调用的,this 指向 obj 对象。 所以但下面代码obj调用foo函数的时候,。this指向的是obj

var a = 'hello'

var obj = {
    a: 'aaa',
    foo: function() {
        console.log(this.a)
    }
}

obj.foo(); // aaa

链式调用的情况,链式调用遵循就近原则,从下面我们可以清晰的看到,是指向离调用函数最近的对象friend,friend是preson2所以打印出的this.name是aaa

function sayHi(){
    console.log('Hello,', this.name);
}
var person2 = {
    name: 'aaa',
    sayHi: sayHi
}
var person1 = {
    name: 'hahhahaahh',
    friend: person2
}

person1.friend.sayHi(); // Hello, aaa

3. 显式绑定

显示绑定指的是通过函数call apply bind 可以修改函数this的指向

call 和 apply

call和apply都是改变函数的this指向并且执行

  • call和apply的第一个参数会绑定到函数体的this上,如果不传参数,例如fun.call(),非严格模式,this默认还是绑定到全局对象
  • call和apply只有在接受参数上有所区别,call函数接收的是一个参数列表,apply函数接收的是一个参数数组。
func.call(this, arg1, arg2, ...)
func.apply(this, [arg1, arg2, ...])
var person = {
    "name": "aaa"
};

function changeWork(company, work) {
    this.company = company;
    this.work = work;
};

changeWork.call(person, 'company1', 'bbb');
console.log(person.work); // 'bbb'

changeWork.apply(person, ['company2', 'ccc']);
console.log(person.work); // 'ccc'

调用call和apply时, 传入的是基本类型数字或者字符串, 绑定this的时候会把他们转换成对象

function getThisType () {
    console.log('this指向内容',this, typeof this);
}
getThisType.call(1); // this指向内容 Number {1} object
getThisType.apply('aaa');  // this指向内容 String {'aaa'} object

bind

bind 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数.

func.bind(thisArg[, arg1[, arg2[, ...]]])

看下面这段代码

var publicAccounts = {
    name: 'aaa',
    author: 'bbb',
    subscribe: function(subscriber) {
        console.log(`${subscriber} ${this.name}`)
    }
}

publicAccounts.subscribe('bbb')  

var subscribe1 = publicAccounts.subscribe.bind({ name: '测试名称A', author: '测试作者B' }, '测试订阅者C')

subscribe1()       // 测试订阅者C 测试名称A

4. new绑定

new绑定的前提我们先来了解一下new做了 哪几个操作

  1. 创建一个空对象,可以是object.creat()
  2. 将空对象的 proto 指向原对象的 prototype
  3. 执行构造函数中的代码
  4. 返回这个新对象

构造函数中的this指向了新生成的实例studyDay.

function study(name){
    this.name = name;
}
var studyDay = new study('aaa');
console.log(studyDay); // {name: 'aaa'}
console.log(studyDay.name); // aaa

5. this绑定的优先级

new绑定 > 显式绑定 > 隐式绑定 > 默认绑定

显示绑定优先级比隐式绑定更高。

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(something) {
    this.a = something
}
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(something) {
  this.a = something
}
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