javascript之apply,call,bind与this绑定

this绑定

在javascript中,this的指向通常是一个令人头痛的问题,js中的this的指向与真正的有类的概念的语言(java,c++,c#)不同,js中的this并不是表示指向自身,而是会随着调用上下文和作用域而改变。

通常this的绑定有四种情况

1.隐式绑定

即作为对象的函数进行调用,此时this指向上下文的对象

function sayName() {
    console.log(this.name);
}

let person = {
    name: "Jack",
    sayName: sayName
}

person.sayName()  // Jack
复制代码

2显示绑定

即通过apply,call和bind进行绑定

// apply, call软绑定, 绑定后可以更改this的指向
function sayName() {
    console.log(this.name);
}

let person = {
    name: 'Jack'
}

let child = {
    name: 'Tim',
    say: say
}

sayName.apply(person);  // Jack
sayName.apply(child);  // Tim
sayName.call(person);  // Jack
sayName.call(child);  // Tim
//bind 硬绑定,一旦绑定,便无法更改this的指向
let say = sayName.bind(person);
say();  // Jack

child.say();  // Jack
let log = say.bind(child);
log();  // Jack, this并没有更改指向
let says = sayName.bind(child);
says();  // Jack, this也没有更改指向
复制代码

3.new绑定

当用new创建一个对象时,this指向这个对象

function Person(name) {
    this.name = name;
}
let person = new Person('Jack');
console.log(person.name);  // Jack
复制代码

4.默认绑定

当不符合上面三种情况时,则使用默认绑定规则。严格模式下绑定到undefined,普通模式下绑定到window(nodejs中绑定到global)

var name = 'Jack';
function sayName() {
  console.log(this.name);
}
let person = {
  name: 'Tim',
  sayName: function() {
    sayName();
  }
};
person.sayName();
复制代码

上面的代码在nodejs和浏览器环境下的结果是不同的。虽然nodejs中默认绑定会绑定到global对象,但是上面的代码结果是输出undefined;在浏览器环境下会输出Jack

如果将var换成let声明变量方式,浏览器环境下回输出空字符串,而nodejs下回输出undefined

绑定的优先级:new > 显示 > 隐式 > 默认

胖箭头函数的this绑定

胖箭头函数没有自己的this,胖箭头函数的this继承自外层代码块所在作用域的this,如果外层代码块不存在this,则继续向上查找。

let person = {
  fun1: function() {
    console.log(this);
    return () => {
      console.log(this);
    }
  },
  fun2: function() {
    console.log(this);
    return function() {
      console.log(this);
    }
  },
  fun3: () => {
    console.log(this);
  }
}

let fun1 = person.fun1();  // person
fun1();  // person
let fun2 = person.fun2();  // person
fun2();  // window
person.fun3();  // window
/*
第一次函数调用,采用隐式绑定,this指向上下文对象,也就是person
第二次函数调用,调用了胖箭头函数,胖箭头函数的this继承自上层代码块中的this,上层
代码块中的this指向person,所以胖箭头函数的this也指向person
第三次函数调用,采用隐式绑定,this指向上下文对象
第四次函数调用,既没有隐式绑定条件,没有显示绑定条件,也没有new绑定条件,所以采用默认绑定规则,this指向window
第五次函数调用,调用了胖箭头函数,而上层person中没有this,所以向上查找,person的上层为window,所以此次胖箭头函数的this指向window
*/
复制代码

apply,call,bind详解

apply

apply接受两个参数,第一个参数指定函数体内this对象的指向,第二个参数是一个数组或类数组,并将此数组或类数组作为参数传递给所调用的函数

let person = {
  0: "Jack",
  1: "Tim",
  length: 2
}
let persons = [
  "Jack",
  "Tim"
]
function log(a, b) {
  console.log(a);
  console.log(b);
}
log.apply(null, person);   // Jack Tim
log.apply(null, persons);  // Jack Tim
复制代码

call

call可以接受多个参数,第一个参数与apply相同,指定函数体内的this对象的指向,其余的参数传给被调用的函数,作为被调用函数的参数

function log(a, b) {
  console.log(a);
  console.log(b);
}
log.apply(null, "Jim", "Jack");   // Jim Jack
复制代码

call和apply的用途

apply和call除了可以更改this指向外,还可以用来实现类似于继承的效果

function Person(name) {
  this.name = name;
  this.sayName = function() {
    console.log(this.name);
  } 
}

function Student() {
  Person.apply(this, arguments);
}

let student = new Student("Jack");
student.sayName();
复制代码

借用其他对象的方法

// 数组没有取得最大值的方法,可以通过调用Math.max()函数来取得数组的最大值
let arr = [1, 2, 3, 4, 7, 9, 5, 4];
console.log(Math.max.apply(null, arr));  // 9
复制代码

bind

bind可接受多个参数,并返回一个新函数。第一个参数指定新函数体内的this对象的指向,其余的参数会在传递实参之前传给新函数,作为新函数的参数

bind只用一个用途,改变this的指向。并且一旦通过bind绑定了this的指向,无法再次通过bind,apply或call更改this的指向


function sayName() {
    console.log(this.name);
}

let person = {
    name: 'Jack'
};

let child = {
    name: 'Tim',
};

let say = sayName.bind(person);
say();  // Jack
let log = say.bind(child);
log();  // Jack, this并没有更改指向
say.apply(child);  // Jack this并没有更改指向
say.call(child);  // Jack this并没有更改指向
复制代码

实现apply函数


Function.prototype.mApply = function(context, arr) {
    // 如果context为null,则将context设置为window(浏览器环境)或global(nodejs环境)
    if (!context) {
        context = typeof window === 'undefined' ? global : window;  // 判断是在什么环境下运行,根据环境来给context设置默认值
    } 
    context.fn = this;  // 给context添加函数,使添加的函数指向当前被调用函数
    let res;  // 返回值
    if (!arr) {
        // 若参数为null,可以直接调用
        res = context.fn(arr);  // 调用添加函数
    } else if (typeof arr === 'object') {
        // 若参数为数组或类数组,则通过...运算符将其传入
        res = context.fn(...arr);  // 调用添加的函数
    }
    delete context.fn;  // 删除添加的函数
    return res;  // 传出返回值
}
let arr = [1, 2, 3, 4, 7, 9, 5, 4];
console.log(Math.max.mApply(null, arr));  // 9
复制代码

实现call函数

Function.prototype.mCall = function(context) {
    // 如果context为null,则将context设置为window(浏览器环境)或global(nodejs环境)
    if (!context) {
        context = typeof window === 'undefined' ? global : window;  // 判断是在什么环境下运行,根据环境来给context设置默认值
    } 
    context.fn = this;  // 给context添加函数,使添加的函数指向被调用的函数
    let args = [...arguments].slice(1);  // 取得除指定上下文参数之外的参数
    let res = context.fn(...args);  // 调用添加的函数,并存储返回值
    delete context.fn;  // 删除添加的函数
    return res;  // 传出返回值
}
let arr = [1, 2, 3, 4, 7, 9, 5, 4];
console.log(Math.max.mCall(null, ...arr));  // 9
复制代码

实现bind

Function.prototype.mBind = function(context) {
    // 若不是函数
    if (typeof this !== 'function') {
        throw new TypeError("is not a funtion")
    }
    let args = [...arguments].slice(1);
    function Fn() {}
    fun.prototype = this.prototype;
    let self = this;  
    let bound = function() {
        let res = [...args, ...arguments];  // 将bind传递的参数与调用时传递的参数拼接
        context = this instanceof Fn ? this : context || this;
        return self.apply(context, res);  // 返回值
    }
    bound.prototype = new Fn();
    return bound;
}
function sayName() {
  console.log(this.name);
}

let person = {
  name: 'Jack'
};

let child = {
  name: 'Tim',
};

let say = sayName.mBind(person);
say();  // Jack
let says = say.mBind(child);
says();  // Jack
复制代码

转载于:https://juejin.im/post/5cfc74c5e51d4510624f9797

猜你喜欢

转载自blog.csdn.net/weixin_34082177/article/details/91449606