JavaScript内置一些方法的实现原理--new关键字,call/apply/bind方法--实现

摘要:
调用该函数并通过apply方法将该函数绑定到obj。访问属性时会触发get方法,并返回调用myCall方法的函数。最后,delete context myFn属性返回res。apply方法与call方法非常相似,只是参数传递不同。apply的第二个参数是数组或类似数组的对象。接下来,让我们实现一个功能相对单一的应用程序方法。略微兼容:1功能。原型myApply=函数。原型应用| |函数{2varself=this,args=[],_args=[];res,i,len;3context=context | | window;4Object.defineProperty;10for{11args.push;12_args.push;15删除内容。myFn;16个回返者;17}; 需要注意的是,只传入了两个参数,第二个是数组或类数组。例如,传入myCall方法中eval函数的字符串直接映射到arguments对象。

先学习下new操作符吧

new关键字调用函数的心路历程:

1.创建一个新对象

2.将函数的作用域赋给新对象(this就指向这个对象)

3.执行函数中的代码

4.返回这个对象

根据这个的思路,来实现一个简单的new操作吧,代码演示:

 1 function myNew(Func, ...args) {
 2   if (typeof Func !== 'function') throw new Error(`${Func} is not a constructor`);
 3   const obj = Object.create(Func.prototype);
 4   const res = Func.apply(obj, args);
 5   if (res instanceof Object) return res;
 6   return obj;
 7 }
 8 function Person(name, age) {
 9   this.name = name;
10   this.age = age;
11 }
12 const p1 = new Person('xm', 20);
13 const p2 = myNew(Person, 'xm', 20);

首先,先判断传进来的第一个参数是不是函数,不是函数抛出错误。

接着以传进来的函数的原型对象为原型创建一个新对象。

这步相当于obj.__proto__ = Func.prototype或者Object.setPrototypeOf(obj, Func.prototype)。

(Object.create()是推荐用法,ie11以下不支持上面那些东西、const,ie11不支持...)

调用函数,通过apply方法把函数的this绑定到obj。如果函数有返回值,且为对象,则返回该对象。

否则返回obj。

稍微兼容一点的写法(到ie9):

 1 function myNew(Func) {
 2   if (typeof Func !== 'function') throw new Error( Func + 'is not a constructor');
 3   var obj = Object.create(Func.prototype);
 4   var args = Array.prototype.slice.call(arguments, 1);
 5   var res = Func.apply(obj, args);
 6   if (res instanceof Object) return res;
 7   return obj;
 8 }
 9 function Person(name, age) {
10   this.name = name;
11   this.age = age;
12 }
13 var p1 = new Person('xm', 20);
14 var p2 = myNew(Person, 'xm', 20);

验证:

JavaScript内置一些方法的实现原理--new关键字,call/apply/bind方法--实现第1张JavaScript内置一些方法的实现原理--new关键字,call/apply/bind方法--实现第2张

call方法

call方法最常见的用法就是改变函数的this指向。

该方法的第一个参数为函数的this指向。后面的参数为函数的参数,按顺序传入。

根据这个思路,来实现这样一个功能简单的call方法吧。代码演示:

 1 Function.prototype.myCall = function(context, ...args) {
 2   let self = this;
 3   context = context || window;
 4   Object.defineProperty(context, 'myFn', {
 5     configurable: true,
 6     get() {
 7       return self;
 8     }
 9   });
10   const res = context.myFn(...args);
11   delete context.myFn;
12   return res;
13 };

以上代码,在Function的原型对象上添加一个myCall方法,就可以实现fn.myCall()如此模样的操作了。

接着定义一个变量保存方法函数内部this,this指向调用该方法的函数。

然后做一个简单的短路操作,如果传进来的第一个参数是undefined、null,则指向window。

当然,如果瞎传,那就只能下一行执行时报错了。

接着在context上添加一个属性,并把该属性设置成可以删除的,设置get方法。

访问该属性时触发get方法,返回的是调用myCall方法的函数。接下来调用函数,返回值保存到res。

最后删除context.myFn属性,返回res。

验证:

JavaScript内置一些方法的实现原理--new关键字,call/apply/bind方法--实现第3张

稍微兼容一些的写法:

 1 Function.prototype.myCall = Function.prototype.call || function(context) {
 2   var self = this, args = [], res, i, len;
 3   context = context || window;
 4   Object.defineProperty(context, 'myFn', {
 5     configurable: true,
 6     get() {
 7       return self;
 8     }
 9   });
10   for (i = 1, len = arguments.length; i < len; i++) {
11     args.push('arguments[' + i + ']');
12   }
13   res = eval('context.myFn(' + args + ')');
14   delete context.myFn;
15   return res;
16 };

当然,测试的时候先把前面的短路操作去掉。。

apply方法

apply方法与call方法很相似,只在传参上有点区别。apply第二个参数是数组或类数组对象。

 接下来就来实现一个功能相对单一的apply方法吧。代码演示:

 1 Function.prototype.myApply = function(context, arr) {
 2   let self = this;
 3   context = context || window;
 4   Object.defineProperty(context, 'myFn', {
 5     configurable: true,
 6     get() {
 7       return self;
 8     }
 9   });
10   const res = context.myFn(...arr);
11   delete context.myFn;
12   return res;
13 };

几乎和call方法的实现一模一样。。

JavaScript内置一些方法的实现原理--new关键字,call/apply/bind方法--实现第4张

稍微兼容一些的写法:

 1 Function.prototype.myApply = Function.prototype.apply || function(context) {
 2   var self = this, args = [], _args = [], res, i, len;
 3   context = context || window;
 4   Object.defineProperty(context, 'myFn', {
 5     configurable: true,
 6     get() {
 7       return self;
 8     }
 9   });
10   for (i = 0, len = arguments[1].length; i < len; i++) {
11     args.push('_args[' + i + ']');
12     _args.push(arguments[1][i]);
13   }
14   res = eval('context.myFn(' + args + ')');
15   delete context.myFn;
16   return res;
17 };

需要注意的是,传进的参数只有两个,第二个是数组或类数组,像myCall方法中传入eval函数的字符串直接映射arguments对象是行不通的。

可以再创建一个数组,把传进来的第二个参数全部push进去,eval函数的字符串直接映射该数组。

当然,测试的时候先把前面的短路操作去掉。。

bind方法

bind方法略有不同,函数调用bind方法,返回的是一个函数。当函数只是普通调用时,this指向bind方法的第一个参数。

如果返回的函数被当做构造函数调用时,前面绑定的this又无效了,此时指向new操作符创建的对象。

如:

1 function person() {
2   console.log(this); 
3 }
4 const P1 = person.bind({name: 'xm'});
5 P1();// {name: 'xm'}
6 new P1();// person实例

还需要注意的是,当返回的函数被当做对象的方法调用时,此时this仍然指向bind方法绑定的对象。如:

1 const obj = {
2   P1  
3 };
4 obj.P1();// {name: 'xm'}

接下来,来实现这样的一个bind方法吧。代码演示:

 1 Function.prototype.myBind = function(context, ...args) {
 2   const self = this;
 3   context = context || window;
 4   const Bound = function() {
 5     const _args = [...args, ...arguments];
 6     let _context = context;
 7     if (this instanceof Bound) _context = this;
 8     return self.apply(_context, _args);
 9   }
10   const _Fn = function () {};
11   _Fn.prototype = this.prototype;
12   Bound.prototype = new _Fn();
13   return Bound;
14 };

以上代码,在Function的原型对象上添加一个myBind方法,就可以实现fn.myBind()如此模样的操作了。

接着保存函数的this,这个this指向调用myBind()方法的函数。

然后简单处理下传进来的第一个参数,为null、undefined时指向window。

接下来就是创建一个Bound函数,这个Bound函数是一个闭包,它可以访问外层函数的变量。

最后它是要作为myBind()方法的返回值,返回出去的。

在Bound函数里,第五行代码先处理了下参数。除第一个context参数,其他参数有时候会在myBind方法里传,有时候会在返回的函数里传。

这里不管三七二十一,都转成数组,然后拼接成一个数组。然后通过apply方法传参。(使用eval函数也行,就是麻烦了些)

第七行代码判断this 和 Bound的关系,如果Bound函数被new操作符调用,则函数内部的this指向Bound的实例(即new创建的对象)。

此时instanceof会返回真,然后这里处理下_context参数,把this赋给_context。

最后通过apply方法调用self(即外层函数保存的this),最后把函数返回值return出去。

测试代码:

1 function person(name, age) {
2   console.log(this);
3   console.log(name, age);
4 }
5 const Bound = person.myBind({name: 'xm'}, 'xh' );
6 const person1 = new Bound(20);

以上代码,person函数先调用myBind()方法,并把返回的函数(Bound)保存到Bound。

然后通过new调用Bound函数。结果:

JavaScript内置一些方法的实现原理--new关键字,call/apply/bind方法--实现第5张

对比:

JavaScript内置一些方法的实现原理--new关键字,call/apply/bind方法--实现第6张

方法没有大的问题,基本是实现了。

JavaScript内置一些方法的实现原理--new关键字,call/apply/bind方法--实现第7张

然后,这三行代码的作用是处理Bound函数的原型链。这样做的一个好处是,

Bound函数原型对象是空函数_Fn的一个实例。可以随意扩展。_Fn函数的原型对象又是person函数的原型对象。

这就等于Bound函数的原型对象的原型指针指向了person函数的原型对象。。。代码描述就是  JavaScript内置一些方法的实现原理--new关键字,call/apply/bind方法--实现第8张

因此,Bound函数的实例不仅拥有Bound.prototype上的方法和属性,还拥有person函数原型对象的方法和属性。

原型链,如图:

JavaScript内置一些方法的实现原理--new关键字,call/apply/bind方法--实现第9张

验证:

JavaScript内置一些方法的实现原理--new关键字,call/apply/bind方法--实现第10张

稍微兼容一些的写法:

 1 Function.prototype.myBind = Function.prototype.bind || function(context) {
 2   var self = this;
 3   var args = Array.prototype.slice.call(arguments, 1);
 4   context = context || window;
 5   var Bound = function() {
 6     var _args = Array.prototype.slice.call(arguments, 0);
 7     var _context = context;
 8     if (this instanceof Bound) _context = this;
 9     return self.apply(_context, args.concat(_args));
10   }
11   var _Fn = function () {};
12   _Fn.prototype = this.prototype;
13   Bound.prototype = new _Fn();
14   return Bound;
15 };

免责声明:文章转载自《JavaScript内置一些方法的实现原理--new关键字,call/apply/bind方法--实现》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇bat 与 PowerShell 的结合使用VSCode 中Python代码自动补全与智能提示【转】下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Android 手动显示和隐藏软键盘

1、方法一(如果输入法在窗口上已经显示,则隐藏,反之则显示) InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);   imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_...

C++面试

https://blog.csdn.net/weixin_44363885/article/details/99567746 这一行是个 贼鸡巴重要的链接!!!   很好的总结 我直接复制到下面了: 社招:社招的同学,无论是1-3年经验,还是中途转行,都可参考。写简历必须有针对性,以后台开发为例,请去拉勾网 / 猎聘 / 智联招聘等网站,多看看后台开发的J...

【学习】java下实现调用oracle的存储过程和函数

在oracle下创建一个test的账户,然后按一下步骤执行: 1.创建表:STOCK_PRICES View Code--创建表格CREATETABLE STOCK_PRICES( RIC VARCHAR(6) PRIMARYKEY, PRICE NUMBER(7,2), UPDATED DATE ); 2.插入测试数据: View...

C++ this指针

1. this指针的用处:   一个对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。也就是说,即使你没有写上this指针,编译器在编译的时候也是加上this的,它作为非静态成员函数的隐含形参,对各...

POJ 3281 Dining (网络流之最大流)

题意:农夫为他的 N (1 ≤ N ≤ 100) 牛准备了 F (1 ≤ F ≤ 100)种食物和 D (1 ≤ D ≤ 100) 种饮料。每头牛都有各自喜欢的食物和饮料, 而每种食物或饮料只能分配给一头牛。最多能有多少头牛可以同时得到喜欢的食物和饮料? 析:是一个经典网络流的题,建立一个超级源点,连向每种食物,建立一个超级汇点,连向每种饮料,然后把每头牛...

Strategy(策略)模式

1.概述         在软件开发中也常常遇到类似的情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能。如查找、排序等,一种常用的方法是硬编码(Hard Coding)在一个类中,如需要提供多种查找算法,可以将这些算法写到一个类中,在该类中提供多个方法,每一个方法对应一个具体的查找算法;当然也可以将...