bind()函数的深入理解及两种兼容方法分析
在JavaScript中,bind()函数仅在IE9+、Firefox4+、Chrome、Safari5.1+可得到原生支持。本文将深入探讨bind()函数并对两种兼容方法进行分析比较。由于本文将反复使用用到原型对象、原型、prototype、[[proto]],为使文章更加易读不致引起混淆,这里将对几者进行明确区分:
1、
原型
:每个函数本身也是一个对象,作为对象的函数拥有一个属性叫做原型,它是一个指针。
2、原型对象
:函数的原型(是一个指针)指向一个对象,这个对象便是原型对象。
3、prototype
:函数的prototype属性就是函数的原型(指针)。
4、[[proto]]
:实例拥有一个内部指针[[prototype]]指向原型对象,实例的原型也就是指实例的[[prototype]]属性。
5、当叙述原型的方法和原型对象的方法时,两者是同一个意思;
6、可以通过对象形式操作原型。
F.prototype.bind()
这样的写法表明F.prototype
虽然本质上是一个指针,但可以使用对象的.
这样的操作符,就好像F.prototype本来就是一个对象一样,实质上是通过指针访问了原型对象。
一、bind()方法从何而来?
第一个问题是,每个函数都可以使用bind函数,那么它究竟从何而来?
事实上,bind()来自函数的原型链,它是Function构造函数的原型对象上的一个方法,基于前面的区分,可以通过Function.prototype访问即Function构造函数的原型对象:
Function.prototype.bind()
由于每个函数都是Function构造函数的实例,因此会继承Function的原型对象的属性和方法。
第二个问题是,每个函数都有的方法一定是从原型链继承而来吗?
答案是否定的,因为每个函数都有call()和apply()方法,但call()和apply()却不是继承而来。`
二、与call()、apply()的区别
call()、apply()可以改变函数运行时的执行环境,foo.call()
、foo.apply()
这样的语句可以看作执行foo(),只不过foo()中的this指向了后面的第一个参数。
foo.bind({a:1})
却并不如此,执行该条语句仅仅得到了一个新的函数,新函数的this被绑定到了后面的第一个参数,亦即新的函数并没有执行。
function foo(){
return this;
}
var f1=foo.call({a:1});
var f2=foo.apply({a:2});
var f3=foo.bind({a:1});
console.log(f1); //{a:1}
console.log(f2); //{a:2}
console.log(f3); //function foo(){
// return this;
//}
console.log(foo()); //window对象
console.log(f3()); //{a: 1}
在上面的例子中,f1和f2都得到改变了执行环境的foo()函数运行后的返回值。f3得到的是另一个函数,函数体本身和foo()是一样的,但执行f3()却和执行foo()得到不同的结果,这是因为bind()函数使得f3中this绑定到一个特定的对象。
三、多个参数
例如:
var obj={
a:1
};
function foo(a,b){
this.a++;
return a+b;
}
var fo=foo.bind(obj,1,2);
console.log(fo()); //3
console.log(obj); //{a:2}
以上例子中,当执行foo()函数,将使得this指向的对象的a属性自加1,对于foo()函数而言,它的this指向window对象,也就是将使得window环境中的a变量自加1,然后同时a+b的值。
fo()函数则是由foo()调用bind()并传入三个参数onj、1和2得到的新函数,该函数的this指向传入的obj对象。当执行fo()函数,将使得obj的a属性自加1,然后返回bind()的后两个参数相加的结果。
四、 兼容方法1:使用apply——简洁的实现
Function.prototype.bind= function(obj){
if (Function.prototype.bind)
return Function.prototype.bind;
var _self = this, args = arguments;
return function() {
_self.apply(obj, Array.prototype.slice.call(args, 1));
}
}
分析:
首先,从总体结构而言,bind()是一个函数,故采用function定义。由于foo.bind()得到的仍然是一个函数,因而返回值是一个函数。
第二,在返回的函数中,需要执行一次改变了执行环境的原函数,使用apply(obj)达到将原函数的执行环境改为obj的目的。
第三,对于bind()函数而言,由于它是Function.prototype的一个属性,它的this将指向调用它的对象。例如,foo.bind(obj),则bind()函数内部的this指向foo()函数。但对于执行bind()后得到的新函数,它的this将指向全局对象,因此需要使用var _self = this
这样的参数传递。
第四,调用bind()得到的新函数需要接收执行bind()时传入的实际参数。因此,使用了args = arguments这样的赋值。需要将执行bind()时传入的参数进行分离,只获取第一个参数后面的参数,slice()方法可以达到这个目的。又由于arguments是类数组对象不是真正的数组,故而没有slice方法,使用call()以达到借用的目的。
最终,参见下例梳理如下:
var func=foo.bind(obj,...);
bind是Function构造函数的prototype指针指向的对象上的一个方法。当某个函数foo()调用它时,即foo.bind()
,将返回一个新的函数。当新的函数执行时,相当于执行一次foo()函数本身,只不过改变了foo()的执行环境为传入的obj,this也指向了传入的obj,传入bind的第一个实参以后的参数作为新函数执行的实际参数。
五、兼容方法2: 基于原型——MDN方法
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;
};
}
分析:
首先,检测Function的原型对象上是否存在bind()方法,若不存在则赋值为一个函数。在函数内部,对调用bind()的对象类型进行了检测,如果是函数,则正常调用,否则抛出异常;
var foo={
a:1
};
var o={
b:1;
}
var f=foo.bind(o);
这就是说上面的调用是不被允许的,因为foo不是函数。
第二,fBound是最终得到的函数。 aArgs得到调用fBound时传入的第一个参数后面的参数,aArgs.concat(Array.prototype.slice.call(arguments)))
得到执行新函数时传入的实参。比如:
var obj={
a:1
};
function foo(a,b,c){
this.a++;
return a+b+c;
}
var fo=foo.bind(obj,1,2);
console.log(fo(3)); //6
console.log(obj); //{a:2}
在上面例子中,aArgs保存的是参数b、c,aArgs.concat(Array.prototype.slice.call(arguments)))
则得到调用fo()时传入的参数3。
第三,fToBind
的作用同前面第一种兼容方法的_self
。fBound
作为构造函数时,它的实例会继承fBound.prototype
。由于fBound.prototype
又是fNOP
的实例,因此fBound.prototype
会继承fNOP.prototype
的属性。fNOP.prototype
和this.prototype
指向了同一个原型对象,这里的this指向的是调用bind()的函数。这样形成的原型链中,fBound
的实例将继承得到fNOP.prototype
的属性,这便是原型链继承。
第四,this instanceof fNOP
实现对新函数调用方式的的判断。当新函数作为一般函数直接调用时,它的this指向绑定对象,显然this不是 fNOP
的实例。如:
var obj={
a:1
};
function foo(a,b){
this.a++;
return a+b;
}
var fo=foo.bind(obj,1,2);
fo();
console.log(obj.a); //2
上面例子中,foo()内部的this指向obj,显然obj不是fNOP的实例,因此this instanceof fNOP
返回false。
当新函数作为构造函数调用时,即new fo()
,它的this
将指向新创建的函数实例,由第三点所述原型链继承,实例的原型链上存在构造函数fNOP,故 this instanceof fNOP
将返回true
。
5、第五,fNOP.prototype = this.prototype
用于实现对bind()的调用者的原型链的继承
。这里,this指向bind()的调用者,因此这使得fNOP.prototype
指向调用者的原型对象。假使调用者也有原型链,那么这样新函数就也能继承原函数的原型链。当然,只有在调用者是一个函数时才能成立,因此需先判断this.prototype
是否返回true
。
六、两种兼容方法的比较
1、方法二中加入了对调用bind()的对象类型的检测,即若调用bind()的不是函数,将抛出异常;
2、方法二中实现了对调用bind()后得到的新函数的调用方式的检测,即新函数可以作为一般函数和构造函数调用,方法一只能作为一般函数调用。
3、方法二中加入了对原型链的维护。
bind()函数的深入理解及两种兼容方法分析的更多相关文章
- php模拟登陆的两种实现方法分析
php模拟登陆的实现方法分析 本文实例分析了php模拟登陆的实现方法.分享给大家供大家参考.具体分析如下: php模拟登陆的实现方法,这里分别列举两种方法实现模拟登陆人人网.具体实例代码如下: 1)使 ...
- C++:一般情况下,设计函数的形参只需要两种形式
C++:一般情况下,设计函数的形参只需要两种形式.一,是引用形参,例如 void function (int &p_para):二,是常量引用形参,例如 void function(const ...
- 两种js方法发起微信支付:WeixinJSBridge,wx.chooseWXPay区别
原文链接:https://www.2cto.com/weixin/201507/412752.html 1.为什么会有两种JS方法可以发起微信支付? 当你登陆微信公众号之后,左边有两个菜单栏,一个是微 ...
- 史上最全的CSS hack方式一览 jQuery 图片轮播的代码分离 JQuery中的动画 C#中Trim()、TrimStart()、TrimEnd()的用法 marquee 标签的使用详情 js鼠标事件 js添加遮罩层 页面上通过地址栏传值时出现乱码的两种解决方法 ref和out的区别在c#中 总结
史上最全的CSS hack方式一览 2013年09月28日 15:57:08 阅读数:175473 做前端多年,虽然不是经常需要hack,但是我们经常会遇到各浏览器表现不一致的情况.基于此,某些情况我 ...
- angular2系列教程(十)两种启动方法、两个路由服务、引用类型和单例模式的妙用
今天我们要讲的是ng2的路由系统. 例子
- 两种Ajax方法
两种Ajax方法 Ajax是一种用于快速创建动态网页的技术,他通过在后台与服务器进行少量的数据交换,可以实现网页的异步更新,不需要像传统网页那样重新加载页面也可以做到对网页的某部分作出更新,现在这项技 ...
- Service的两种启动方法
刚才看到一个ppt,介绍service的两种启动方法以及两者之间的区别. startService 和 bindService startService被形容为我行我素,而bindService被形容 ...
- Linux系统中存储设备的两种表示方法
转:https://blog.csdn.net/holybin/article/details/38637381 一.对于IDE接口的硬盘的两种表示方法: 1.IDE接口硬盘,对于整块硬盘的两种表示方 ...
- JavaScript监听手机物理返回键的两种解决方法
JavaScript没有监听物理返回键的API,所以只能使用 popstate 事件监听. 有两个解决办法: 1.返回到指定的页面 pushHistory(); window.addEventList ...
随机推荐
- 基于 HTML5 WebGL 的 水泥工厂可视化系统
前言 如今的制造行业,基于数据进行生产策略制定与管理已经成为一种趋势,特别是 工业4.0 的浪潮下,数据战略已经成为很多制造企业的优先战略,而数据可视化以更直观的方式,帮助指导决策,成为数据分析传递信 ...
- 一起了解 .Net Foundation 项目 No.23
.Net 基金会中包含有很多优秀的项目,今天就和笔者一起了解一下其中的一些优秀作品吧. 中文介绍 中文介绍内容翻译自英文介绍,主要采用意译.如与原文存在出入,请以原文为准. WorldWide Tel ...
- VSCode 初次写vue项目并一键生成.vue模版
VSCode 写vue项目一键生成.vue模版 1.新建代码片段 文件-->首选项-->用户代码片段-->点击新建代码片段--取名vue.json 确定 2.配置快捷生成的vue模板 ...
- python 判断一个字符串是否是小数
"""练习判断一个小数1.判断是否合法2.合法需要有一个小数点3.小数点左边必须是个整数,右边必须是个正整数 """ def xiaoshu ...
- Array(数组)对象-->concat() 方法
1.定义和用法 concat() 方法用于连接两个或多个字符串. 语法: string.concat(string1, string2, ..., stringX) 举例: var str1='hel ...
- pgsql中的lateral使用小结
pgsql中的lateral 什么是LATERAL 带有LATERAL的SQL的计算步骤 LATERAL在OUTER JOIN中的使用限制(或定义限制) LATERAL的几个简单的例子 总结 举几个我 ...
- Java的多线程编程模型5--从AtomicInteger开始
Java的多线程编程模型5--从AtomicInteger开始 2011-06-23 20:50 11393人阅读 评论(9) 收藏 举报 java多线程编程jniinteger测试 AtomicIn ...
- Apache SkyWalking
Apache SkyWalking 什么是 SkyWalking SkyWalking 是观察性分析平台和应用性能管理系统. 提供分布式追踪.服务网格遥测分析.度量聚合和可视化一体化解决方案. 支持J ...
- ASE课程总结 by 张葳
本期ASE课程分为两个阶段,第一阶段的personal project与第二阶段的team project,其中,第一阶段旨在锻炼我们个人的问题解决能力和编程能力,第二阶段则锻炼主要我们的管理能力,合 ...
- Python程序设计实验报告四:循环结构程序设计(设计型实验)
安徽工程大学 Python程序设计 实验报告 班级 物流191 姓名 姚彩琴 学号3190505129 成绩 日期 2020.4.8 指导老师 修宇 [实验名称 ...