0
点赞
收藏
分享

微信扫一扫

JS中实现call方法了解原理

引言

如何手写实现原型链中的call方法呢?

原本的call方法

首先使用原本的call方法来做一个小demo

/**
 * 手写call函数
 * 禁止使用bind, apply辅助
 */

// call函数是什么
// 比如有这么一个函数
function method(a,b){
    console.log(this,a,b);
    return a+b;
}
// 使用call来调用上面的方法
method.call({},1,2);

得到结果

{} 1 2

call调用了method方法并且将this指定为{}

所以输出的this就是{}

思路和实现

手写实现call方法的思路大致如下:

  • 在Function的原型链中添加一个自定义的mycall方法,接收参数为指定的this以及其他未知的参数
  • 修改this的指向
  • 调用原本的方法并且将未知参数传入,获得返回值后,将返回值返回

实现1

代码

Function.prototype.myCall = function (ctx, ...args) {
  ctx.func = this;
  var res = ctx.func(...args);
  delete ctx.func;
  return res;
}

解释

接收一个ctx参数为this指向使用

...args是es6的剩余参数使用方式,可以接收未知数量的参数

将原本的this赋值给ctx的指定变量func

调用func方法并传入剩余参数,得到返回值

这个时候func已经没用了,所以delete删掉

最后返回res结果

测试

method.myCall({},2,3);

结果

{func: ƒ} 2 3

上述方法可以,但是有很多细节没有处理

实现2

实现1中比较简陋,没有考虑多种情况,比如:

例如第一个参数是null undefined

method.myCall(null,2,3);

method.myCall(undefined,2,3);

就会报错

JS中实现call方法了解原理_属性描述符

代码

我们来优化一下代码

Function.prototype.myCall2 = function (ctx, ...args) {
  // 进行一个判断
  ctx = (ctx === null || ctx === undefined) ? globalThis : Object(ctx);
  ctx.func = this;
  let res = ctx.func(...args);
  delete ctx.func;
  return res;
}

解释

对ctx进行一个是否为null或者undefined的判断

如果是,就将全局变量赋值给ctx,globalThis针对不同的运行时环境具体是不一样的,比如浏览器中就是window,在node环境中是global

如果为 非null/undefined ,则对ctx进行object包装,这样即使是基础数据类型,也会变成包装类

测试

method.myCall2(undefined,2,3);// 解决了上述问题

结果

chrome

JS中实现call方法了解原理_属性描述符_02

node

JS中实现call方法了解原理_属性描述符_03


实现3

mycall2依然有问题,比如我们设置了一个属性func,但是如果第一个参数本身就有一个同名属性的话,func这个属性就会被覆盖掉了

我们可以使用symbol来解决属性名重复的问题

代码

Function.prototype.myCall3 = function (ctx, ...args) {
  // 进行一个判断
  ctx = (ctx === null || ctx === undefined) ? globalThis : Object(ctx);
  let sym = Symbol('mycall');
  ctx[sym] = this;
  let res = ctx[sym](...args);
  delete ctx[sym];
  return res;
}

解释

因为symbol是唯一的,所以解决了重名问题

测试

method.myCall3({},2,3);//解决上述问题

结果

chrome

JS中实现call方法了解原理_手写call_04

node

JS中实现call方法了解原理_属性描述符_05

实现4

上述其实已经实现了手写call,但是有点不够舒服

因为输出的内容中会有设定的symbol属性,不是很美观

那么就需要用到属性描述符的相关内容了

代码

Function.prototype.myCall4 = function (ctx, ...args) {
  // 进行一个判断
  ctx = (ctx === null || ctx === undefined) ? globalThis : Object(ctx);
  let sym = Symbol('mycall');

  Object.defineProperty(ctx, sym, {
    enumerable: false,// 在node环境中,不会获取不可枚举的属性;而在浏览器中,会全部获取,所以会出现浏览器console.log能看到该属性,而在node中看不到
    configurable: true,
    value: this
  });

  let res = ctx[sym](...args);
  delete ctx[sym];
  return res;
}

解释

Object.defineProperty() 静态方法会直接在一个对象上定义一个新属性,或修改其现有属性,并返回此对象。

ctx要定义属性的对象

sym一个字符串或 Symbol,指定了要定义或修改的属性键

enumerable当且仅当该属性在对应对象的属性枚举中出现时,值为 true。默认值为 false。

configurable

当设置为 false 时,

该属性的类型不能在数据属性和访问器属性之间更改

该属性不可被删除

其描述符的其他属性也不能被更改(但是,如果它是一个可写的数据描述符,

则 value 可以被更改,writable 可以更改为 false)。

value

与属性相关联的值。可以是任何有效的 JavaScript 值(数字、对象、函数等)。默认值为 undefined。

使用属性描述符后,可以将指定的属性改为不可枚举的类型

测试

method.myCall4({}, 2, 3);

结果

node

JS中实现call方法了解原理_属性描述符_06

chrome

JS中实现call方法了解原理_js的call方法_07

总结

看似八股文的东西,其实考验的是es6的理解程度,希望能帮到有需要的人,并且也能提升自己对js的理解程度

举报

相关推荐

0 条评论