javascript 原生 bind() 的 ES6 + ES5 实现

前言: 本来只是想写一下简单的 bind 函数实现,没想到写着写着还能牵出 js 中继承的知识,其实研究原生函数的实现总是能学到很多新东西

在实现之前呢,我们首先要知道bind是做什么的。JS MDN 给出的定义是The bind() methods 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.,简单来说就是bind()函数创建了一个新函数(原函数的拷贝),这个函数接受一个提供新的this上下文的参数,以及之后任意可选的其他参数。当这个新函数被调用时,它的this关键字指向第一个参数的新上下文。而第二个之后的参数会与原函数的参数组成新参数(原函数的参数在后),传递给函数。

弄清楚这个之后,我们再来分析看看要怎么做。

首先,调用 bind() 会返回一个闭包,这个闭包中创建了一个新函数,这个函数首先包含原函数的属性与方法,并且这个函数的 this 值是传给 bind() 函数第一个参数,所以自然而然我们想到用 call 或者 apply 来改变原函数的 this ,这里我们选择 apply, 理由是我们新函数的第一个之后的参数是由传给 bind() 的第二个及之后的参数(代码中的 formerArgs )再加上原函数的参数(代码中的 laterArgs )构成的,我们把它们拼接成一个数组就完事儿了~具体代码如下:

Function.prototype.bind = function (ctx) {

// 保存原函数的 this 至 _this

var _this = this

// slice 使用了两次,保存到变量中

// slice 主要用于类数组对象 arguments 的浅复制

var slice = Array.prototype.slice

// 传给 bind() 函数的第二个至之后的参数,从  arguments 的第二位开始

var formerArgs = slice.call(arguments, 1)

// bind() 本身就是一个函数,返回

return function (){

// 传给原函数的参数

let laterArgs = slice.call(arguments, 0)

// 返回一个函数,这个函数调用了原函数,并且 this 指向 bind 的第一个参数,

// 第二个参数由 formerArgs  与 laterArgs组成

return _this.apply(ctx, formerArgs.concat(laterArgs))

}

}


ES6 实现

上面的代码是基于 ES5 实现的,当时对于不确定的参数的一般处理方法都是利用类数组对象arguments(其中包含了传递给函数的所有参数),也就免不了使用call或者是apply对其进行数组操作。代码也就显得比较冗长。但是 ES6 不一样了呀~我们有了不定参数这个神器。无论有无参数,有几个参数都可以简单地处理。

不定参数: 传递给函数的最后一个参数可以被标记为不定参数,当函数被调用时,不定参数之前的参数都可正常被填充,剩下的参数会被放进一个数组中,并被赋值给不定参数。而当没有剩下的参数时,不定参数会是一个空数组,而不会被填充为undefined。

同样的功能,只要几行代码就可以实现:

// formerArgs 为传递给 bind 函数的第二个到之后的参数

Function.prototype.bind = function (ctx, ...formerArgs) {

let _this = this

// laterArgs 为传递给原函数的参数

return (...laterArgs) => {

// bind 函数的不定参数在原函数参数之前,formerArgs 本身就是数组,可以直接调用数组的 concat 方法,无需借助 call 或 apply

return _this.apply(ctx, formerArgs.concat(laterArgs))

}

}


至此,我们就实现了简单的 bind() 函数的功能,接下来我们给它做点优化。


优化 UPUPUP..

· 当 Function 的原型链上没有 bind 函数时,才加上此函数

·if (!Function.prototype.bind) {

// add bind() to Function.prototype

}

· 只有函数才能调用 bind 函数,其他的对象不行。即判断 this 是否为函数。

if (typeof this !== 'function') {

// throw NOT_A_FUNCTION error

}

· 压轴戏: 关于继承

我们上面的代码使用了借用 apply 继承的方式。用了 apply 来改变 this 的指向,继承了原函数的基本属性和引用属性,并且保留了可传参优点,但是新函数无法实现函数复用,每个新函数都会复制出一份新的原函数的函数,并且也无法继承到原函数通过 prototype 方式定义的方法或属性。

为解决以上问题,我们选用组合继承方式,在使用 apply 继承的基础上,加上了原型链继承。

所以我们可以这么改。

if(!Function.prototype.binds) {

Function.prototype.binds =function(ctx){

if(typeofthis!=='function') {

thrownewTypeError("NOT_A_FUNCTION -- this is not callable")

}

var_this =this

varslice =Array.prototype.slice

varformerArgs = slice.call(arguments,1)

// 定义一个中间函数,用于作为继承的中间值

varfun =function(){}

varfBound =function(){

letlaterArgs = slice.call(arguments,0)

return_this.apply(ctx, formerArgs.concat(laterArgs))

}

// 先让 fun 的原型方法指向 _this 即原函数的原型方法,继承 _this 的属性

fun.prototype = _this.prototype

// 再将 fBound 即要返回的新函数的原型方法指向 fun 的实例化对象

// 这样,既能让 fBound 继承 _this 的属性,在修改其原型链时,又不会影响到 _this 的原型链

fBound.prototype =newfun()

returnfBound

}

}

在上面的代码中,我们引入了一个新的函数 fun,用于继承原函数的原型,并通过 new 操作符实例化出它的实例对象,供 fBound 的原型继承,至此,我们既让新函数继承了原函数的所有属性与方法,又保证了不会因为其对原型链的操作影响到原函数。用图来表示应该是下面这样的:

这样我们对新函数的 prototype 修改只会应用在它自己身上,而不会影响到原函数。

其他

MDN 中还提到,若是将 bind 绑定之后的函数当作构造函数,通过 new 操作符使用,则不绑定传入的 this,而是将 this 指向实例化出来的对象。所以我们最终的代码为:

if (!Function.prototype.binds) {

Function.prototype.binds = function (ctx) {

if (typeof this !== 'function') {

throw new TypeError("NOT_A_FUNCTION -- this is not callable")

}

var _this = this

var slice = Array.prototype.slice

var formerArgs = slice.call(arguments, 1)

var fun = function () {}

var fBound = function (){

let laterArgs = slice.call(arguments, 0)

// 若通过 new 调用 bind() 之后的函数,则这时候 fBound 的 this 指向的是 fBound 实例,

// 而下面又定义了 fBound 是 fun 的派生类(其 prototype 指向 fun 的实例),

// 所以 this instanceof fun === true ,这时 this 指向了 fBound 实例,不另外绑定!

return _this.apply(this instanceof fun ? this : ctx || this, formerArgs.concat(laterArgs))

}

fun.prototype = _this.prototype

fBound.prototype = new fun()

return fBound

}

}

打完收工~~欧耶 ( •̀ ω •́ )y

P.S: ES7 中已经淘汰了 .bind 的写法,而是使用两个冒号的方式 :: 来代替,(要不要这么可爱!不过呢,人家还只是个提案而已,到时候会不会被采用还不一定的呢

用法有两种,第一种::对象.方法名

letobj = {

val:'value',

method:function(){

console.log(this.val)

}

};

// ::对象.方法名

// 等价于 obj.method.bind(obj)

// 将 method 的 this 绑定为 obj

// 这里的 method 必须是 obj 的方法

::obj.method;

// call

obj.method();// value

第二种,对象::方法名()

letobj = {

val:'value'

};

functionmethod(){

console.log(this.val);

}

// 对象::方法名()

// 等价于 method.call(obj) 或 method.apply(obj)

// 会直接调用并输出 'value'

// obj::method();

// 对象::方法  (无括号

// 等价于 method.bind(obj)

// 不会自动执行,必须赋值给 method 才能实现绑定

// 手动调用 method ,输出 vaue

method = obj::method;

method();

嘛~ 有说的不对的地方,欢迎指正..

文章来源:https://jothy1023.github.io/2016/10/16/js-bind-es5-es6/

作者:四月(转载已经获得作者授权)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容