Skip to content

函数的call和apply方法 #23

Open
@andyChenAn

Description

@andyChenAn

call方法

call方法调用一个函数,其具有一个指定的this值和指定的参数列表。

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

thisArg参数,表示的是在fun函数运行时指定的this值。这里需要注意的是,如果是在非严格模式下,指定null或者undefined为this值时,this会自动执行全局对象(window),同时值为原始值的this会执行该原始值的包装对象。

// 非严格模式下
var a = 1;
function foo () {
    console.log(this.a);
}
foo.call(null);
// 打印结果为:1
// 严格模式下
'use strict';
var a = 1;
function foo () {
    console.log(this.a);
}
foo.call(null);
// 报错

如果在非严格模式下,函数调用call方法中没有传入第一个参数,那么会自动执行全局对象(window),如果在严格模式下,那么this值会是undefined。

// 非严格模式下
var a = 1;
function foo () {
    console.log(this.a);
}
foo.call();
// 严格模式下
'use strict';
var a = 1;
function foo () {
    // 这里的this值是undefined
    console.log(this.a);
}
foo.call();
// 报错

用法

使用call方法调用父构造函数

当我们需要继承父构造函数时,我们可以通过在子构造函数里调用call方法来执行父构造函数,从而实现继承父构造函数的属性。

function Person (name , age) {
    this.name = name;
    this.age = age;
}

function Andy (name , age) {
    Person.call(this , name , age);
}

function Jack (name , age) {
    Person.call(this , name , age);
}

var andy = new Andy('andy' , 22);
var jack = new Jack('jack' , 23);

使用call方法来绑定this值

var name = 'jack';

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

var obj = {
    name : 'andy'
}
say.call(obj);

要注意,如果上面代码中不传入obj这个对象,那么在非严格模式下this值会执行全局对象(window),在严格模式下this值会是undefined。

模拟call方法

  • 1、绑定this。
  • 2、可以传入参数。
  • 3、当第一个参数不传入时,或者传入的是null或undefined,在非严格模式下会指向全局对象(window)。
  • 4、调用call方法可以有返回值。

因为所有的函数都有call方法,那么我们可以直接在函数的原型上添加该方法,首先我们要做的是调用call方法来绑定this值,当函数调用call方法时,会执行这个函数,并给这个函数指定一个this值。

Function.prototype.call2 = function (context) {
    context.fn = this;
    context.fn();
    // 当调用完之后,就可以删除对象上的这个属性
    delete context.fn;
};
Function.prototype.call2 = function (context) {
    context.fn = this;
    context.fn();
    delete context.fn;
};
var a = 1;
var obj = {
    a : 2
}
function foo () {
    console.log(this.a);
}
foo.call2(obj);
// 打印的结果为:2

但是当我们不传入第一个参数的时候或者传入的是null或者undefined,在非严格模式下this值会指向window

Function.prototype.call2 = function (context) {
    context = context || window;
    context.fn = this;
    context.fn();
    delete context.fn;
};

除了绑定this值,还可以传入其他参数到执行的函数里面,一开始我想到的是使用es6语法的rest参数方式,但是忽然觉得如果要模拟怎么能用es6语法呢?可以参考其他人的方式使用eval()来实现

Function.prototype.call2 = function (context) {
    context = context || window;
    var args = [];
    // 这里我们需要获取除第一个参数之外的其他参数
    for (var i = 1; i < arguments.length ; i++) {
        args.push('arguments[' + i + ']');
    };
    
    context.fn = this;
    eval('context.fn('+ args +')');
    delete context.fn;
};
Function.prototype.call2 = function (context) {
    context = context || window;
    var args = [];
    for (var i = 1; i < arguments.length ; i++) {
        args.push('arguments[' + i + ']');
    };
    
    context.fn = this;
    // args是一个数组,这里主要用到了字符串与数组相加的隐式转换,数组会调用toString()方法转为字符串,如果是使用数组的join()方法来将数组转为字符串,会报错。
    eval('context.fn('+ args +')');
    delete context.fn;
};
var a = 1;
var obj = {
    a : 2
}
function foo (name , age) {
    console.log(this.a);
    console.log(name);
    console.log(age);
}
foo.call2(obj , 'andy' , 22);

除了上面讲的之外,调用call方法还可以有返回值,而返回值就是执行函数的返回值

Function.prototype.call2 = function (context) {
    context = context || window;
    var args = [];
    for (var i = 1; i < arguments.length ; i++) {
        args.push('arguments[' + i + ']');
    };
    
    context.fn = this;
    // 将结果保存并返回即可
    var res = eval('context.fn('+ args +')');
    delete context.fn;
    return res;
};

apply和call方法的区别

其实apply和call方法的主要区别就是传入的参数不同,call方法传入的是一个参数列表,而apply方法传入的是一个保存参数列表的数组。

apply方法模拟

Function.prototype.apply2 = function (context , arr) {
    var context = context || window;
    var args = [];
    var res = '';
    context.fn = this;
    if (!arr) {
        res = context.fn();
    } else {
        for (var i = 0 ; i < arr.length ; i++) {
            args.push('arr['+i+']');
        };
        res = eval('context.fn('+ args +')');
    }
    delete context.fn;
    return res;
}

参考冴羽的文章,但是自己应该要按照前辈们的思路再去实现一遍,来巩固自己的基础知识。

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions