Giter VIP home page Giter VIP logo

Comments (200)

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024 101

@liuxinqiong 我们来写个 demo 哈:

Function.prototype.bind2 = function (context) {
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        self.apply(this instanceof fBound ? this : context, args.concat(bindArgs));
    }
    fBound.prototype = this.prototype;
    return fBound;
}


function bar() {}

var bindFoo = bar.bind2(null);

bindFoo.prototype.value = 1;

console.log(bar.prototype.value) // 1

你会发现我们明明修改的是 bindFoo.prototype ,但是 bar.prototype 的值也被修改了,这就是因为 fBound.prototype = this.prototype导致的。

from blog.

jawil avatar jawil commented on May 2, 2024 74

V8 源码系列从入门到放弃,卒

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024 71

@fbsstar 是的,Object.create 的模拟实现就是:

Object.create = function( o ) {
    function f(){}
    f.prototype = o;
    return new f;
};

from blog.

fbsstar avatar fbsstar commented on May 2, 2024 30

fNOP.prototype = this.prototype;
fbound.prototype = new fNOP();

是不是就等于fbound.prototype = Object.create(this.prototype);

from blog.

stickmy avatar stickmy commented on May 2, 2024 18

最终版代码应该是

  this instanceof fBound

而不是

  this instanceof fNOP

from blog.

roadwild avatar roadwild commented on May 2, 2024 16
if (typeof Function.prototype.bind1 !== 'function') {
	Function.prototype.bind1 = function(context, ...rest) {
		if (typeof this !== 'function') {
			throw new TypeError('invalid invoked!')
		}
		var self = this
		return function F(...args) {
			if (this instanceof F) {
				return new self(...rest, ...args)
			}
			return self.apply(context, rest.concat(args))
		}
	}
}

前辈,用es6这样写有问题吗?

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024 10

@jiangshanmeta 确实是要返回值的,感谢指出哈,我明明就是仿照着 MDN 实现的,结果还是疏忽了这一点……

关于第二点,使用 new 关键字调用的时候,apply 返回的结果无论是不是对象,都能保证得到一个对象,举个例子:

Function.prototype.bind2 = function (context) {

    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fNOP = function () {};

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

    fNOP.prototype = this.prototype;

    fBound.prototype = new fNOP();

    return fBound;
}


var value = 2;

var foo = {
    value: 1
};

function bar(name, age) {
    this.habit = 'shopping';
    return 2333
}

var bindFoo = bar.bind2(foo, 'jiangshan');

var obj = new bindFoo();

console.log(obj) // {habit: 'shopping'}

这是因为 new 操作符本身就会对构造函数的返回值进行判断,如果是基本类型的值,就按照没有返回值进行处理。

不过 MDN 的整个实现方式有一个问题,就是 new 的时候,实例的原型是指向 fBound.prototype,而不是像原生 bind,指向原函数的 prototype 属性,underscore 的实现中解决了这个问题,让我惊叹的就是 underscore 的实现方式了。

我们看 _.bind 函数的实现:

  var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) {
    if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
   // 使用new关键字调用
    var self = baseCreate(sourceFunc.prototype);
    var result = sourceFunc.apply(self, args);
   // 校验结果是否是对象
    if (_.isObject(result)) return result;
    return self;
  };

  _.bind = restArgs(function(func, context, args) {
    if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
    var bound = restArgs(function(callArgs) {
      return executeBound(func, bound, context, this, args.concat(callArgs));
    });
    return bound;
  });

你会发现,bound 函数是一定有返回值,而且返回值肯定是一个对象,所以 new _.bind 函数的返回函数,即 new bound 时候,得到的对象就是 bound 函数返回的这个对象,所以 underscore 之所以判断 _.isObject 其实就是模拟 new 的底层实现中对构造函数返回值是否是对象的判断,如果是对象,就返回这个对象,如果不是对象,就返回另一个对象,反正 new 的时候,bound 函数肯定会返回一个对象。

说的有点凌乱,还需要再研究一下,感谢指点哈~

此外,我写 underscore 系列的目的是在于讲解如何像 underscore 一样组织多个函数,所以不会系统的讲解 underscore 中的各种方法,总之,多多交流,很期待你的 underscore 系列哈~ ( ̄▽ ̄)~*

from blog.

youzaiyouzai666 avatar youzaiyouzai666 commented on May 2, 2024 9

建议第三版中
fBound.prototype = this.prototype;
修改为:
fBound.prototype = self.prototype;
因为构造函数版本中,个人认为 核心是两个this的理解,如果理解了两个this,那么基本上就没太大的坑了。
再者用es6语法 写demo可读性更强

from blog.

cobish avatar cobish commented on May 2, 2024 9

@ry928330

fNOP.prototype = this.prototype; 就是将 this.prototype 原型对象作为 fNOP.prototype 的原型对象,也就是 this.prototypefNOP.prototype 指向同一个对象。

var f = new fNOP(); 之后找原型链上的属性,就是通过 f.__proto__,

因为 f.__proto__ == fNOP.prototype == this.prototype

就会去 this.prototype 上找属性了。

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024 7

@cobish 并不需要哈~ 我们先看下原生的 bind() 方法的特性:

bind 方法所返回的函数并不包含 prototype 属性,并且将这些绑定的函数用作构造函数所创建的对象从原始的未绑定的构造函数中继承 prototype

这就意味着如果你打印构造函数所创建的对象的 constructor 属性,应该指向未绑定的构造函数,举个例子:

    var foo = { value: 1};
    function bar() {}
    var bindFoo = bar.bind(foo);
    var obj = new bindFoo();
    console.log(obj.constructor);

原生会打印 bar 函数,如果 fBound.prototype.constructor = fBound 的话,就变成了打印 fBound 函数,如果没有这句话,因为 fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); 的缘故,就会指向 bar 函数

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024 6

哈哈,欢迎光临。@jawil

from blog.

baixiaoji avatar baixiaoji commented on May 2, 2024 6

看到写bind最少的代码

// The .bind method from Prototype.js
Function.prototype.bind = function(){
  var fn = this, args = Array.prototype.slice.call(arguments), object = args.shift();
  return function(){
    return fn.apply(object,
      args.concat(Array.prototype.slice.call(arguments)));
  };
};

分享一下

from blog.

jawil avatar jawil commented on May 2, 2024 5

先来个沙发,等会有时间看

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024 5

@Bloss 因为 fBound.prototype = new fNOP() 的缘故,两种写法实现的效果是一致的~

from blog.

Tan90Qian avatar Tan90Qian commented on May 2, 2024 2

发现一个问题:如果将es3版本的prototype部分去除,那么测试代码

var value = 2;

var foo = {
    value: 1
};

function bar(name, age) {
    this.habit = 'shopping';
    console.log(this.value);
    console.log(name);
    console.log(age);
}

bar.prototype.friend = 'kevin';

var bindFoo = bar.bind(foo, 'daisy');

var obj = new bindFoo('18');
// undefined
// daisy
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// kevin

在bind2下console.log(obj.friend);会输出undefined,因此个人认为es3下的最终版应该参考call的polyfill实现方法,改成如下形式:

Function.prototype.bind2 = function(context) {
    if (typeof this !== 'function') {
        throw new Error('Function.prototype.bind - what is trying to be bound is not callable');
    }

    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fBound = function() {
        var bindArgs = Array.prototype.slice.call(arguments);
        if (this instanceof fBound) {
            var args2 = [];
            for(var i = 0, len = args.length; i < len; i++) {
                args2.push('args[' + i + ']');
            }
            for(var j = 0, len = bindArgs.length; j < len; j++) {
                args2.push('bindArgs[' + j + ']');
            }
            return eval('new self('+ args2 +')');
        } else {
            return self.apply(context, args.concat(bindArgs));
        }
    };
    return fBound;
};

from blog.

mengsixing avatar mengsixing commented on May 2, 2024 2

@mqyqingfeng 大佬好,为什么这段代码执行会报错呢?

Function.prototype.bind2 = function(context) {
        if (typeof this !== 'function') {
          throw new Error(
            'Function.prototype.bind - what is trying to be bound is not callable'
          );
        }

        var self = this;
        var args = Array.prototype.slice.call(arguments, 1);

        var fNOP = function() {};

        var fBound = function() {
          var bindArgs = Array.prototype.slice.call(arguments);
          return self.apply(
            this instanceof fNOP ? this : context,
            args.concat(bindArgs)
          );
        };

        fNOP.prototype = this.prototype;
        fBound.prototype = new fNOP();
        return fBound;
      };


// 上面是拷贝的最终版代码,下面是测试案例

      var o = {
        val: 123,
        getValue() {
          return this.val;
        }
      };
      var b = {
        val: 456,
      };
      var getValue = o.getValue;
      var myBind = getValue.bind2(b);
      console.log(myBind());

执行效果如下:
image

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024 1

对第三版模拟实现代码进行了优化。以前是

this instanceof self ? this : context

现在改成了

this instanceof fBound ? this : context

因为 fNOP.prototype = this.prototype的缘故,两段代码在效果上并没有区别,但是个人觉得改成 fBound 会更好理解, 而且 MDN 也是采用的 fBound 。

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024 1

@baixiaoji 感谢分享哈~

不过这段代码并没有完整的实现 bind 的特性,比如 "当 bind 返回的函数作为构造函数的时候,bind 时指定的 this 值会失效"

var value = 2;

var foo = {
    value: 1
};

function bar(name, age) {
    console.log(this.value);
}

var bindFoo = bar.bind(foo);

var obj = new bindFoo('18');

使用原生的 bind 就会返回 undefined,使用这段代码的话,就会返回 1

from blog.

RayJune avatar RayJune commented on May 2, 2024 1

@mqyqingfeng 有匪君子,如切如磋,如琢如磨。😄

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024 1

@jimmylinzl

var value = 2;
var foo = {
    value: 1,
    bar: bar.bind(null)
};

function bar() {
    console.log(this.value);
}

foo.bar() // 2

如果用的是 context || this,这段代码会打印 1, 原生的实现中应该打印 2,导致这个差异的原因就是在 context 不存在的时候使用了 this

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024 1

@hileix 这段代码首先是规范中规定的:

default

其次,常见的情景下确实不会出现调用者不是函数的情况,但是也不能保证不出现一些极端的例子,比如我声明一个对象,继承了 bind 函数,然后调用 bind,这个时候就应该报错:

var obj = {};
obj.__proto__ = Function.prototype;
obj.bind() // 报错

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024 1

@hjscript 结果是 undefined 才是正确的呀……

default

from blog.

YagamiNewLight avatar YagamiNewLight commented on May 2, 2024 1
Function.prototype.bind2 = function (context) {
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        self.apply(this instanceof fBound ? this : context, args.concat(bindArgs));
    }
    fBound.prototype = this.prototype;
    return fBound;
}


function bar() {}

var bindFoo = bar.bind2(null);

bindFoo.prototype.value = 1;

console.log(bar.prototype.value) // 1

这段代码没看懂,为什么bar.prototytype.value会有值呀?

因为 fBound.prototype = this.prototype 这里已经把bar的prototype和bindFoo的prototype绑到一起了

from blog.

sourceplace avatar sourceplace commented on May 2, 2024 1

if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}

上面的在您的写法里是不是多余的?
因为Function.prototype.bind2中bind2是在函数构造函数prototype上,非函数是调不到这个方法的,除非不把bind2放在Function.prototype上

from blog.

littlewin-wang avatar littlewin-wang commented on May 2, 2024 1

@yhlben

var o = {
  val: 123,
  getValue () {
    return this.val;
  }
};

这种写法里等同于

var o = {
  val: 123,
  getValue: () => {
    return this.val;
  }
};

箭头函数没有实例化,原型对象这些东西...
换成普通函数一切正常

var o = {
  val: 123,
  getValue: function () {
    return this.val;
  }
};
var b = {
  val: 456,
};
var getValue = o.getValue;
var myBind = getValue.bind2(b);
console.log(myBind());  // 456

from blog.

JuniorTour avatar JuniorTour commented on May 2, 2024

前辈,好像有一个typo。
模拟构造函数效果里的代码,有一个hobbit-霍比特人属性,应该是habit-习惯吧?#笑哭

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

哈哈,确实是写错了,本来是想写habit,没有想到hobbit写的太顺手,我竟然没有任何违和的感觉……感谢指出哈~

from blog.

enjkvbej avatar enjkvbej commented on May 2, 2024

我把最后的实现代码跑了一下构造函数的例子 发现this依然失效了啊 是什么问题呢

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@enjkvbej 作为构造函数时,this 就是会失效呐

from blog.

jawil avatar jawil commented on May 2, 2024

😄

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@jawil 说起来,博主的 V8 源码系列写得怎么样了?很好奇第一篇会讲什么?

from blog.

caiyongmin avatar caiyongmin commented on May 2, 2024

为什么要设置 fBound.prototype = this.prototype,只是为了继承一下绑定函数的原型对象中的属性吗?

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@caiyongmin 为了让 fBound 构造的实例能够继承绑定函数的原型中的值

from blog.

caiyongmin avatar caiyongmin commented on May 2, 2024

我的意思是,为什么要继承?

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@caiyongmin 因为原生的 bind 的效果就是这样呐

from blog.

liuxinqiong avatar liuxinqiong commented on May 2, 2024

您好,我们直接将 fBound.prototype = this.prototype,我们直接修改 fBound.prototype 的时候,也会直接修改绑定函数的 prototype。这里有点不太懂诶,能多讲解一下吗,谢谢

from blog.

liuxinqiong avatar liuxinqiong commented on May 2, 2024

@mqyqingfeng 万分感谢,点破之后,对之前的知识都有了新的认识!已经第二次看了,每次都有收获!

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@liuxinqiong 哈哈,感谢肯定~ 加油哈~

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@youzaiyouzai666 感谢指出,改成 self 能避免理解混乱,确实更好一些~ 关于 es6 的写法,我给自己的要求是在没写 ES6 系列之前,尽量保持 ES5 的写法,这是希望看这个系列的初学者们不要有额外的学习成本

from blog.

cobish avatar cobish commented on May 2, 2024

有个疑惑,最终代码中不需要将 fBound 的 constructor 给指回来吗?

fBound.prototype = new fNOP();

即:

fBound.prototype.constructor = fBound;

from blog.

stickmy avatar stickmy commented on May 2, 2024

@mqyqingfeng 是这样的= =,谢谢博主

from blog.

cobish avatar cobish commented on May 2, 2024

@mqyqingfeng 谢谢解惑,万分感谢~

from blog.

ry928330 avatar ry928330 commented on May 2, 2024

@mqyqingfeng 这种形式fNOP.prototype = this.prototype,原型链上会发生什么呢,是将this上的prototype拷贝给了fNOP.prototype么,所以现在是this.prototype上有什么fNOP.prototype上就有什么是吧,还有什么别的引申的作用么

from blog.

ry928330 avatar ry928330 commented on May 2, 2024

@cobish 嗯嗯,你说的这个意思我理解了,多谢~

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@cobish 感谢回答哈~

from blog.

jiangshanmeta avatar jiangshanmeta commented on May 2, 2024

有一点小小的问题,fBound 方法是有返回值的,要把apply的结果返回出来。
MDN

即使是这样还有一个问题,就是如果使用new关键字调用,我们最终希望得到的是一个对象,但是apply返回的结果不见得能保证这一点。

在underscore的实现中就考虑到了这一点:

  var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) {
    if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
   // 使用new关键字调用
    var self = baseCreate(sourceFunc.prototype);
    var result = sourceFunc.apply(self, args);
   // 校验结果是否是对象
    if (_.isObject(result)) return result;
    return self;
  };

看见博主准备写underscore系列,希望能有帮助。// 其实我也在写蛤

from blog.

paul-wj avatar paul-wj commented on May 2, 2024

self.apply(this.constructor === fNOP ? this : context, args.concat(bindArgs));
这样是不是更好一点?

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@wj5576081 使用 constructor 也可以啦,不过 constructor 一般认为是容易被修改和丢失的,所以开发者更倾向于不使用 constrcutor 进行判断

from blog.

paul-wj avatar paul-wj commented on May 2, 2024

@mqyqingfeng 谢谢回答,受教了~

from blog.

jasonzhangdong avatar jasonzhangdong commented on May 2, 2024

带着疑问去研究包括评论内容,会收获不少。我基本是一天一片文章。

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@jasonzhangdong 那今天就该是第 12 天了,与你共勉~ 欢迎交流哈~

from blog.

jingaier avatar jingaier commented on May 2, 2024

楼主,
var args = Array.prototype.slice.call(arguments, 1);
var bindArgs = Array.prototype.slice.call(arguments);
不知道这样写什么意思
看到这篇就和看到讲this那篇一样,一脸懵逼

from blog.

RayJune avatar RayJune commented on May 2, 2024

@mqyqingfeng 感谢博主的分享,讲解的很详细,非常有帮助。

有个小建议,博主可以把对代码做过的修改都更新到第一条 comment 上,比如第一条的所有 apply 都没有加上 return,这样防止只看第一条 comment 而没有看其他评论的同学理解错误 :)

from blog.

RayJune avatar RayJune commented on May 2, 2024

@jingaier

替博主冒一下泡:

var args = Array.prototype.slice.call(arguments, 1); 
// 利用 function.call() 对 arguments 这个类数组元素调用数组的方法 array.slice(),
// 得到除了 arguments[0] 的所有 arguments 元素组成的数组
var bindArgs = Array.prototype.slice.call(arguments); 
// 转换类数组元素 arguments 为数组元素,方便使用数组方法,比如后面的 array.concat()

from blog.

RayJune avatar RayJune commented on May 2, 2024

@mqyqingfeng 博主真的有必要改一下第一个 comment 里的代码,看到有的同学照搬有错误的代码了(感到很可惜) https://pororoj.github.io/2017/09/20/js%E4%B9%8Bcall%E3%80%81apply%E3%80%81bind%E7%9A%84%E6%A8%A1%E6%8B%9F%E5%AE%9E%E7%8E%B0/#bind的模拟实现

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@RayJune 感谢冒泡哈~ @jingaier 关于这个方法,这篇文章 JavaScript深入之类数组对象与arguments 中有所涉及

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@RayJune 非常好的建议,非常感谢,以后每篇文章,我都会将第一个 comment 作为更新的日志

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@RayJune 我已经对这篇文章进行了修改,Pororo 同学的博文由于找不到留言的地方,我在他的 Github 上开了一个 issue 说明了情况,具体地址是 alicejxr/pororoJ.github.io#1,指出这个问题,不胜感激~

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@jiangshanmeta 很感谢你指出 return 这个问题,可惜我当时看着看着研究 underscore 的 bind 源码去了,结果忘了改了……😂 不过还好在 @RayJune 同学的提醒下,现在已经做了更改~

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@RayJune 很有文采,佩服佩服~ o( ̄▽ ̄)d

from blog.

Idealv avatar Idealv commented on May 2, 2024

这里bind的实现应该类似柯里化,先传递一部分参数,返回一个函数处理剩下的参数。

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@IdealVillage 我觉得用柯里化表示不准确,偏函数或者局部应用更加准确些

default

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@sinkinlife 非常赞,还没有发现什么问题,我觉得这是更优化和易懂的实现~ o( ̄▽ ̄)d

from blog.

roadwild avatar roadwild commented on May 2, 2024

@mqyqingfeng 谢谢指点~o( ̄▽ ̄)d

from blog.

ClarenceC avatar ClarenceC commented on May 2, 2024

看懂算是理解了,又好像有点不理解,回头过来再看一次.

from blog.

SunXinFei avatar SunXinFei commented on May 2, 2024

根据《JavaScript深入之call和apply的模拟实现》里面的实现,我们在bind里面如果这么写会有什么问题
// 第一版如果改成这样

Function.prototype.bind2 = function (context) {
    var context = context || window;
    context.fn = this;
    return function () {
        context.fn();
        delete context.fn;
    }
}

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@SunXinFei 我觉得可以再 return 一下 context.fn 的返回值:

Function.prototype.bind2 = function (context) {
    var context = context || window;
    context.fn = this;
    return function () {
        var res = context.fn();
        delete context.fn;
        return res
    }
}

var foo = {
    value: 1
};

function bar() {
	return this.value;
}

var bindFoo = bar.bind2(foo);

console.log(bindFoo()); // 1

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@SunXinFei 哈哈,当时我就是看到并回答了那个问题,才萌生了写 call、apply 实现方式的想法的~

from blog.

SunXinFei avatar SunXinFei commented on May 2, 2024

注意:尽管在全局和 foo 中都声明了 value 值,最后依然返回了 undefind,说明绑定的 this 失效了,如果大家了解 new 的模拟实现, 就会知道这个时候的 this 已经指向了 obj。

我认为加粗的这句话表述不是很好,根据《JS高级编程》里面的描述,new的时候一共做了四件事,

  1. 创建一个新的对象
  2. 把this指向该新对象
  3. 执行构造函数的代码
  4. 返回新对象

所以我认为严谨表述应该为就会知道这个时候的 this 已经指向了新对象,不然会有这样的歧义:

function bar(name, age) {
    this.habit = 'shopping';
    console.log(this.aa);//注意这里this是新对象
}

var obj ={
    aa:1
};
obj = new bar('18');

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@SunXinFei 因为在例子中,obj 是通过 var obj = new bindFoo('18'); 这种方式产生的,所谓的新对象指的还是 obj 吧……

from blog.

mumiao avatar mumiao commented on May 2, 2024

self.apply(this instanceof self ? this : context || this, args.concat(bindArgs))对于这个有个疑问,希望解惑,根据博主的例子,如果上下文是null的话,this应该指的就是window对象不是吗,所以context不存在的话用this有什么问题吗

from blog.

lizhongzhen11 avatar lizhongzhen11 commented on May 2, 2024

牛逼牛逼,打卡

from blog.

hileix avatar hileix commented on May 2, 2024
if (typeof this !== "function") {
  throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}

这一句应该没有必要吧?

Function.prototype.bind = Function.prototype.bind || function () {
    ……
};

最后,你将模拟的 bind 函数放在 Function.prototype 下。那么调用 bind() 函数的对象,就应该是一个函数。那么 模拟的 bind() 函数里面,应该就不需要判断 this 是否为函数吧?

from blog.

hsroad avatar hsroad commented on May 2, 2024

楼主,写的很好 照着写了一遍发现依然拿不到vaule的值,尴尬了,求解?

from blog.

hsroad avatar hsroad commented on May 2, 2024

代码如下:

Function.prototype.bind2 = function (context) {

    var self = this
    var args = Array.prototype.slice.call(arguments, 1)

    var fBound = function () {
      var bindArgs = Array.prototype.slice.call(arguments)
      console.log('this=>', this)
      return self.apply(this instanceof fBound ? this : context, args.concat(bindArgs))
    }

    fBound.prototype = this.prototype
    console.log('this.prototype=>', this.prototype)
    return fBound
  }

  var value = 2

  var foo = {
    value: 1
  }

  function bar (name, age) {
    this.habit = 'shopping'
    console.log(this.value)  // 还是为undefined
    console.log(name)
    console.log(age)
  }

  bar.prototype.friend = 'kevin'

  var bindFoo = bar.bind2(foo, 'daisy')

  var obj = new bindFoo('18')
  console.log(obj.habit)
  console.log(obj.friend)

from blog.

hsroad avatar hsroad commented on May 2, 2024

我又了看了一遍,貌似明白了 谢谢楼主!

from blog.

archiewx avatar archiewx commented on May 2, 2024

image
这个zhong'wen'ba中文版已经修正了。

if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if (typeof this !== 'function') {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }

    var aArgs   = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          return fToBind.apply(this instanceof fNOP
                 ? this
                 : oThis,
                 // 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    // 维护原型关系
    if (this.prototype) {
      // Function.prototype doesn't have a prototype property
      fNOP.prototype = this.prototype; 
    }
    fBound.prototype = new fNOP();

    return fBound;
  };
}

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@zsirfs 恩恩,查了下中文版的内容 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind 感谢指出~ o( ̄▽ ̄)d

from blog.

baixiaoji avatar baixiaoji commented on May 2, 2024

2.调用 bind 的不是函数咋办?

其实不太明白,一个绑在函数原型链上函数,怎样做才能让他的调用方不是一个函数呢?


啊啊,傻掉了,对象一个属性执行这个函数在调用就成了。

from blog.

YanLIU0822 avatar YanLIU0822 commented on May 2, 2024

self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs))
self.apply(context, args.concat(bindArgs))
lz 您好 我有几点不明白

  1. 这两句好像执行结果都是一样的
  2. 为什么判断是fNOP? 不是 new fBound() 为什么不是判断 this instanceof fBound?

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@YanLIU0822 关于第一个问题,这两句在普通的例子中结果是相同的,只有当 bind 返回的函数作为构造函数时才会出现结果上的不同,你可以以这个例子进行测试:

    var foo = {
        value: 1
    };

    function bar(name, age) {
        console.log(this.value);
    }

    var bindFoo = bar.bind2(foo);

    var obj = new bindFoo();

修改 bind2 函数这里的实现,打印的结果会有所不同。

关于第二个问题,这两种其实都可以,我只是用了 MDN 中的写法而已~

from blog.

Tan90Qian avatar Tan90Qian commented on May 2, 2024
function bar() {}

var bindFoo = bar.bind2(null);

bindFoo.prototype.value = 1;

console.log(bar.prototype.value) // 

你会发现我们明明修改的是 bindFoo.prototype ,但是 bar.prototype 的值也被修改了,这就是因为 fBound.prototype = this.prototype导致的。

关于 @mqyqingfeng 冴羽大大在 @liuxinqiong 时写的demo,有一个严重问题
冴羽大大 回复@cobish 时,是这样写的
并不需要哈~ 我们先看下原生的 bind() 方法的特性: bind 方法所返回的函数并不包含 prototype 属性,并且将这些绑定的函数用作构造函数所创建的对象从原始的未绑定的构造函数中继承 prototype

也就是说,bindFoo其实是不应该有prototype属性的,使用原生的bind方法时,bindFoo.prototype.value = 1;该语句会抛出异常Uncaught TypeError: Cannot set property 'value' of undefined

所以和原生bind方法一致的polyfill代码应该不包括prototype的部分 也就是:

Function.prototype.bind2 = function(context) {
    if (typeof this !== 'function') {
        throw new Error('Function.prototype.bind - what is trying to be bound is not callable');
    }

    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fBound = function() {
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fBound ? this : context, args.concat(bindArgs));
    };
    return fBound;
};

同时, @sinkinlife 的es6实现方法可以做一些补充

if (typeof Function.prototype.bind1 !== 'function') {
    Function.prototype.bind1 = function(context, ...rest) {
        if (typeof this !== 'function') {
            throw new TypeError('invalid invoked!');
        }
        var self = this;
        // 不直接返回函数F
        function F(...args) {
            if (this instanceof F) {
                return new self(...rest, ...args);
            }
            return self.apply(context, rest.concat(args));
        };
        // 删除F的prototype属性
        delete F.prototype;
        return F;
    };
}

这样 当测试代码为

var foo={
	value: 1
};

function bar(name) {}

var bindFoo = bar.bind(foo, 'daisy');
var obj = new bindFoo();

console.log('prototype:', bindFoo.prototype);
console.log('__proto__:', obj.__proto__);

时,原生的bind、es3下的bind2和es6下的bind1,都有一致的表现了。不过其他情况下的表现结果还没有进行过测试

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@Tan90Qian 哈哈,(๑•̀ㅂ•́)و✧,被你发现了,MDN 提供的实现方法其实并不是完美模拟的,其中 bind 方法所返回的函数并不包含 prototype 属性,这点就没有模拟实现,可能是因为实现起来还是有点繁琐吧,关于更好的实现,可以参考 ES5-shim,Lucas 也写过两篇文章:

  1. 从一道面试题的进阶,到“我可能看了假源码”
  2. 从一道面试题的进阶,到“我可能看了假源码”(2)

还有 Demo 确实写得有问题呀,感谢指出和分享,我去想想为啥要用个中转函数……

from blog.

ttsy avatar ttsy commented on May 2, 2024

你好,关于「之所以 return self.apply(context),是考虑到绑定函数可能是有返回值的」这里好像不使用 return 效果也是一样的?我根据您的例子去掉 return 后结果还是一样的。

from blog.

cobish avatar cobish commented on May 2, 2024

@ttsy

看文中的例子:

function bar () {
  return this.value;
}

var bindFoo = bar.bind(foo);
console.log(bindFoo()); // 如果没有 return,这里就不会打印出 1

bindFoo 就是返回的那个函数(写成这样是不是易懂一点):

return function () {
  var val = self.apply(context);
  return val;
}

如果不把 val 返回,那最后就取不到 bindFoo 的返回值了。

from blog.

ttsy avatar ttsy commented on May 2, 2024

@cobish 但是不 return 的话,直接写 self.apply(context) 也可以得到相同的效果吧?

from blog.

cobish avatar cobish commented on May 2, 2024

@ttsy 没有 return 的话 bindFoo 的值为 undefined,你可以打印试试。

需要注意的是,这里作者例子中第 9 行用的是 bind(原生的),所以打印出 1。如果是没有 return 的 bind2,打印是就是 undefined 了。

var foo = {
    value: 1
};

function bar() {
    return this.value;
}

var bindFoo = bar.bind(foo);

console.log(bindFoo()); // 1

from blog.

ttsy avatar ttsy commented on May 2, 2024

@cobish 明白,感谢指教。

from blog.

vbty avatar vbty commented on May 2, 2024

tql

from blog.

YagamiNewLight avatar YagamiNewLight commented on May 2, 2024

发现了一个问题。那就是调用bind函数得到的函数的参数个数问题。
参考如下代码

function getFnLength(fn){ // 获取传入函数的参数个数
   return fn.length
} 
function add(a,b){
  return a+b
}
getFnLength(add) //2
getFnLength(add.bind(null,1)) // 1
getFnLength(add.bind(null,1,2)) // 0

但是如果使用大佬你自己写的bind2函数

getFnLength(add) //2
getFnLength(add.bind2(null,1)) // 0
getFnLength(add.bind2(null,1,2)) // 0

这里的参数就变成了0. 当然我知道这是由于...args不定参数导致的
关于这一点不同我是在自己实现柯里化的时候发现的,我的写法很普通的柯里化实现有点不太一样,但是确实是有效的:

function curry(fn){
  let len = fn.length
  return function curriedFn(...args){
    let subLen = args.length
    if(subLen >= len){
      return fn.apply(this,args)
    }else{
         return curry(fn.bind(null,...args))  //这里实现递归调用柯里化函数的时候需要知道剩余参数的个数。
    }
  }
}

在我这种柯里化的实现中,我发现bind后返回的函数的参数是根据bind传进去的参数个数决定的。不然我的这个curry化也不会起作用。
所以大佬你写的那个bind函数可能还要考虑返回后函数的参数的问题。
我后来想了一下这种知道剩余参数个数的可能的bind实现方式:

Function.prototype.myBind = function(that,...args){
  let self = this
  let restArgs = Array.from({length:self.length - args.length}).map((_,index) => 'a' + index)
  let f = `function binded(${restArgs.toString()}) {
    return self.call(that,...args,${restArgs.toString()})
  }`
  eval(f)
  return binded
}

当然实现很丑陋,而且bind的功能也不全,这这里仅供参考。

不好意思,没有仔细看评论。原来这个问题已经在
1.从一道面试题的进阶,到“我可能看了假源码”
2.从一道面试题的进阶,到“我可能看了假源码”(2)
里面讨论过了。而且是用Function构造函数实现的。

from blog.

dreamsline avatar dreamsline commented on May 2, 2024
Function.prototype.bind2 = function (context) {
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        self.apply(this instanceof fBound ? this : context, args.concat(bindArgs));
    }
    fBound.prototype = this.prototype;
    return fBound;
}


function bar() {}

var bindFoo = bar.bind2(null);

bindFoo.prototype.value = 1;

console.log(bar.prototype.value) // 1

这段代码没看懂,为什么bar.prototytype.value会有值呀?

from blog.

dreamsline avatar dreamsline commented on May 2, 2024
Function.prototype.bind2 = function (context) {
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        self.apply(this instanceof fBound ? this : context, args.concat(bindArgs));
    }
    fBound.prototype = this.prototype;
    return fBound;
}


function bar() {}

var bindFoo = bar.bind2(null);

bindFoo.prototype.value = 1;

console.log(bar.prototype.value) // 1

这段代码没看懂,为什么bar.prototytype.value会有值呀?

因为 fBound.prototype = this.prototype 这里已经把bar的prototype和bindFoo的prototype绑到一起了

嗯嗯,谢谢,突然懂了。

from blog.

coolpail avatar coolpail commented on May 2, 2024
function f() {
  this.c = 1
}
let fn = f.bind({a:1})
fn.prototype   //undefined

好像与模拟的有出入啊
按照模拟函数来看应该是:

{
__proto__:绑定函数的prototype
}

from blog.

zooeyking avatar zooeyking commented on May 2, 2024

最后的代码一脸懵逼了,求解释 this instanceof fBound this到底是什么?
image

因为代码执行顺序有点闹不明白了,特意加了debugger
Function.prototype.bind2 = function (context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);

var fBound = function hahaha() {
	
    var bindArgs = Array.prototype.slice.call(arguments);
	console.log(this, 777, this.name, this.prototype)
    return self.apply(this instanceof fBound ? (console.log(this),this) : context, args.concat(bindArgs));
}
fBound.prototype = this.prototype;
return fBound;

}
var value = 2;

var foo = {
value: 1
};
function bar(name, age) {
this.habit = 'shopping';
console.log(this.value);
console.log(name);
console.log(age);
}
debugger
bar.prototype.friend = 'kevin';
var bindFoo = bar.bind2(foo, 'daisy');
var obj = new bindFoo('18');
console.log(obj.habit);
console.log(obj.friend);

from blog.

venoral avatar venoral commented on May 2, 2024

对第三版模拟实现代码进行了优化。以前是

this instanceof self ? this : context

现在改成了

this instanceof fBound ? this : context

因为 fNOP.prototype = this.prototype的缘故,两段代码在效果上并没有区别,但是个人觉得改成 fBound 会更好理解, 而且 MDN 也是采用的 fBound 。

是因为版本三中fBound.prototype = this.prototype; (这个this,其实就是第一行的self,属于同一个上下文,个人觉得self比较好理解),this instanceof self表示了this -> fBound.prototype -> self.prototype的继承关系,所以可以直接写成this instanceof fBound

from blog.

lazybonee avatar lazybonee commented on May 2, 2024

@Tan90Qian 我试了一下您的代码,最后进行了delete F.prototype。但是实际上生成的函数还是有原型对象存在的。

from blog.

TStoneLee avatar TStoneLee commented on May 2, 2024

这个bind的知识点好多鸭, 尤其是返回的函数当做构造函数的实现,很蒙,回头再多读几遍,多谢大神的文章

from blog.

SageWu avatar SageWu commented on May 2, 2024

这样的话,bind返回的函数的prototype属性为undefined
以及以返回的函数为构造函数生成的对象的__proto__是原函数prototype属性所指向的对象了。

Function.prototype.bind = function (context) {
    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - No function type");
    }

    var self = this;
    var bind_args = Array.prototype.slice.call(arguments, 1);
    var bind_fn = function () {
        var args = Array.prototype.slice.call(arguments);
        //当构造函数的prototype属性为undefined时,生成的对象__proto__指向Object.prototype
        if(this.constructor.name === "Object") {
            var arg_strs = [];
            for(var i = 0; i < bind_args.length; i++) {
                arg_strs.push("bind_args[" + i + "]");
            }
            for(var i = 0; i < args.length; i++) {
                arg_strs.push("args[" + i + "]");
            }

            return eval("new self(" + arg_strs + ")");
        }
        else {
            return self.apply(context, bind_args.concat(args));
        }
    }
    bind_fn.prototype = undefined;

    return bind_fn;
}

//es6版本
Function.prototype.bind = function (context, ...bind_args) {
    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - No function type");
    }

    var self = this;
    var bind_fn = function (...args) {
        if(this.constructor.name === "Object")
            return new self(...bind_args, ...args);
        else
            return self.apply(context, bind_args.concat(args));
    }
    bind_fn.prototype = undefined;

    return bind_fn;
}

from blog.

ChengZhao100 avatar ChengZhao100 commented on May 2, 2024

@mqyqingfeng 大佬好,为什么这段代码执行会报错呢?

Function.prototype.bind2 = function(context) {
        if (typeof this !== 'function') {
          throw new Error(
            'Function.prototype.bind - what is trying to be bound is not callable'
          );
        }

        var self = this;
        var args = Array.prototype.slice.call(arguments, 1);

        var fNOP = function() {};

        var fBound = function() {
          var bindArgs = Array.prototype.slice.call(arguments);
          return self.apply(
            this instanceof fNOP ? this : context,
            args.concat(bindArgs)
          );
        };

        fNOP.prototype = this.prototype;
        fBound.prototype = new fNOP();
        return fBound;
      };


// 上面是拷贝的最终版代码,下面是测试案例

      var o = {
        val: 123,
        getValue() {
          return this.val;
        }
      };
      var b = {
        val: 456,
      };
      var getValue = o.getValue;
      var myBind = getValue.bind2(b);
      console.log(myBind());

执行效果如下:
image

myBind中的prototype就是getValue的prototype,其实就是o。o作为一个普通对象没有prototype属性,所以会有这个报错。

from blog.

mengsixing avatar mengsixing commented on May 2, 2024

@littlewin-wang 应该不是这个问题,方法的简写并不是相当于箭头函数。
可以参考软老师的es6教程:
image

from blog.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.