博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JS复习笔记之造call、apply轮子
阅读量:7037 次
发布时间:2019-06-28

本文共 5980 字,大约阅读时间需要 19 分钟。

计划

?会整理一系列js知识,每周按空闲时间长短至少两篇,按照你不知道的js的内容排版顺序跟着写,目的扎实基础。写一篇文章会查阅参考大量有关内容,笔者写得安心,读者看得放心。此处轮子代码和思路来源于@冴羽大大。 (咦,mac自带输入法打不出冴ya这个字。)

Function.prototype.call()

引用MDN关于此方法的一些描述:

  1. 语法:
    fun.call(thisArg, arg1, arg2, ...)。
    参数含义:
    thisArg: 在fun函数运行时指定的this值。需要注意的是,指定的this值并不一定是该函数执行时真正的this值,如果这个函数处于non-strict mode(非严格模式),则指定为nullundefined的this值会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象
    arg1, arg2,...:指定的参数列表
  2. 允许为不同的对象分配和调用属于一个对象的函数/方法。
    提供新的 this 值给当前调用的函数/方法。你可以使用call来实现继承:写一个方法,然后让另外一个新的对象来继承它(而不是在新对象中再写一次这个方法)。

举个例子:

function Pig(name, taste) {  this.name = name;  this.taste = taste;}function Cat(name, taste) {  Pig.call(this, name, taste);  this.shape = 'cute';}console.log(new Cat('honey', 'delicious').name) // honey复制代码

这个例子注意到3点

  • Pig函数执行了
  • call改变了Pig函数的this指向,指向到Cat
  • 上面的Cat函数体等同于把Pig的函数体给"拿"了过来:function Cat(name, taste) { this.name = name, this.taste = taste, this.shape = 'cute'}

再来个例子:

function Pig(name) {  this.name = name;  console.log(this.name);  console.log(this.taste);}const foo = {  taste: 'delicious'}Pig.call(foo, 'honey') //输出: honey, delicious复制代码

如果再给foo加上个name属性呢?注意看

function Pig(name) {  this.name = name;  console.log(this.name);  console.log(this.taste);}const foo = {  taste: 'delicious',  name: "foo's name"}Pig.call(foo, 'honey') // 输出: honey, deliciousconsole.log(foo.name)  // 输出:honey复制代码

现在的情况可以看出,Pig函数体执行的时候,this会指向foo, Pig本身的函数体里this相关的修改同步于它指向的foo。

造轮子第一步

要模拟之前想想思路:根据call的特性,大致是调用的函数执行,执行时指向call的第一个参数里的this,可以传参,并且函数体里可以同步指向的this相关修改。

一步步来。我们把foo对象改变一下:

const foo = {  taste: 'delicious',  name: "foo's name",  Pig: function() {    console.log(this.name);    console.log(this.taste);  }}foo.Pig(); // 输出:foo's name复制代码

想想,此时是不是Pig函数体里的this指向了foo对象了?单论这点,等同于 Pig.call(foo)。

造轮子的第一步思路就在这里,分步骤来说就是:

1.将函数设置为对象的属性

2.执行该函数

3.删除该函数

1.foo.Pig = Pig2.foo.Pig()3.delete foo.Pig复制代码

根据这个思路写出造轮子第一步的代码:

// 第一步完整代码Function.prototype.call2 = function(obj) {  obj.fn = this // foo.Pig = this 此处this可以获取调用call2()的函数的函数体。this的显示绑定机制。  obj.fn()  delete obj.fn}// have a testconst foo = {  taste: 'delicious',  name: "foo's name"}function Pig() {  console.log(this.taste);  console.log(this.name);}Pig.call2(foo);  // 输出:delicious foo's nameconsole.log(foo); // 输出:{ taste: 'delicious', name: 'foo\'s name' }复制代码

看看输出结果,符合预期。调用的函数Pig执行了,执行时也是指向的第一个参数foo对象的this,并且函数体里可以同步指向的this相关修改。但是此时参数只能传第一个。接下来去改善。

造轮子第二步

根据call的特性,大致是调用的函数执行,执行时指向call的第一个参数里的this,可以传参,并且函数体里可以同步指向的this相关修改。我们现在参数那里还没有完善。

举个例子:

function Pig(weight, age) {    console.log(this.taste);    console.log(this.name);    console.log(weight);    console.log(age);}const foo = {    taste: 'delicious',    name: 'honey'}Pig.call(foo, 100, 6); // 输出:delicious honey 100 6复制代码

正常的传参应该是这样的,怎么去完善咱们的代码呢?

我们可以利用arguments类数组对象来取得传入的参数。以上一个例子而言,arguments应该是:

arguments = {    0: foo,    1: 100,    2: 6,    length: 3}复制代码

利用ES6语法和call本身的第二步第一版代码

那我们像下面那样修改行吗?

Function.prototype.call2 = function(obj) {    obj.fn = this;    obj.fn(...arguments.slice(1)) // 注意此处    delete obj.fn}复制代码

不行的,类数组对象没有slice这个方法。我们得用

Array.prototype.slice.call(arguments, 1)

来把arguments这个类数组对象转换成数组,然后从第一个元素往后切,返回一个新数组。

所以代码修改后应该为:

// 第二步第一版完整代码Function.prototype.call2 = function(obj) {    obj.fn = this;    var arr = Array.prototype.slice.call(arguments, 1); // 这个例子就是[100, 6]    obj.fn(...arr);    delete obj.fn;}// have a testconst foo = {    taste: 'delicious',    name: 'honey'}function Pig(weight, age) {    console.log(this.taste);    console.log(this.name);    console.log(weight, age)}Pig.call2(foo, 100, 6)// delicious// honey// 100 6复制代码

测试成功。但这里用到了ES6和call本身,下面写第二版代码,ES3原汁原味。

原汁原味ES3的第二步第二版代码

同样利用arguments。

类数组对象,有length属性,我们可以用循环构建一个参数数组。

const arr = [];for (let i = 1, len = arguments.length; i < len; i++) {    arr.push('arguments[' + i + ']');}// arr => ['arguments[1]', 'arguments[2]...']复制代码

好了,不定参参数数组有了。接下来就是想办法把这个数组的每一项以参数的格式放进obj.fn()的形参中。

我们可以先把arr数组变成字符串。有两种办法。

arr.toString / arr.join()

返回值都是 "arguments[1], arguments[2]..."

然后可以利用eval()这个魔鬼来直接把这字符串的两个引号去掉,达到形参格式要求。看下面逻辑。

eval('obj.fn(' + arr + ')')

在eval里此处的arr会自动调用arr.toString()方法。

最终相当于执行了obj.fn(arguments[1], arguments[2], ...)

所以:

// 原汁原味ES3的第二步第二版完整代码Function.prototype.call2 = function(obj) {    obj.fn = this;    const arr = [];    for (let i = 1, len = arguments.length; i < len; i++) {        arr.push('arguments[' + i + ']'); // 写出arr.push(argumentsp[i])也可以    }    eval('obj.fn(' + arr + ')');    delete obj.fn;}// have a testconst foo = {    taste: 'delicious',    name: 'honey'}function Pig(weight, age) {    console.log(this.taste);    console.log(this.name);    console.log(weight, age);}Pig.call2(foo, 100, 6); // delicious// honey// 100 6复制代码

成功。这个就比较兼容了。

造轮子第三步

再完善两点。

  1. thisArg参数在非严格模式下,指定为null和undefined的this值会自动指向全局对象(浏览器中就是window对象)。
  2. 函数可以有返回值。

这比较容易解决,看代码:

// 第三步完整代码:Function.prototype.call2 = function(obj) {    var obj = obj || window;    obj.fn = this;    const arr = [];    for (let i = 1, len = arguments.length; i < len; i++) {        arr.push('arguments[' + i + ']');     }    const result = eval('obj.fn(' + arr + ')');    delete obj.fn;    return result;}// have a testvar name = "Darling";const foo = {    name: 'honey'}function Pig(weight, age) {    console.log(this.name);    return {        weight: weight,        age: age,        name: this.name    }}Pig.call2(null);// Darling// Object { //    weight: undefined//    age: undefined//    name: "Darling"// }Pig.call2(foo, 100, 6);// honey// Object {
// weight: 100,// age: 6,// name: 'honey'//}复制代码

OK,目前为止,造完了一个简单的call轮子call2?。

Function.prototype.apply()的轮子

与call就参数差异,直接上代码。

Function.prototype.apply2 = function(obj, arr) {    var obj = obj || window;    obj.fn = this;    var result;    if (!arr) {        result = obj.fn();    } else {        var args = [];        for (let i = 0, len = arr.length; i < len; i++) {            args.push('arr[' + i + ']');        }        result = eval('obj.fn(' + args + ')');    }    delete obj.fn;    return result;}复制代码

参考:

1.

2.

3.

转载于:https://juejin.im/post/5c9ab8206fb9a070d7558a0e

你可能感兴趣的文章
面试题目:反转链表的算法实现
查看>>
xss挖掘初上手
查看>>
SGU 116 Index of super-prime
查看>>
简化Web开发的12个HTML5-CSS框架
查看>>
C#温故而知新学习系列之.NET运行机制—.NET中非托管代码是指什么?(二)
查看>>
25个漂亮的国外绿色网站设计作品分享
查看>>
C++中delete与delete[]
查看>>
iphone:URL initWithString 返回为空
查看>>
ASP.NET页面间数据传递的9种方法
查看>>
#百度360大战# 我为什么要支持360
查看>>
html5指南--3.拖拽功能的实现
查看>>
绘图: Python matplotlib简介
查看>>
C# HttpHelper 1.0正式版发布
查看>>
《简明Python教程》学习笔记
查看>>
Fine Uploader文件上传组件
查看>>
POJ 3264 Balanced Lineup
查看>>
php_mcrypt.dll无法加载解决方法
查看>>
Semplice Linux 4 发布,轻量级发行版
查看>>
在android项目中R.java中失去id类怎么办
查看>>
Andorid时间控件和日期控件
查看>>