最近在帮朋友复习 JS 相关的基础知识,遇到不会的问题,她就会来问我。

这不是很简单?三下五除二,分分钟解决。

  1. function bind(fn, obj, ...arr) {
  2. return fn.apply(obj, arr)
  3. }

于是我就将这段代码发了过去

这时候立马被女朋友进行了一连串的灵魂拷问。

这个时候,我马老师就坐不住了,我不服气,我就去复习了一下 bind,发现太久不写基础代码,还是会需要一点时间复习,这一次我得写一个有深度的 bind,深的马老师的真传,给他分成了五层速记法。

第一层 - 绑定在原型上的方法

这一层非常的简单,得益于 JS 原型链的特性。由于 function xxx 的原型链 指向的是 Function.prototype , 因此我们在调用 xxx.bind 的时候,调用的是 Function.prototype 上的方法。

  1. Function.prototype._bind = function() {}

这样,我们就可以在一个构造函数上直接调用我们的bind方法啦~例如像这样。

  1. funciton myfun(){}
  2. myfun._bind();

想要详细理解这方面的可以看这张图和这篇文章(https://github.com/mqyqingfeng/blog/issues/2)

第二层 - 改变 this 的指向

这可以说是 bind 最核心的特性了,就是改变 this 的指向,并且返回一个函数。而改变 this , 我们可以通过已知的 apply 和 call 来实现,这里我们就暂且使用 apply 来进行模拟。首先通过 self 来保存当前 this,也就是传入的函数。因为我们知道 this 具有 隐式绑定的规则(摘自 《你不知道的JavaScript(上)》2.2.2 ),

  1. function foo() {console.log(this.a)}
  2. var obj = {a: 2, foo};
  3. obj.foo(); // 2

通过以上特性,我们就可以来写我们的 _bind 函数。

  1. Function.prototype._bind = function(thisObj) {
  2. const self = this;
  3. return function () {
  4. self.apply(thisObj);
  5. }
  6. }
  1. var obj = {a:1}
  2. function myname() {console.log(this.a)}
  3. myname._bind(obj)(); // 1

可能很多朋友都止步于此了,因为在一般的面试中,特别是一些校招面试中,可能你只需要知道前面两个就差不多了。但是想要在面试中惊艳所有人,仍然是不够的,接下来我们继续我们的探索与研究。

第三层 - 支持柯里化

函数柯里化是一个老生常谈的话题,在这里再复习一下。

  1. function fn(x) {
  2. return function (y) {
  3. return x + y;
  4. }
  5. }
  6. var fn1 = fn(1);
  7. fn1(2) // 3

不难发现,柯里化使用了闭包,当我们执行 fn1 的时候,函数内使用了外层函数的 x, 从而形成了闭包。

而我们的 bind 函数也是类似,我们通过获取当前外部函数的 arguments ,并且去除了绑定的对象,保存成变量 args,最后 return 的方法,再一次获取当前函数的 arguments, 最终用 finalArgs 进行了一次合并。

  1. Function.prototype._bind = function(thisObj) {
  2. const self = this;
  3. const args = [...arguments].slice(1)
  4. return function () {
  5. const finalArgs = [...args, ...arguments]
  6. self.apply(thisObj, finalArgs);
  7. }
  8. }

通过以上代码,让我们 bind 方法,越来越健壮了。

  1. var obj = { i: 1}
  2. function myFun(a, b, c) {
  3. console.log(this.i + a + b + c);
  4. }
  5. var myFun1 = myFun._bind(obj, 1, 2);
  6. myFun1(3); // 7

一般到了这层,可以说非常棒了,但是再坚持一下下,就变成了完美的答卷。

第四层 - 考虑 new 的调用

要知道,我们的方法,通过 bind 绑定之后,依然是可以通过 new 来进行实例化的, new 的优先级会高于 bind摘自 《你不知道的JavaScript(上)》2.3 优先级)。

这一点我们通过原生 bind 和我们第四层的 _bind 来进行验证对比。

  1. // 原生
  2. var obj = { i: 1}
  3. function myFun(a, b, c) {
  4. // 此处用new方法,this指向的是当前函数 myFun
  5. console.log(this.i + a + b + c);
  6. }
  7. var myFun1 = myFun.bind(obj, 1, 2);
  8. new myFun1(3); // NAN
  9. // 第四层的 bind
  10. var obj = { i: 1}
  11. function myFun(a, b, c) {
  12. console.log(this.i + a + b + c);
  13. }
  14. var myFun1 = myFun._bind(obj, 1, 2);
  15. new myFun1(3); // 7

注意,这里使用的是 bind方法

因此我们需要在 bind 内部,对 new 的进行处理。而 new.target 属性,正好是用来检测构造方法是否是通过 new 运算符来被调用的。

接下来我们还需要自己实现一个 new ,

而根据 MDNnew 关键字会进行如下的操作:

1.创建一个空的简单JavaScript对象(即{});

2.链接该对象(设置该对象的constructor)到另一个对象 ;

3.将步骤1新创建的对象作为this的上下文 ;

4.如果该函数没有返回对象,则返回this

  1. Function.prototype._bind = function(thisObj) {
  2. const self = this;
  3. const args = [...arguments].slice(1);
  4. return function () {
  5. const finalArgs = [...args, ...arguments];
  6. // new.target 用来检测是否是被 new 调用
  7. if(new.target !== undefined) {
  8. // this 指向的为构造函数本身
  9. var result = self.apply(this, finalArgs);
  10. // 判断改函数是否返回对象
  11. if(result instanceof Object) {
  12. return reuslt;
  13. }
  14. // 没有返回对象就返回 this
  15. return this;
  16. } else {
  17. // 如果不是 new 就原来的逻辑
  18. return self.apply(thisArg, finalArgs);
  19. }
  20. }
  21. }

看到这里,你的造诣已经如火纯情了,但是最后还有一个小细节。

第五层 - 保留函数原型

以上的方法在大部分的场景下都没有什么问题了,但是,当我们的构造函数有 prototype 属性的时候,就出问题啦。因此我们需要给 prototype 补上,还有就是调用对象必须为函数。

  1. Function.prototype._bind = function (thisObj) {
  2. // 判断是否为函数调用
  3. if (typeof target !== 'function' || Object.prototype.toString.call(target) !== '[object Function]') {
  4. throw new TypeError(this + ' must be a function');
  5. }
  6. const self = this;
  7. const args = [...arguments].slice(1);
  8. var bound = function () {
  9. var finalArgs = [...args, ...arguments];
  10. // new.target 用来检测是否是被 new 调用
  11. if (new.target !== undefined) {
  12. // 说明是用new来调用的
  13. var result = self.apply(this, finalArgs);
  14. if (result instanceof Object) {
  15. return result;
  16. }
  17. return this;
  18. } else {
  19. return self.apply(thisArg, finalArgs);
  20. }
  21. };
  22. if (self.prototype) {
  23. // 为什么使用了 Object.create? 因为我们要防止,bound.prototype 的修改而导致self.prototype 被修改。不要写成 bound.prototype = self.prototype; 这样可能会导致原函数的原型被修改。
  24. bound.prototype = Object.create(self.prototype);
  25. bound.prototype.constructor = self;
  26. }
  27. return bound;
  28. };

以上就是一个比较完整的 bind 实现了,如果你想了解更多细节的实践,可以查看。(也是 MDN 推荐的)

https://github.com/Raynos/function-bind

结语

** ️关注+点赞+收藏+评论+转发️ **,原创不易,鼓励笔者创作更好的文章

关注公众号秋风的笔记,一个专注于前端面试、工程化、开源的前端公众号

js 实现 bind 的这五层,你在第几层?的更多相关文章

  1. 【转载】JS中bind方法与函数柯里化

    原生bind方法 不同于jQuery中的bind方法只是简单的绑定事件函数,原生js中bind()方法略复杂,该方法上在ES5中被引入,大概就是IE9+等现代浏览器都支持了(有关ES5各项特性的支持情 ...

  2. js中bind解析

    一.arguments的含义 // arguments 是一个对应于传递给函数的参数的类数组对象 function a(){ console.log(arguments); } a(); // Arg ...

  3. 详解js的bind、call、apply

    详解js的bind.call.apply 说明 虽然bind.call.apply都是js很基础的一块知识,但是我从未认真总结过这三者的区别. 由于公司后端是用的微服务架构,又没有中间层对接,导致前端 ...

  4. js中bind,call,apply方法的应用

    最近用js的类写东西,发现一个无比蛋疼的事,那就是封装的类方法中的this指针经常会改变指向,失去上下文,导致程序错误或崩溃. 比如: function Obj(){ this.type = &quo ...

  5. js中bind、call、apply函数的用法

    最近一直在用 js 写游戏服务器,我也接触 js 时间不长,大学的时候用 js 做过一个 H3C 的 web的项目,然后在腾讯实习的时候用 js 写过一些奇怪的程序,自己也用 js 写过几个的网站.但 ...

  6. js中bind、call、apply函数的用法 (转载)

    最近看了一篇不错的有关js的文章,转载过来收藏先!!! 最近一直在用 js 写游戏服务器,我也接触 js 时间不长,大学的时候用 js 做过一个 H3C 的 web 的项目,然后在腾讯实习的时候用 j ...

  7. JS中bind、call和apply的作用以及在TS装饰器中的用法

    目录 1,前言 1,call 1.1,例子 1.2,直接调用 1.3,将this指向另一个对象 1.4,传递参数 2,apply 2.1,例子 2.2,直接调用 2.3,将this指向另一个对象 2. ...

  8. js的bind方法

    转载:http://www.jb51.net/article/94451.htm http://www.cnblogs.com/TiestoRay/p/3360378.html https://seg ...

  9. js原生bind()用法[注意不是jquery里面的bind()]

    <div id="a"> <div></div> <div></div> <div></div> ...

随机推荐

  1. Win 10 下Pipenv源码安装 odoo12

    因为,本身电脑已经安装odoo8,9,10等odoo的版本,当时,没有考虑是直接是统一的环境很配置. 现在,在odoo11的环境下,需要Python 3的语言环境可以很好地支持odoo11的功能,所以 ...

  2. 记一次scrapy-redis爬取小说网的分布式搭建过程

    scrapy-redis简介 scrapy-redis是scrapy框架基于redis数据库的组件,用于scrapy项目的分布式开发和部署. 有如下特征: 分布式爬取 可以启动多个spider工程,相 ...

  3. x64dbg 条件断点相关文档

    输入 字符格式 条件断点 Input When using x64dbg you can often use various things as input. Commands Commands ha ...

  4. 自己挖的坑自己填--jxl进行Excel下载堆内存溢出问题

    今天在进行使用 jxl 进行 Excel 下载时,由于数据量大(4万多条接近5万条数据的下载),数据结构过于负责,存在大量大对象(虽然在对象每次用完都设置为null,但还是存在内存溢出问题),加上本地 ...

  5. Elasticsearch扩展X-pack实施流程-实施

    Elasticsearch扩展X-PACK实施流程 elasticsearch5.2.1安装X-PACK,对ES集群进行监控,报警,安全验证,报告,图形化操作 注意 版本号需要固定,小版本都不能差,要 ...

  6. [图论]最短网络:prim

    最短网络 目录 最短网络 Description Input Output Sample Input Sample Output 解析 代码 Description 农民约翰被选为他们镇的镇长!他其中 ...

  7. 学习笔记-json数据格式化

    标准的json : let result=[{"a": 'aa', "b": 'aa', "c": 'aa'}, {"a" ...

  8. SpringBoot项目打包部署

    部署方式 SpringBoot项目可以通过jar包或者war包部署在服务器上,因为jar包更适合前后端分离的项目,所以这里我们使用jar包的方式. 添加maven支持 <!-- 这个插件,可以将 ...

  9. c# 定时启动一个操作、任务

    // 定时启动一个操作.任务 using System; using System.Collections.Generic; using System.Collections.ObjectModel; ...

  10. 04.ElementUI源码学习:组件封装、说明文档的编写发布

    0x00.前言 书接上文.项目经过一系列的配置,开发脚手架已经搭建完毕.接下来开始封装自定义组件.并基于 markdown 文件生成文档和演示案例. 后续文章代码会根据篇幅,不影响理解的情况下进行部分 ...