>

深入之call和apply的模拟实现,深入之类数组对象

- 编辑:至尊游戏网站 -

深入之call和apply的模拟实现,深入之类数组对象

JavaScript 深刻之类数组对象与 arguments

2017/05/27 · JavaScript · arguments

初藳出处: 冴羽   

JavaScript 深远之call和apply的照葫芦画瓢达成

2017/05/25 · JavaScript · apply, call

原稿出处: 冴羽   

类数组对象

所谓的类数组对象:

具备四个 length 属性和若干索引属性的靶子

举个例证:

var array = ['name', 'age', 'sex']; var arrayLike = { 0: 'name', 1: 'age', 2: 'sex', length: 3 }

1
2
3
4
5
6
7
8
var array = ['name', 'age', 'sex'];
 
var arrayLike = {
    0: 'name',
    1: 'age',
    2: 'sex',
    length: 3
}

即使如此,为何叫做类数组对象呢?

那让大家从读写、获取长度、遍历五个地方看看那四个目标。

call

一句话介绍 call:

call() 方法在应用贰个钦点的 this 值和若干个钦点的参数值的前提下调用有些函数或艺术。

举个例证:

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

1
2
3
4
5
6
7
8
9
var foo = {
    value: 1
};
 
function bar() {
    console.log(this.value);
}
 
bar.call(foo); // 1

专心两点:

  1. call 改变了 this 的指向,指向到 foo
  2. bar 函数施行了

读写

console.log(array[0]); // name console.log(arrayLike[0]); // name array[0] = 'new name'; arrayLike[0] = 'new name';

1
2
3
4
5
console.log(array[0]); // name
console.log(arrayLike[0]); // name
 
array[0] = 'new name';
arrayLike[0] = 'new name';

模仿完结率先步

那么大家该怎么模拟达成那三个功能啊?

试想当调用 call 的时候,把 foo 对象改换成如下:

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

1
2
3
4
5
6
7
8
var foo = {
    value: 1,
    bar: function() {
        console.log(this.value)
    }
};
 
foo.bar(); // 1

以此时候 this 就针对了 foo,是或不是很轻巧吗?

然则这样却给 foo 对象自己加多了贰脾品质,那可不行呀!

而是也不用担忧,大家用 delete 再删除它不就好了~

于是我们模拟的步调能够分成:

  1. 将函数设为对象的品质
  2. 施行该函数
  3. 去除该函数

以上个例证为例,正是:

// 第一步 foo.fn = bar // 第二步 foo.fn() // 第三步 delete foo.fn

1
2
3
4
5
6
// 第一步
foo.fn = bar
// 第二步
foo.fn()
// 第三步
delete foo.fn

fn 是目的的属性名,反正最后也要刨除它,所以起成什么都不留意。

依照这一个思路,大家能够品味着去写第一版的 call2 函数:

// 第一版 Function.prototype.call2 = function(context) { // 首先要获取调用call的函数,用this能够赢得 context.fn = this; context.fn(); delete context.fn; } // 测验一下 var foo = { value: 1 }; function bar() { console.log(this.value); } bar.call2(foo); // 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 第一版
Function.prototype.call2 = function(context) {
    // 首先要获取调用call的函数,用this可以获取
    context.fn = this;
    context.fn();
    delete context.fn;
}
 
// 测试一下
var foo = {
    value: 1
};
 
function bar() {
    console.log(this.value);
}
 
bar.call2(foo); // 1

正好能够打字与印刷 1 哎!是或不是很欢跃!(~ ̄▽ ̄)~

长度

console.log(array.length); // 3 console.log(arrayLike.length); // 3

1
2
console.log(array.length); // 3
console.log(arrayLike.length); // 3

宪章完成第二步

最一齐先也讲了,call 函数还能够给定参数推行函数。比如:

var foo = { value: 1 }; function bar(name, age) { console.log(name) console.log(age) console.log(this.value); } bar.call(foo, 'kevin', 18); // kevin // 18 // 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var foo = {
    value: 1
};
 
function bar(name, age) {
    console.log(name)
    console.log(age)
    console.log(this.value);
}
 
bar.call(foo, 'kevin', 18);
// kevin
// 18
// 1

当心:传入的参数并不分明,那可如何做?

不急,我们得以从 Arguments 对象中取值,抽取第贰个到结尾二个参数,然后放到一个数组里。

譬喻说那样:

// 以上个例子为例,此时的arguments为: // arguments = { // 0: foo, // 1: 'kevin', // 2: 18, // length: 3 // } // 因为arguments是类数组对象,所以能够用for循环 var args = []; for(var i = 1, len = arguments.length; i len; i++) { args.push('arguments[' + i + ']'); } // 执行后 args为 [foo, 'kevin', 18]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 以上个例子为例,此时的arguments为:
// arguments = {
//      0: foo,
//      1: 'kevin',
//      2: 18,
//      length: 3
// }
// 因为arguments是类数组对象,所以可以用for循环
var args = [];
for(var i = 1, len = arguments.length; i  len; i++) {
    args.push('arguments[' + i + ']');
}
 
// 执行后 args为 [foo, 'kevin', 18]

不定长的参数难点消除了,我们跟着要把那一个参数数组放到要奉行的函数的参数里面去。

// 将数组里的要素作为五个参数放进函数的形参里 context.fn(args.join(',')) // (O_o)?? // 这一个艺术自然是老大的啦!!!

1
2
3
4
// 将数组里的元素作为多个参数放进函数的形参里
context.fn(args.join(','))
// (O_o)??
// 这个方法肯定是不行的啦!!!

可能有人想到用 ES6 的方法,然则 call 是 ES3 的法子,大家为了仿照效法达成多个ES3 的办法,要用到ES6的点子,好像……,嗯,也足以啊。然则大家这一次用 eval 方法拼成三个函数,类似于如此:

eval('context.fn(' + args +')')

1
eval('context.fn(' + args +')')

此处 args 会自动调用 Array.toString() 那么些点子。

所以大家的第二版制服了多个大难题,代码如下:

// 第二版 Function.prototype.call2 = function(context) { context.fn = this; var args = []; for(var i = 1, len = arguments.length; i len; i++) { args.push('arguments[' + i + ']'); } eval('context.fn(' + args +')'); delete context.fn; } // 测验一下 var foo = { value: 1 }; function bar(name, age) { console.log(name) console.log(age) console.log(this.value); } bar.call2(foo, 'kevin', 18); // kevin // 18 // 1

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
// 第二版
Function.prototype.call2 = function(context) {
    context.fn = this;
    var args = [];
    for(var i = 1, len = arguments.length; i  len; i++) {
        args.push('arguments[' + i + ']');
    }
    eval('context.fn(' + args +')');
    delete context.fn;
}
 
// 测试一下
var foo = {
    value: 1
};
 
function bar(name, age) {
    console.log(name)
    console.log(age)
    console.log(this.value);
}
 
bar.call2(foo, 'kevin', 18);
// kevin
// 18
// 1

(๑•̀ㅂ•́)و✧

遍历

for(var i = 0, len = array.length; i len; i++) { …… } for(var i = 0, len = arrayLike.length; i len; i++) { …… }

1
2
3
4
5
6
for(var i = 0, len = array.length; i  len; i++) {
   ……
}
for(var i = 0, len = arrayLike.length; i  len; i++) {
    ……
}

是否很像?

那类数组对象足以运用数组的点子吗?譬喻:

arrayLike.push('4');

1
arrayLike.push('4');

只是上述代码会报错: arrayLike.push is not a function

于是毕竟依然类数组呐……

效仿达成第三步

宪章代码已经到位 十分之七,还大概有多个小点要在意:

1.this 参数能够传 null,当为 null 的时候,视为指向 window

举个例证:

var value = 1; function bar() { console.log(this.value); } bar.call(null); // 1

1
2
3
4
5
6
7
var value = 1;
 
function bar() {
    console.log(this.value);
}
 
bar.call(null); // 1

虽说这些例子本人不选取 call,结果照旧一样。

2.函数是能够有再次来到值的!

例如:

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 // }

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
// }

然而都很好化解,让我们向来看第三版也正是终极一版的代码:

// 第三版 Function.prototype.call2 = function (context) { 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 value = 2; var obj = { value: 1 } function bar(name, age) { console.log(this.value); return { value: this.value, name: name, age: age } } bar.call(null); // 2 console.log(bar.call2(obj, 'kevin', 18)); // 1 // Object { // value: 1, // name: 'kevin', // age: 18 // }

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
31
32
33
34
35
36
37
38
39
40
41
// 第三版
Function.prototype.call2 = function (context) {
    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 value = 2;
 
var obj = {
    value: 1
}
 
function bar(name, age) {
    console.log(this.value);
    return {
        value: this.value,
        name: name,
        age: age
    }
}
 
bar.call(null); // 2
 
console.log(bar.call2(obj, 'kevin', 18));
// 1
// Object {
//    value: 1,
//    name: 'kevin',
//    age: 18
// }

到此,大家做到了 call 的模拟完毕,给和煦一个赞 b( ̄▽ ̄)d

调用数组方法

假使类数组正是随机的想用数组的章程怎么做呢?

既然如此无法直接调用,我们能够用 Function.call 直接调用:

var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3 } Array.prototype.join.call(arrayLike, '&'); // name&age&sex Array.prototype.slice.call(arrayLike, 0); // ["name", "age", "sex"] // slice能够变成类数组转数组 Array.prototype.map.call(arrayLike, function(item){ return item.toUpperCase(); }); // ["NAME", "AGE", "SEX"]

1
2
3
4
5
6
7
8
9
10
11
var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3 }
 
Array.prototype.join.call(arrayLike, '&'); // name&age&sex
 
Array.prototype.slice.call(arrayLike, 0); // ["name", "age", "sex"]
// slice可以做到类数组转数组
 
Array.prototype.map.call(arrayLike, function(item){
    return item.toUpperCase();
});
// ["NAME", "AGE", "SEX"]

apply的效仿达成

apply 的落到实处跟 call 类似,在此边直接给代码,代码来自于乐乎 @郑航的兑现:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Function.prototype.apply = function (context, arr) {
    var context = Object(context) || window;
    context.fn = this;
 
    var result;
    if (!arr) {
        result = context.fn();
    }
    else {
        var args = [];
        for (var i = 0, len = arr.length; i  len; i++) {
            args.push('arr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')')
    }
 
    delete context.fn
    return result;
}

类数组转对象

在地点的例证中早就涉嫌了一体系数组转数组的点子,再补偿八个:

var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3 } // 1. slice Array.prototype.slice.call(arrayLike); // ["name", "age", "sex"] // 2. splice Array.prototype.splice.call(arrayLike, 0); // ["name", "age", "sex"] // 3. ES6 Array.from Array.from(arrayLike); // ["name", "age", "sex"] // 4. apply Array.prototype.concat.apply([], arrayLike)

1
2
3
4
5
6
7
8
9
var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3 }
// 1. slice
Array.prototype.slice.call(arrayLike); // ["name", "age", "sex"]
// 2. splice
Array.prototype.splice.call(arrayLike, 0); // ["name", "age", "sex"]
// 3. ES6 Array.from
Array.from(arrayLike); // ["name", "age", "sex"]
// 4. apply
Array.prototype.concat.apply([], arrayLike)

那么为啥会讲到类数组对象呢?以致类数组有何应用吗?

要谈起类数组对象,Arguments 对象就是二个类数组对象。在客商端 JavaScript 中,一些 DOM 方法(document.getElementsByTagName()等)也回到类数组对象。

首要参谋

乐乎难题 不能够运用call、apply、bind,怎样用 js 完毕 call 只怕 apply 的机能?

Arguments对象

接下去重视讲讲 Arguments 对象。

Arguments 对象只定义在函数体中,包罗了函数的参数和此外属性。在函数体中,arguments 指代该函数的 Arguments 对象。

举个例证:

function foo(name, age, sex) { console.log(arguments); } foo('name', 'age', 'sex')

1
2
3
4
5
function foo(name, age, sex) {
    console.log(arguments);
}
 
foo('name', 'age', 'sex')

打字与印刷结果如下:

图片 1

我们能够见见除了类数组的索引属性和length属性之外,还应该有多个callee属性,接下去大家二个叁个介绍。

深入种类

JavaScript深切连串目录地址:。

JavaScript深刻体系揣度写十五篇左右,目的在于帮大家捋顺JavaScript底层知识,珍视传授如原型、功效域、实践上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、承继等困难概念。

即便有不当只怕不严慎的地方,请必须给予指正,十二分谢谢。要是喜欢照旧具备启示,迎接star,对小编也是一种驱策。

本系列:

  1. JavaScirpt 深切之从原型到原型链
  2. JavaScript 深切之词法效用域和动态作用域
  3. JavaScript 深远之实行上下文栈
  4. JavaScript 深远之变量对象
  5. JavaScript 深切之作用域链
  6. JavaScript 深切之从 ECMAScript 标准解读 this
  7. JavaScript 深切之施行上下文
  8. JavaScript 深刻之闭包
  9. JavaScript 深刻之参数按值传递

    1 赞 收藏 评论

图片 2

length属性

Arguments对象的length属性,表示实参的尺寸,举个例证:

function foo(b, c, d){ console.log("实参的长度为:" + arguments.length) } console.log("形参的尺寸为:" + foo.length) foo(1) // 形参的长短为:3 // 实参的长短为:1

1
2
3
4
5
6
7
8
9
10
function foo(b, c, d){
    console.log("实参的长度为:" + arguments.length)
}
 
console.log("形参的长度为:" + foo.length)
 
foo(1)
 
// 形参的长度为:3
// 实参的长度为:1

callee属性

Arguments 对象的 callee 属性,通过它可以调用函数自己。

讲个闭包卓越面试题使用 callee 的缓和格局:

var data = []; for (var i = 0; i 3; i++) { (data[i] = function () { console.log(arguments.callee.i) }).i = i; } data[0](); data[1](); data[2](); // 0 // 1 // 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var data = [];
 
for (var i = 0; i  3; i++) {
    (data[i] = function () {
       console.log(arguments.callee.i)
    }).i = i;
}
 
data[0]();
data[1]();
data[2]();
 
// 0
// 1
// 2

接下去讲讲 arguments 对象的多少个注意要点:

arguments 和相应参数的绑定

function foo(name, age, sex, hobbit) { console.log(name, arguments[0]); // name name // 改换形参 name = 'new name'; console.log(name, arguments[0]); // new name new name // 改变arguments arguments[1] = 'new age'; console.log(age, arguments[1]); // new age new age // 测验未传入的是不是会绑定 console.log(sex); // undefined sex = 'new sex'; console.log(sex, arguments[2]); // new sex undefined arguments[3] = 'new hobbit'; console.log(hobbit, arguments[3]); // undefined new hobbit } foo('name', 'age')

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
function foo(name, age, sex, hobbit) {
 
    console.log(name, arguments[0]); // name name
 
    // 改变形参
    name = 'new name';
 
    console.log(name, arguments[0]); // new name new name
 
    // 改变arguments
    arguments[1] = 'new age';
 
    console.log(age, arguments[1]); // new age new age
 
    // 测试未传入的是否会绑定
    console.log(sex); // undefined
 
    sex = 'new sex';
 
    console.log(sex, arguments[2]); // new sex undefined
 
    arguments[3] = 'new hobbit';
 
    console.log(hobbit, arguments[3]); // undefined new hobbit
 
}
 
foo('name', 'age')

传扬的参数,实参和 arguments 的值会分享,当未有传来时,实参加 arguments 值不会分享

除外,以上是在非严刻方式下,借使是在严峻情势下,实参和 arguments 是不会分享的。

传送参数

将参数从多少个函数字传送递到另贰个函数

// 使用 apply 将 foo 的参数字传送递给 bar function foo() { bar.apply(this, arguments); } function bar(a, b, c) { console.log(a, b, c); } foo(1, 2, 3)

1
2
3
4
5
6
7
8
9
// 使用 apply 将 foo 的参数传递给 bar
function foo() {
    bar.apply(this, arguments);
}
function bar(a, b, c) {
   console.log(a, b, c);
}
 
foo(1, 2, 3)

强大的ES6

利用ES6的 … 运算符,大家能够轻巧转成数组。

function func(...arguments) { console.log(arguments); // [1, 2, 3] } func(1, 2, 3);

1
2
3
4
5
function func(...arguments) {
    console.log(arguments); // [1, 2, 3]
}
 
func(1, 2, 3);

应用

arguments的行使其实过多,在下个连串,也正是 JavaScript 专题种类中,我们会在 jQuery 的 extend 达成、函数柯里化、递归等场景见到arguments 的人影。那篇小说就不现实实行了。

比方要计算那些情状的话,暂且能想到的不外乎:

  1. 参数不定长
  2. 函数柯里化
  3. 递归调用
  4. 函数重载

招待留言回复。

深刻体系

JavaScript深入种类目录地址:。

JavaScript深远种类测度写十五篇左右,意在帮大家捋顺JavaScript底层知识,入眼解说如原型、成效域、实行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、承接等难处概念。

设若有荒唐也许不实事求是的地点,请必得给予指正,十一分多谢。若是喜欢照旧持有启迪,迎接star,对作者也是一种鞭挞。

  1. JavaScirpt 深入之从原型到原型链
  2. JavaScript 深远之词法成效域和动态成效域
  3. JavaScript 深切之实施上下文栈
  4. JavaScript 深切之变量对象
  5. JavaScript 浓烈之作用域链
  6. JavaScript 深入之从 ECMAScript 标准解读 this
  7. JavaScript 深远之实行上下文
  8. JavaScript 深远之闭包
  9. JavaScript 深远之参数按值传递
  10. JavaScript 浓重之call和apply的效仿完成
  11. JavaScript 深切之bind的如法炮制实现
  12. JavaScript 深远之new的模拟完结

    1 赞 2 收藏 评论

图片 3

本文由技术教程发布,转载请注明来源:深入之call和apply的模拟实现,深入之类数组对象