javaScript之call和apply

call

call() 方法在使用一个指定的this值和若干个指定的参数的前提下调用某个函数或方法。

1
2
3
4
5
6
7
8
9
10
11
var foo = {
value : "blue"
}
var bar = function(){
// 这里的this指的就是foo
console.log(this.value);
}
bar.call(foo) // 1
// 传递 null 或 undefined 在非严格模式下this指向window,严格模式下 this为对应的 null | undefined
bar.call(null)
bar.call(undefined)

注意到两点:

  1. call改变的this的指向,最开始this指向的是window,call之后this指向了foo
  2. bar函数执行了

模拟实现第一步

1
2
3
4
5
6
7
8
var foo = {
value: 'blue',
bar(){
console.log(this) //foo这个对象
console.log(this.value)
}
}
foo.bar()

这个时候this就指向了foo,但是这样做就给foo对象本身添加了一个属性,这样是不行的。
这个时候就可以想到,不要那就delete它不就可以了吗
这里列出了模拟的步骤:

  1. 将函数设为对象的属性 foo.fn = function(){}

  2. 执行函数 foo.fn()

  3. 删除该函数 delete foo.fn

    这里我们把这些步骤封装在Function的原型上的一个call2函数里面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Function.prototype.call2 = function(context){
    context.fn = this;
    context.fn();
    delete context.fn;
    }

    var foo = {
    value : 1
    }
    var bar = function(){
    console.log(this.value)
    }
    bar.call2(foo); // 1

模拟实现第二步

call函数不仅能改变this的指向,而且还能传递参数
1. 我们可以通过循环处理arguments来获取到传递进来的参数
2. 通过eval函数执行字符串代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Function.prototype.call2 = function(context){
context.fn = this;
var args = [];
for(var i = 1, len = arguments.length; i < len; i++){
args.push("arguments["+i+"]");
}
// 这里的args会自动调用 Array.toString()方法---隐式转换 --("a" + [1,2] => "a1,2")
// eval的参数可以是:JavaScript的表达式、语句或一系列语句的字符串
//返回字符串中代码的返回值。如果返回值为空,则返回 undefined。
eval('context.fn('+args+')')

context.fn;
delete context.fn;
}

var foo = {
value : 1
}
var bar = function(name,age,...arrs){
console.log(this.value) // 1
console.log(name,age,arrs) // a , c , [3,4,5]
}
bar.call2(foo,"a","c",3,4,5);

模拟第三步

目前代码已经完成了80%,还有一些需要注意的地方

  1. this 参数可以传null
  2. 函数是可以有返回值的!
    例如使用call时:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    var obj = {
    value: 1
    }

    function bar(name, age) {
    return {
    value: this.value,
    name: name,
    age: age
    }
    }

    console.log(bar.call(obj, 'kevin', 18));
    // Object {
    // value: 1,
    // name: 'kevin',
    // age: 18
    // }

处理思路:return出去 eval()的返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Function.prototype.call2 = function(context) {
// 如果传进来的是 null 或 undefinded, this指向window
var context = context || window;
context.fn = this;

var args = [];
for(var i = 1, len = arguments.length; i < len; i ++){

args.push("arguments["+i+"]");
}
var result = eval('context.fn('+args+")");
delete context.fn;

return result;
}

// 测试
var obj = {
value: 1
}

function bar(name, age) {
return {
value: this.value,
name: name,
age: age
}
}

console.log(bar.call2(obj, 'kevin', 18));

apply的模拟实现

apply和call类似,唯一的区别就是参数是数组形式传递的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Function.prototype.apply = function (context, arr) {
var context = Object(context) || window;
context.fn = this;

var result;
if (!arr) {
result = context.fn();
}
else {
var args = [];
// 这里只是把argument改成了参数数组 arr
for (var i = 0, len = arr.length; i < len; i++) {
args.push('arr[' + i + ']');
}
result = eval('context.fn(' + args + ')')
}

delete context.fn
return result;
}