8000 JavaScript 中 apply 、call 的详解 · Issue #7 · lin-xin/blog · GitHub
[go: up one dir, main page]

Skip to content

JavaScript 中 apply 、call 的详解 #7

Open

Description

@lin-xin

apply 和 call 的区别

ECMAScript 规范给所有函数都定义了 call 与 apply 两个方法,它们的应用非常广泛,它们的作用也是一模一样,只是传参的形式有区别而已。

apply( )

apply 方法传入两个参数:一个是作为函数上下文的对象,另外一个是作为函数参数所组成的数组。

var obj = {
    name : 'linxin'
}

function func(firstName, lastName){
    console.log(firstName + ' ' + this.name + ' ' + lastName);
}

func.apply(obj, ['A', 'B']);    // A linxin B

可以看到,obj 是作为函数上下文的对象,函数 func 中 this 指向了 obj 这个对象。参数 A 和 B 是放在数组中传入 func 函数,分别对应 func 参数的列表元素。

call( )

call 方法第一个参数也是作为函数上下文的对象,但是后面传入的是一个参数列表,而不是单个数组。

var obj = {
    name: 'linxin'
}

function func(firstName, lastName) {
    console.log(firstName + ' ' + this.name + ' ' + lastName);
}

func.call(obj, 'C', 'D');       // C linxin D

对比 apply 我们可以看到区别,C 和 D 是作为单独的参数传给 func 函数,而不是放到数组中。

对于什么时候该用什么方法,其实不用纠结。如果你的参数本来就存在一个数组中,那自然就用 apply,如果参数比较散乱相互之间没什么关联,就用 call。

apply 和 call 的用法

1.改变 this 指向

var obj = {
    name: 'linxin'
}

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

func.call(obj);       // linxin

我们知道,call 方法的第一个参数是作为函数上下文的对象,这里把 obj 作为参数传给了 func,此时函数里的 this 便指向了 obj 对象。此处 func 函数里其实相当于

function func() {
    console.log(obj.name);
}

2.借用别的对象的方法

先看例子

var Person1  = function () {
    this.name = 'linxin';
}
var Person2 = function () {
    this.getname = function () {
        console.log(this.name);
    }
    Person1.call(this);
}
var person = new Person2();
person.getname();       // linxin

从上面我们看到,Person2 实例化出来的对象 person 通过 getname 方法拿到了 Person1 中的 name。因为在 Person2 中,Person1.call(this) 的作用就是使用 Person1 对象代替 this 对象,那么 Person2 就有了 Person1 中的所有属性和方法了,相当于 Person2 继承了 Person1 的属性和方法。

3.调用函数

apply、call 方法都会使函数立即执行,因此它们也可以用来调用函数。

function func() {
    console.log('linxin');
}
func.call();            // linxin

call 和 bind 的区别

在 EcmaScript5 中扩展了叫 bind 的方法,在低版本的 IE 中不兼容。它和 call 很相似,接受的参数有两部分,第一个参数是是作为函数上下文的对象,第二部分参数是个列表,可以接受多个参数。
它们之间的区别有以下两点。

1.bind 发返回值是函数

var obj = {
    name: 'linxin'
}

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

var func1 = func.bind(obj);
func1();                        // linxin

bind 方法不会立即执行,而是返回一个改变了上下文 this 后的函数。而原函数 func 中的 this 并没有被改变,依旧指向全局对象 window。

2.参数的使用

function func(a, b, c) {
    console.log(a, b, c);
}
var func1 = func.bind(null,'linxin');

func('A', 'B', 'C');            // A B C
func1('A', 'B', 'C');           // linxin A B
func1('B', 'C');                // linxin B C
func.call(null, 'linxin');      // linxin undefined undefined

call 是把第二个及以后的参数作为 func 方法的实参传进去,而 func1 方法的实参实则是在 bind 中参数的基础上再往后排。

在低版本浏览器没有 bind 方法,我们也可以自己实现一个。

if (!Function.prototype.bind) {
        Function.prototype.bind = function () {
            var self = this,                        // 保存原函数
                context = [].shift.call(arguments), // 保存需要绑定的this上下文
                args = [].slice.call(arguments);    // 剩余的参数转为数组
            return function () {                    // 返回一个新函数
                self.apply(context,[].concat.call(args, [].slice.call(arguments)));
            }
        }
    }

Activity

  1. DummyLIU commented on Nov 20, 2017

    @DummyLIU

    good

  2. Kingwantfly commented on Jan 8, 2018

    @Kingwantfly

    完美,讲的很好

  3. xsdream commented on Jan 18, 2018

    @xsdream

    nice!

  4. Fang0408 commented on Feb 25, 2018

    @Fang0408

    ES6里面的call还能用解构来传参
    const f = function (a, b, c) { console.log(a, b, c) } const arr = [1, 2, 3] f.call(null, ...arr)

  5. laclys commented on Mar 15, 2018

    @laclys

    @Fang0408 直接f(...arr)不就得了

  6. 124design commented on Apr 28, 2018

    @124design

    nice!

  7. fonice commented on Apr 30, 2018

    @fonice

    2.借用别的对象的方法

    Person1.call(this) 的作用就是使用 Person1 对象代替 this 对象

    我的理解是 Person1 的 this 变成 Person2的this,并执行一遍Person1函数

    Person2就多了一个name属性

  8. ettingshausen commented on May 19, 2018

    @ettingshausen

    补充一个使用 apply 的例子。
    比如求一个数组的最大值,使用 Math.max 函数,但是这个函数入参不支持数组,只能是将多个参数逐个传入,用逗号分隔。

    let max = Math.max(1, 4, 8, 9, 0)

    有了 apply,就可以这么调用:

    let arr = [1, 4, 8, 9, 0];
    let max = Math.max.apply(null, arr);
  9. HiIcy commented on Jun 9, 2018

    @HiIcy

    nice!

  10. Rudy24 commented on Jun 26, 2018

    @Rudy24

    @lin-xin 楼主这里有点问题吧 Person1.call(this) 的作用就是使用 Person1 对象代替 this 对象,
    Person1.call(this), 按照理解应该是 把Person1函数中的this指向了Person2中的this

  11. ycshill commented on Aug 10, 2018

    @ycshill

    var Person1 = function () {
    this.name = 'linxin';
    }
    var Person2 = function () {
    this.getname = function () {
    console.log(this.name);
    }
    Person1.call(this);
    }
    var person = new Person2();
    person.getname();
    这个例子不是太理解,各位楼主有没有详细解答的

  12. web-display commented on Aug 28, 2018

    @web-display

    在一定程度上Person1.call(this) 的作用就是使用 Person1 对象代替 this 对象是成立的,那样子的话Person2就不能拥有自己的属性了,我的理解是Person1.call(this)执行完毕后讲Person1的属性复制到了Person2中

  13. ZhangzyGY commented on Sep 28, 2018

    @ZhangzyGY

    nice!

  14. 16slowly commented on Oct 13, 2018

    @16slowly
    if (!Function.prototype.bind) {
            Function.prototype.bind = function () {
                var self = this,                        // 保存原函数
                    context = [].shift.call(arguments), // 保存需要绑定的this上下文
                    args = [].slice.call(arguments);    // 剩余的参数转为数组
                return function () {                    // 返回一个新函数
                    self.apply(context,[].concat.call(args, [].slice.call(arguments)));
                }
            }
        }

    以上返回的函数体中的 self.apply(context,[].concat.call(args, [].slice.call(arguments))) 里第二个参数为啥是 [].concat.call(args, [].slice.call(arguments)), 它不就应该是 args 吗 ?
    @lin-xin

  15. MontageD commented on Nov 8, 2018

    @MontageD

    好文,赞

  16. montilloo commented on Dec 1, 2018

    @montilloo

    Perfect!

  17. ChrisSong1994 commented on Dec 10, 2018

    @ChrisSong1994

    @lin-xin 楼主这里有点问题吧 Person1.call(this) 的作用就是使用 Person1 对象代替 this 对象,
    Person1.call(this), 按照理解应该是 把Person1函数中的this指向了Person2中的this

    返回的是一个函数,可以继续添加参数

  18. devindo commented on Jan 25, 2019

    @devindo

    补充一个使用 apply 的例子。
    比如求一个数组的最大值,使用 Math.max 函数,但是这个函数入参不支持数组,只能是将多个参数逐个传入,用逗号分隔。

    let max = Math.max(1, 4, 8, 9, 0)

    有了 apply,就可以这么调用:

    let arr = [1, 4, 8, 9, 0];
    let max = Math.max.apply(null, arr);

    Math.max(...[1,2,3])

  19. zqzq3 commented on Apr 17, 2019

    @zqzq3

    完全看不懂

  20. leiyangs commented on Jun 14, 2019

    @leiyangs

    豁然开朗

  21. ximelly commented on Jun 20, 2019

    @ximelly
    if (!Function.prototype.bind) {
            Function.prototype.bind = function () {
                var self = this,                        // 保存原函数
                    context = [].shift.call(arguments), // 保存需要绑定的this上下文
                    args = [].slice.call(arguments);    // 剩余的参数转为数组
                return function () {                    // 返回一个新函数
                    self.apply(context,[].concat.call(args, [].slice.call(arguments)));
                }
            }
        }

    以上返回的函数体中的 self.apply(context,[].concat.call(args, [].slice.call(arguments))) 里第二个参数为啥是 [].concat.call(args, [].slice.call(arguments)), 它不就应该是 args 吗 ?
    @lin-xin

    同问

  22. izayoi-hibiki commented on Jun 20, 2019

    @izayoi-hibiki
    if (!Function.prototype.bind) {
            Function.prototype.bind = function () {
                var self = this,                        // 保存原函数
                    context = [].shift.call(arguments), // 保存需要绑定的this上下文
                    args = [].slice.call(arguments);    // 剩余的参数转为数组
                return function () {                    // 返回一个新函数
                    self.apply(context,[].concat.call(args, [].slice.call(arguments)));
                }
            }
        }

    以上返回的函数体中的 self.apply(context,[].concat.call(args, [].slice.call(arguments))) 里第二个参数为啥是 [].concat.call(args, [].slice.call(arguments)), 它不就应该是 args 吗 ?
    @lin-xin

    同问

    Function.prototype.binding = function () {
        let self = this;
        let context = [].shift.call(arguments);
        let args = [].slice.call(arguments);
        console.log('Function binding args', args);
    
        return function () {
            self.apply(context, [].concat.call(args, [].slice.call(arguments)))
        }
    };
    
    let obj = {
        a: 'a'
    };
    
    function f(b, c, d) {
        console.log(this.a, b, c, d)
    }
    
    f.binding(obj, 'b', 'c', 'd')('e');
    f.binding(obj, 'b', 'c')('d', 'e');
    f.binding(obj, 'b')('c', 'd', 'e');
    f.binding(obj)('b', 'c', 'd', 'e');

    输出:

    Function binding args [ 'b', 'c', 'd' ]
    a b c d
    Function binding args [ 'b', 'c' ]
    a b c d
    Function binding args [ 'b' ]
    a b c d
    Function binding args []
    a b c d
    
    Process finished with exit code 0
    

    从输出可以看出 args 是 f.binding 里除了第一个参数剩下所有的参数,而 [].slice.call(arguments) 是 f.binding 后第二个括号里的参数, args 的 this 是 Function.prototype.binding,[].slice.call(arguments) 的 this 是 f.binding(obj)()。

  23. ximelly commented on Jun 20, 2019

    @ximelly
    if (!Function.prototype.bind) {
            Function.prototype.bind = function () {
                var self = this,                        // 保存原函数
                    context = [].shift.call(arguments), // 保存需要绑定的this上下文
                    args = [].slice.call(arguments);    // 剩余的参数转为数组
                return function () {                    // 返回一个新函数
                    self.apply(context,[].concat.call(args, [].slice.call(arguments)));
                }
            }
        }

    以上返回的函数体中的 self.apply(context,[].concat.call(args, [].slice.call(arguments))) 里第二个参数为啥是 [].concat.call(args, [].slice.call(arguments)), 它不就应该是 args 吗 ?
    @lin-xin

    同问

    Function.prototype.binding = function () {
        let self = this;
        let context = [].shift.call(arguments);
        let args = [].slice.call(arguments);
        console.log('Function binding args', args);
    
        return function () {
            self.apply(context, [].concat.call(args, [].slice.call(arguments)))
        }
    };
    
    let obj = {
        a: 'a'
    };
    
    function f(b, c, d) {
        console.log(this.a, b, c, d)
    }
    
    f.binding(obj, 'b', 'c', 'd')('e');
    f.binding(obj, 'b', 'c')('d', 'e');
    f.binding(obj, 'b')('c', 'd', 'e');
    f.binding(obj)('b', 'c', 'd', 'e');

    输出:

    Function binding args [ 'b', 'c', 'd' ]
    a b c d
    Function binding args [ 'b', 'c' ]
    a b c d
    Function binding args [ 'b' ]
    a b c d
    Function binding args []
    a b c d
    
    Process finished with exit code 0
    

    从输出可以看出 args 是 f.binding 里除了第一个参数剩下所有的参数,而 [].slice.call(arguments) 是 f.binding 后第二个括号里的参数, args 的 this 是 Function.prototype.binding,[].slice.call(arguments) 的 this 是 f.binding(obj)()。

    明白了 多谢!

  24. httol commented on Aug 8, 2019

    @httol
    if (!Function.prototype.bind) {
            Function.prototype.bind = function () {
                var self = this,                        // 保存原函数
                    context = [].shift.call(arguments), // 保存需要绑定的this上下文
                    args = [].slice.call(arguments);    // 剩余的参数转为数组
                return function () {                    // 返回一个新函数
                    self.apply(context,[].concat.call(args, [].slice.call(arguments)));
                }
            }
        }

    以上返回的函数体中的 self.apply(context,[].concat.call(args, [].slice.call(arguments))) 里第二个参数为啥是 [].concat.call(args, [].slice.call(arguments)), 它不就应该是 args 吗 ?
    @lin-xin

    同问

    Function.prototype.binding = function () {
        let self = this;
        let context = [].shift.call(arguments);
        let args = [].slice.call(arguments);
        console.log('Function binding args', args);
    
        return function () {
            self.apply(context, [].concat.call(args, [].slice.call(arguments)))
        }
    };
    
    let obj = {
        a: 'a'
    };
    
    function f(b, c, d) {
        console.log(this.a, b, c, d)
    }
    
    f.binding(obj, 'b', 'c', 'd')('e');
    f.binding(obj, 'b', 'c')('d', 'e');
    f.binding(obj, 'b')('c', 'd', 'e');
    f.binding(obj)('b', 'c', 'd', 'e');

    输出:

    Function binding args [ 'b', 'c', 'd' ]
    a b c d
    Function binding args [ 'b', 'c' ]
    a b c d
    Function binding args [ 'b' ]
    a b c d
    Function binding args []
    a b c d
    
    Process finished with exit code 0
    

    从输出可以看出 args 是 f.binding 里除了第一个参数剩下所有的参数,而 [].slice.call(arguments) 是 f.binding 后第二个括号里的参数, args 的 this 是 Function.prototype.binding,[].slice.call(arguments) 的 this 是 f.binding(obj)()。

    明白了 多谢!

    这个是标准的闭包写法,函数嵌套函数的时候,被嵌套的函数的作用域链是单独的,而且每次执行外部函数时,这个作用域链总是会生成新的链,像this和arguments都不会和外部函数共享,也是单独的,比如嵌套函数想拿到外部的arguments和this需要在嵌套函数外面用变量做存储才可以,类似let self = this,let args = arguments。例子中嵌套函数的arguments就是自己函数的,而不是外部函数的。

    Function.prototype.binding = function () {
        let self = this;
        let context = [].shift.call(arguments);
        let args = [].slice.call(arguments);
        console.log('Function binding args', args);
    
        return function () {console.log('innerFunc',arguments);
            self.apply(context, [].concat.call(args, [].slice.call(arguments)))
        }
    };
    
    let obj = {
        a: 'a'
    };
    
    function f(b, c, d) {
        console.log(this.a, b, c, d)
    }
    
    f.binding(obj, 'b', 'c', 'd')('e');
    f.binding(obj, 'b', 'c')('d', 'e');
    f.binding(obj, 'b')('c', 'd', 'e');
    f.binding(obj)('b', 'c', 'd', 'e');
    Function binding args (3) ["b", "c", "d"]
    innerFunc Arguments ["e", callee: ƒ, Symbol(Symbol.iterator): ƒ]
    Function binding args (2) ["b", "c"]
    innerFunc Arguments(2) ["d", "e", callee: ƒ, Symbol(Symbol.iterator): ƒ]
    Function binding args ["b"]
    innerFunc Arguments(3) ["c", "d", "e", callee: ƒ, Symbol(Symbol.iterator): ƒ]
    VM1135:5 Function binding args []
    innerFunc Arguments(4) ["b", "c", "d", "e", callee: ƒ, Symbol(Symbol.iterator): ƒ]
  25. 1793523411 commented on Jul 6, 2020

    @1793523411

    懂了,真棒!!!

  26. haohao3308 commented on Jun 1, 2021

    @haohao3308
    if (!Function.prototype.bind) {
            Function.prototype.bind = function () {
                var self = this,                        // 保存原函数
                    context = [].shift.call(arguments), // 保存需要绑定的this上下文
                    args = [].slice.call(arguments);    // 剩余的参数转为数组
                return function () {                    // 返回一个新函数
                    self.apply(context,[].concat.call(args, [].slice.call(arguments)));
                }
            }
        }

    以上返回的函数体中的 self.apply(context,[].concat.call(args, [].slice.call(arguments))) 里第二个参数为啥是 [].concat.call(args, [].slice.call(arguments)), 它不就应该是 args 吗 ?
    @lin-xin

    同问

    Function.prototype.binding = function () {
        let self = this;
        let context = [].shift.call(arguments);
        let args = [].slice.call(arguments);
        console.log('Function binding args', args);
    
        return function () {
            self.apply(context, [].concat.call(args, [].slice.call(arguments)))
        }
    };
    
    let obj = {
        a: 'a'
    };
    
    function f(b, c, d) {
        console.log(this.a, b, c, d)
    }
    
    f.binding(obj, 'b', 'c', 'd')('e');
    f.binding(obj, 'b', 'c')('d', 'e');
    f.binding(obj, 'b')('c', 'd', 'e');
    f.binding(obj)('b', 'c', 'd', 'e');

    输出:

    Function binding args [ 'b', 'c', 'd' ]
    a b c d
    Function binding args [ 'b', 'c' ]
    a b c d
    Function binding args [ 'b' ]
    a b c d
    Function binding args []
    a b c d
    
    Process finished with exit code 0
    

    从输出可以看出 args 是 f.binding 里除了第一个参数剩下所有的参数,而 [].slice.call(arguments) 是 f.binding 后第二个括号里的参数, args 的 this 是 Function.prototype.binding,[].slice.call(arguments) 的 this 是 f.binding(obj)()。

    既然 args已经 转化为数组了那最后的 :
    `self.apply(context, [].concat.call(args, [].slice.call(arguments)))`
    写成:
    `self.apply(context, args.concat([].slice.call(arguments)))`
    不是更方便
    为什么还要调用一次call()呢
    
  27. SolitaryBirdDeadWood commented on Jun 29, 2022

    @SolitaryBirdDeadWood
        let arr1 = [1, 2, 7, 5, 6];
        let arr2 = [4, 7, 5, 4, 6, 8, ]
        Array.prototype.push.apply(arr1, arr2)
    

    还有添加

  28. Repository owner deleted a comment from lydfree on Jun 29, 2022
  29. SolitaryBirdDeadWood commented on Jun 29, 2022

    @SolitaryBirdDeadWood

    在Person2中的 call 将Person1的指向改为 Person2中的实例 Person1 的 this.name 相当于写在了 Person2中

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      0