javascript之apply,call,bind与this绑定

this绑定

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

一般this的绑定有四种状况java

1.隐式绑定node

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

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

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

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

2显示绑定c#

即经过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指向这个对象app

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

4.默认绑定函数

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

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
复制代码
相关文章
相关标签/搜索