javascript-call/apply/bind

loading 2022年04月15日 139次浏览

1.call

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

1.1 语法

参数

function.call(thisArg, arg1, arg2, ...)

thisArg:可选的,在function函数运行时使用的this值。(在非严格模式下,当传入null或者undefined时会自动指向全局对象)
arg1,arg2...:指定的参数列表,可以用arguments代替

返回值

使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined。

描述

call() 允许为不同的对象分配和调用属于一个对象的函数/方法。
call() 提供新的 this 值给当前调用的函数/方法。你可以使用 call 来实现继承:写一个方法,然后让另外一个新的对象来继承它(而不是在新对象中再写一次这个方法)。

1.2 示例

1.2.1 通过call()方法实现继承

        function Product(name,price){
            this.name = name;
            this.price = price;
        }
        function Fruit(name,price){
            this.type = 'fruit';
            Product.call(this,name,price)
        }
        var apple = new Fruit('apple',10)
        console.log(apple);

1.2.2 利用call传递对象参数

        function greet(){
            var reply = this.animal+' typically sleep between ' + this.sleepDuration;
            console.log(reply);
        }
        var obj = {
            animal:'cats',
            sleepDuration:'0-24h'
        }
        greet.call(obj)

1.2.3 箭头函数中的call/apply

由于箭头函数中的this已经绑定为上一层作用域中的this,所以使用call/apply时传入的第一个参数this无效

例如下列代码,由于getAge方法被obj调用,所以getAge中的this指向obj,则b = 1990。

调用getAge时传入参数使得year = 2015,通过call的第二个参数将2015传入到fn的参数y中,使得y = 2015,而由于fn是箭头函数,其中的this相当于getAge中的this,因此最终返回2015 - 1990 = 25。

var obj = {
    birth: 1990,
    getAge: function (year) {
        var b = this.birth; // 1990
        var fn = (y) => y - this.birth; // this.birth仍是1990
        return fn.call({birth:2000}, year);
    }
};
obj.getAge(2015); // 25

1.2.4 严格模式和非严格模式下的call

call是可以传参的,但是在严格模式和非严格模式下,得到的值不一样。

非严格模式

        const obj = {
            name: 'yoimiya'
        }
        const fn = function (a, b) {
            console.log(a + b);
            console.log(this);
        }
        fn.call(100, 200);//this=100 a=200 b=undefined 输出NaN和Number{100}
        fn.call(obj, 100, 200);//this=obj a100 b200 输出300和obj

        fn.call();
        fn.call(null);
        fn.call(undefined);//这三条都相当于this=window 都输出NaN和Window

严格模式

        fn.call();//this=undefined 输出NaN undefined
        fn.call(null);//this=null 输出NaN null
        fn.call(undefined);//this=undefined 输出NaN undefined

1.3 手写call

        Function.prototype.myCall = function (context = window, ...args) {
            if (typeof this !== 'function') throw new TypeError('123123');
            context.fn = this;
            let res = context.fn(...args);
            delete context.fn;
            return res;
        }

测试代码

        var value = 1;
        var obj = {
            value: 2
        }
        function bar(name, age) {
            return {
                value: this.value,
                name: name,
                age: age
            }
        }

        console.log(bar.myCall(null, 'lebron', 37));
        //  相当于指向window {value: 1, name: 'lebron', age: 37}

        console.log(bar.myCall(obj, 'lebron', 37));
        // {value: 2, name: 'lebron', age: 37}

2.apply

2.1 手写apply()

别的完全一样,唯一区别是apply()传的是数组作为参数,中间部分处理有些区别:

        let myApply = function (context = window , arr) {
            if(typeof this !== 'function') throw new TypeError('...');
            context.fn = this;
            const res = context.fn(...arr);
            delete context.fn;
            return res;
        }

测试用例就是把传入的后几个参数变成数组,此处略。

3.bind

3.1 bind作用

bind和call/apply的区别是体现了预处理的思想,返回一个函数,需要调用时再调用:

        const obj = {
            name : 'yoimiya'
        }
        const fn = function(a , b){
            console.log(a + b);
        }
        fn.call(obj, 1, 2);//->改变this和执行fn函数同时完成了
        const afterBindFn = fn.bind(obj, 1, 2);
        //改变了fn中的this为obj,并且给fn传递了两个参数值1、2,
        //但是此时并没有把fn这个函数执行,而是返回一个函数
        
        //需要手动执行
        afterBindFn();

3.2 手写bind()

手写bind主要要注意三点:

  • bind会创建一个新函数
  • bind返回的函数可以通过new构造,但是提供的this值会被忽略
  • bind需要考虑实例化后对原型链的影响
        Function.prototype.myBind = function (context = window, ...args) {
            if (typeof this !== 'function') throw new TypeError('123');
            let fn = this;
            let resFn = function () {
                // 如果bind返回的函数被当作构造函数(被new)
                if (this instanceof resFn) {
                    return new fn(...args, ...arguments);
                }
                // 直接执行bind返回的函数
                return fn.apply(context, [...args, ...arguments])
            }
            // 设置原型
            resFn.prototype = this.prototype;
            return resFn;
        }

这里补充一个小知识点:new.target:

new.target 属性允许你检测函数或构造方法是否是通过new运算符被调用的。 在通过new运算符被初始化的函数或构造方法中,new.target返回一个指向构造方法或函数的引用。在普通的函数调用中,new.target 的值是undefined。

        function Foo() {
            if (!new.target) throw "Foo() must be called with new";
            console.log(new.target);
        }

        // Foo(); // throws "Foo() must be called with new"
        new Foo(); // Foo(){...}

测试myBind:

情况1 直接调用resFn

	const obj = {
            val: 1
        }
        // bar1函数用于直接调用的情况
        const bar1 = function (name, age) {
            return {
                val: this.val,
                name,
                age
            }
        }

        // 直接调用
        const afterBindFn1 = bar1.myBind(obj, 'yoimiya', 21);
        console.log(afterBindFn1()); // {val: 1, name: 'yoimiya', age: 21}

情况2 resFn作为构造函数

        // bar2函数用于作为构造函数的情况
        const bar2 = function (name, age) {
            this.val = 2;
            this.name = name;
            this.age = age;
        }

        // new出实例再调用
        const afterBindFn2 = bar2.myBind(obj, 'yoimiya', 21);
        const test = new afterBindFn2();
        console.log(test); // {val: 2, name: 'yoimiya', age: 21}