前言

最近由于工作比较忙,好久都没时间静下心来研究一些东西了。今天在研究 call 和 apply 的区别的时候,看到 github 上面的一篇文章,看完以后,感觉启发很大。

文章链接为 https://github.com/lin-xin/blog/issues/7 ,有兴趣的童鞋可以前往学习一下。

但是我主要想写的并不是我今天学习了这篇博文,那样也就太没有技术含量了对吧。

bind的实现

其实文章并不难理解,只要是对 js 有一定程度的了解的同学就能很容易看懂。我主要关心的是文章最后给出的自己动手实现 bind 函数的代码,代码如下:

if (!Function.prototype.bind) {
Function.prototype.bind = function () {
var self = this, // 保存原函数
context = [].shift.call(arguments), // 保存需要绑定的this上下文
args = [].slice.call(arguments); // 剩余的参数转为数组
return function () { // 返回一个新函数
self.apply(context,[].concat.call(args, [].slice.call(arguments)));
}
}
}

说实话,咋看这一段代码,感觉好像很平淡无奇,但是你要是细细去体味的话,简直能够让你回味无穷。

[].shift.call(arguments) 的含义

我相信,对于很多对 js 这门语言掌握并不算深的童鞋来说,这句代码的含义貌似并不怎么容易理解,我们其实细分一下,可以拆分出好几个知识点出来。

arguments 的含义

我相信,很多人应该知道 arguments,甚至于在实际的学习工作中也经常用到,但是我还是想在这个地方复习一下。

MDN 网站上对于 Arguments 对象是这么定义的:

arguments 是一个对应于传递给函数的参数的类数组对象。

我们可以看到 arguments 是一个类数组对象,他怎么用呢?别急,我们马上给出示例:

上面是我在 chrome 浏览器控制台进行的一个小测试,可以看到,arguments 是一个类数组对象,它的值中包含了我们在调用函数时候,朝里面传入的参数。

而我们知道,对于对象的调用,我们一般采用点号—— obj.key 的调用方式或者括号加对象的key的方式—— obj[key] ,这两种方式是完全等同的。

因此我们在创建函数的时候,其实并不需要朝里面传入变量,直接用 arguments 的方式来获取变量岂不是更好?依我个人的拙见,虽然这种方式接收参数很灵活,但是也存在弊端,函数的参数本来就是写起来让自己或者他人阅读起来更舒服的,君不见 C、Java 中参数还要指定类型呢,JS 已经够宽松了,再按照这种标准宽松下去,写起来是很随意,但是用起来就很蛋疼了。

[].shift

咋一看,你还不一定看得懂这一句代码,其实它就等同于 Array.prototype.shift ,不信你打开你的 chrome 浏览器控制台,试着输入 Array.prototype.shift === [].shift ,然后你看看是不是能够得到 true。也就是说,两者其实是同一个函数,只是调用的方式存在差异,一个是通过原型的方式直接在类上调用;一个是通过实例化,继承过来,然后再调用。

如果你对这个话题很感兴趣,刚好我在知乎上看到有相关话题的讨论:js中 [].slice 与 Array.prototype.slice 有什么区别?,你可以自行前往查看或者讨论。

call

如果还有不知道call为何物的同学,请前往 mdn 网站 call 页面去详细了解。

mdn 对 call 的定义为:

call() 方法调用一个函数, 其具有一个指定的this值和分别地提供的参数(参数的列表)。

call 的语法为:

fun.call(thisArg, arg1, arg2, …)


在了解了上面三个小知识点以后,我们就能理解了 [].shift.call(arguments) 的含义了。arguments 是类数组对象,它没有 shift 等数组独有的方法,怎么办?现在,我想要拿出传入的参数中的第一个参数,怎么操作,就只有用这种方式了。

我们来试验一下:

function a(){return arguments;}
var temp = a("a","b");temp.slice();   //Uncaught TypeError: temp.slice is not a function

我们可以看到,如果你想直接对 temp 用 slice() 函数,不可能的,做不到的!因为 arguments 是类数组对象,并非真正的对象,并没有 slice 方法。

但是如果你用下面方法:

[].slice.call(temp); //[“a”, “b”]

我们可以看到的是,用上面的方式,就神奇般的得到了想要的结果。

我们在类数组对象上面,成功的运用了数组的 slice 方法。也就是说,相当于,先将 temp 转为数组,然后再对其运用了 slice 方法,然后返回了我们想要的结果。

当然,这种写法,在 es6 面前,已经成为了过去式,es6 为数组新增了一个 from 方法,这个函数的使用方式如下:

Array.from(temp).slice(); //[“a”, “b”]

感兴趣的同学,可以去 mdn 网站去了解Array.from()
因此,我们可以总结下,原来 context = [].shift.call(arguments) 这一句就是把参数中的第一个剪切出来,赋给 context,那么也就相当于起到了将 参数中的 this 保存的目的。

args = [].slice.call(arguments);

这一句,将除了 this 上下文的所有参数,传给了 args ,以备后来使用。

bind的理解

要读懂返回值,必须得理解,bind 的作用是什么。

The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

有兴趣的同学,可以前往 Function.prototype.bind() 页面查看详细解读。简单点理解,bind 就是用来绑定上下文的,强制将函数的执行环境绑定到目标作用域中去。与 call 和 apply 其实有点类似,但是不同点在于,它不会立即执行,而是返回一个函数。因此我们要想自己实现一个 bind 函数,就必须要返回一个函数,而且这个函数会接收绑定的参数的上下文。

返回函数

return function () { // 返回一个新函数
self.apply(context,[].concat.call(args, [].slice.call(arguments)));
}

这一段其实很好理解,因为bind 绑定了上下文,因此 self.apply 的第一个参数,是之前我们保存的 context。接下来,我们将 bind 的其余参数和调用bind后返回的函数在执行的过程中接收的参数进行拼接,作为一个数组传入apply的第二个参数中去。这就完美的实现了 bind 函数的功能,不得不说很是巧妙。

后记

做业务,其实不太能用上这些技术性很强的东西,但是我们如果止步不前,也许我们当初选行业的时候就选错了。互联网行业发展的速度日新月异,几天就出来了一个新的技术,我们如果不进步,就是在退步。

虽然工作很忙,但是也不能忘了充实自己。先立个 flag 吧,尽量每个星期写一篇技术方面的博文,积少成多,慢慢的,自己也会成为大神的吧。

js中自己实现bind函数的方式相关推荐

  1. main.js中封装全局登录函数

    main.js中封装全局登录函数 1. 在 main.js 中封装全局登录函数 通过 vue 对象的原型扩展,可以扩展一个函数,这样这个函数就可以在 每一个界面通过类似指向对象的方式,去访问这个函数. ...

  2. 前端百题斩【006】——js中三类字符串转数字的方式

    写该系列文章的初衷是"让每位前端工程师掌握高频知识点,为工作助力".这是前端百题斩的第6斩,希望朋友们关注公众号"执鸢者",用知识武装自己的头脑. js中字符串 ...

  3. js中数组的几种循环方式

    js中数组的几种循环方式 for循环最基本的循环方式,不多说.这种最基本的循环才是速度最快的,效率最高的. for(var i = 0;i<5;i++){console.log(i) } for ...

  4. 【温故知新】——原生js中常用的四种循环方式

    一.引言 本文主要是利用一个例子,讲一下原生js中常用的四种循环方式的使用与区别: 实现效果: 在网页中弹出框输入0   网页输出"欢迎下次光临" 在网页中弹出框输入1   网页输 ...

  5. js中(function(){…})()立即执行函数写法理解

    js中(function(){-})()立即执行函数写法理解 javascript和其他编程语言相比比较随意,所以javascript代码中充满各种奇葩的写法,有时雾里看花,当然,能理解各型各色的写法 ...

  6. JS中遍历数组的两种方式

    方式一 for循环 //遍历arr,获取arr中Person对象for(var i=0 ; i<arr.length ; i++){var p = arr[i];//判断Person对象的age ...

  7. js中自己实现bind方法及详解

    1. 详细代码如下: if (!Function.prototype.bind) {Function.prototype.bind = function () {var self = this, // ...

  8. js中避免全局变量冗杂的解决方式

    1.在开发前端当中,经常会定义一些全局变量,想在整个js中的方法都能使用,但是在一个js中定义全局变量后,会出现在其它js冗杂的问题,所以对变量的定义还得注意重名等问题. 解决方式: 在js中定义匿名 ...

  9. js中构造函数与普通函数的区别

    构造函数不仅只出现在JavaScript中,它同样存在于很多主流的程序语言里,比如c++.Java.PHP等等.与这些主流程序语言一样,构造函数在js中的作业一样,也是用来创建对象时初始化对象,并且总 ...

最新文章

  1. vue前端表格插件_Grid.js - 跨框架的前端表格插件
  2. Operations on word vectors-v2 吴恩达老师深度学习课程第五课第二周编程作业1
  3. requests库和BeautifulSoup4库爬取新闻列表
  4. android的读取xml配置文件,android中XMl文件的读取
  5. java 不支持fork,grails不能运行fork模式解决方法
  6. 【机器学习】监督学习--(回归)多项式回归
  7. 代码批量加引号_如何用Word批量制作员工工作证?1分钟搞定1000份!只需三步
  8. 【PostgreSQL-9.6.3】如何得到psql中命令的实际执行SQL
  9. html绘制位图,Canvas 绘制位图图像 ImageBitmap
  10. python小甲鱼课后作业_小甲鱼python课后习题总结
  11. 开源语音助手_开源语音助手软件入门
  12. MOS管和IGBT有什么区别?别傻傻分不清了
  13. Springboot+WebSocket实现匿名聊天室
  14. spdlog日志安装与使用
  15. 磷酸铁锂电池充电过压保护
  16. 数据采集:如何使用物联网边缘计算网关采集数据?
  17. html5网页录音和语音识别
  18. 汽车级三相无传感器、无刷直流(BLDC)电机控制器A4962
  19. mysql 唯一索引出现重复数据_mysql使用唯一索引避免插入重复数据
  20. idea报依赖未找到_idea maven 工程一直报找不到包

热门文章

  1. java 可能尚未初始化变量,java - 局部变量“变量”可能尚未初始化-Java - 堆栈内存溢出...
  2. python代理池好难啊_新人不会自己搭建代理池?快来引用大佬的
  3. php sql语句过滤,php如何做sql过滤
  4. 连接池超时配置_HttpClient连接池的一些思考
  5. 如何使WordPress博客添加多个sidebar侧边栏
  6. Mac 运行 psql postgres 报错
  7. Linux内存卡(SD卡、TF卡)作为Swap交换空间
  8. C++ 面向对象(一)—— 类(Classes)
  9. Exynos4412 文件系统制作(三)—— 文件系统移植
  10. bash shell函数中返回任意值的四种方法