原文地址:JavaScript基础心法——call apply bindjavascript
欢迎star。java
若是有错误的地方欢迎指正。git
整理call
、apply
、bind
这三个方法的的知识点。github
以前这篇文章提到过this
的各类状况,其中有一种状况就是经过call
、apply
、bind
来将this
绑定到指定的对象上。web
也就是说,这三个方法能够改变函数体内部this
的指向。segmentfault
这三个方法有什么区别呢?分别适合应用在哪些场景中呢?数组
先举个简单的栗子 ~app
var person = { name: "axuebin", age: 25 }; function say(job){ console.log(this.name+":"+this.age+" "+job); } say.call(person,"FE"); // axuebin:25 FE say.apply(person,["FE"]); // axuebin:25 FE var sayPerson = say.bind(person,"FE"); sayPerson(); // axuebin:25 FE
对于对象person
而言,并无say
这样一个方法,经过call
/apply
/bind
就能够将外部的say
方法用于这个对象中,其实就是将say
内部的this
指向person
这个对象。ide
call
是属于全部Function
的方法,也就是Function.prototype.call
。函数
The call() method calls a function with a given this value and arguments provided individually.
call() 方法调用一个函数, 其具备一个指定的this值和分别地提供的参数(参数的列表)。
它的语法是这样的:
fun.call(thisArg[,arg1[,arg2,…]]);
其中,thisArg
就是this
指向,arg
是指定的参数。
call
的用处简而言之就是可让call()中的对象调用当前对象所拥有的function。
ECMAScript规范中是这样定义call
的:
当以thisArg
和可选的arg1
,arg2
等等做为参数在一个func
对象上调用call
方法,采用以下步骤:
IsCallable(func)
是false
, 则抛出一个TypeError
异常。argList
为一个空列表。arg1
开始以从左到右的顺序将每一个参数插入为argList
的最后一个元素。thisArg
做为this
值并以argList
做为参数列表,调用func
的[[Call]]
内部方法,返回结果。call
方法的length
属性是1。
在外面传入的thisArg
值会修改并成为this
值。thisArg
是undefined
或null
时它会被替换成全局对象,全部其余值会被应用ToObject
并将结果做为this
值,这是第三版引入的更改。
var obj = { a: 1 } function foo(b, c){ this.b = b; this.c = c; console.log(this.a + this.b + this.c); } foo.call(obj,2,3); // 6
在须要实现继承的子类构造函数中,能够经过call
调用父类构造函数实现继承。
function Person(name, age){ this.name = name; this.age = age; this.say = function(){ console.log(this.name + ":" + this.age); } } function Student(name, age, job){ Person.call(this, name ,age); this.job = job; this.say = function(){ console.log(this.name + ":" + this.age + " " + this.job); } } var me = new Student("axuebin",25,"FE"); console.log(me.say()); // axuebin:25 FE
apply
也是属于全部Function
的方法,也就是Function.prototype.apply
。
The apply() method calls a function with a given this value, and arguments provided as an array (or an array-like object).
apply() 方法调用一个函数, 其具备一个指定的this值,以及做为一个数组(或相似数组的对象)提供的参数。
它的语法是这样的:
fun.apply(thisArg, [argsArray]);
其中,thisArg
就是this
指向,argsArray
是指定的参数数组。
经过语法就能够看出call
和apply
的在参数上的一个区别:
call
的参数是一个列表,将每一个参数一个个列出来apply
的参数是一个数组,将每一个参数放到一个数组中当以thisArg
和argArray
为参数在一个func
对象上调用apply
方法,采用以下步骤:
IsCallable(func)
是false
, 则抛出一个TypeError
异常 .若是argArray
是null
或undefined
, 则
thisArg
做为this
值并以空参数列表调用func
的[[Call]]
内部方法的结果。Type(argArray)
不是Object
, 则抛出一个TypeError
异常 .len
为以"length"
做为参数调用argArray
的[[Get]]
内部方法的结果。n
为ToUint32(len)
.argList
为一个空列表 .index
为0.只要index
<n
就重复
indexName
为ToString(index)
.nextArg
为以indexName
做为参数调用argArray
的[[Get]]
内部方法的结果。nextArg
做为最后一个元素插入到argList
里。index
为index + 1
.thisArg
做为this
值并以argList
做为参数列表,调用func
的[[Call]]
内部方法,返回结果。apply
方法的length
属性是 2。
在外面传入的thisArg
值会修改并成为this
值。thisArg
是undefined
或null
时它会被替换成全局对象,全部其余值会被应用ToObject
并将结果做为this
值,这是第三版引入的更改。
在用法上apply
和call
同样,就不说了。
参考连接:https://github.com/jawil/blog/issues/16
Function.prototype.myApply=function(context){ // 获取调用`myApply`的函数自己,用this获取 context.fn = this; // 执行这个函数 context.fn(); // 从上下文中删除函数引用 delete context.fn; } var obj ={ name: "xb", getName: function(){ console.log(this.name); } } var me = { name: "axuebin" } obj.getName(); // xb obj.getName.myApply(me); // axuebin
确实成功地将this
指向了me
对象,而不是自己的obj
对象。
上文已经提到apply
须要接受一个参数数组,能够是一个类数组对象,还记得获取函数参数能够用arguments
吗?
Function.prototype.myApply=function(context){ // 获取调用`myApply`的函数自己,用this获取 context.fn = this; // 经过arguments获取参数 var args = arguments[1]; // 执行这个函数,用ES6的...运算符将arg展开 context.fn(...args); // 从上下文中删除函数引用 delete context.fn; } var obj ={ name: "xb", getName: function(age){ console.log(this.name + ":" + age); } } var me = { name: "axuebin" } obj.getName(); // xb:undefined obj.getName.myApply(me,[25]); // axuebin:25
context.fn(...arg)
是用了ES6的方法来将参数展开,若是看过上面那个连接,就知道这里不经过...
运算符也是能够的。
原博主经过拼接字符串,而后用eval
执行的方式将参数传进context.fn
中:
for (var i = 0; i < args.length; i++) { fnStr += i == args.length - 1 ? args[i] : args[i] + ','; } fnStr += ')';//获得"context.fn(arg1,arg2,arg3...)"这个字符串在,最后用eval执行 eval(fnStr); //仍是eval强大
咱们知道,当apply
的第一个参数,也就是this
的指向为null
时,this
会指向window
。知道了这个,就简单了~
Function.prototype.myApply=function(context){ // 获取调用`myApply`的函数自己,用this获取,若是context不存在,则为window var context = context || window; context.fn = this; //获取传入的数组参数 var args = arguments[1]; if (args == undefined) { //没有传入参数直接执行 // 执行这个函数 context.fn() } else { // 执行这个函数 context.fn(...args); } // 从上下文中删除函数引用 delete context.fn; } var obj ={ name: "xb", getName: function(age){ console.log(this.name + ":" + age); } } var name = "window.name"; var me = { name: "axuebin" } obj.getName(); // xb:25 obj.getName.myApply(); // window.name:undefined obj.getName.myApply(null, [25]); // window.name:25 obj.getName.myApply(me, [25]); // axuebin:25
ES6中新增了一种基础数据类型Symbol
。
const name = Symbol(); const age = Symbol(); console.log(name === age); // false const obj = { [name]: "axuebin", [age]: 25 } console.log(obj); // {Symbol(): "axuebin", Symbol(): 25} console.log(obj[name]); // axuebin
因此咱们能够经过Symbol
来建立一个属性名。
var fn = Symbol(); context[fn] = this;
Function.prototype.myApply=function(context){ // 获取调用`myApply`的函数自己,用this获取,若是context不存在,则为window var context = context || window; var fn = Symbol(); context[fn] = this; //获取传入的数组参数 var args = arguments[1]; if (args == undefined) { //没有传入参数直接执行 // 执行这个函数 context[fn]() } else { // 执行这个函数 context[fn](...args); } // 从上下文中删除函数引用 delete context.fn; }
这样就是一个完整的apply
了,咱们来测试一下:
var obj ={ name: "xb", getName: function(age){ console.log(this.name + ":" + age); } } var name = "window.name"; var me = { name: "axuebin" } obj.getName(); // xb:25 obj.getName.myApply(); // window.name:undefined obj.getName.myApply(null, [25]); // window.name:25 obj.getName.myApply(me, [25]); // axuebin:25
ok 没啥毛病 ~
再次感谢1024大佬 ~
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.
bind()方法建立一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供以前提供一个给定的参数序列。
语法:
fun.bind(thisArg[, arg1[, arg2[, ...]]])
其中,thisArg
就是this
指向,arg
是指定的参数。
能够看出,bind
会建立一个新函数(称之为绑定函数),原函数的一个拷贝,也就是说不会像call
和apply
那样当即执行。
当这个绑定函数被调用时,它的this
值传递给bind
的一个参数,执行的参数是传入bind
的其它参数和执行绑定函数时传入的参数。
当咱们执行下面的代码时,咱们但愿能够正确地输出name
,而后现实是残酷的
function Person(name){ this.name = name; this.say = function(){ setTimeout(function(){ console.log("hello " + this.name); },1000) } } var person = new Person("axuebin"); person.say(); //hello undefined
这里this
运行时是指向window
的,因此this.name
是undefined
,为何会这样呢?看看MDN的解释:
由setTimeout()调用的代码运行在与所在函数彻底分离的执行环境上。这会致使,这些代码中包含的 this 关键字在非严格模式会指向 window。
有一个常见的方法可使得正确的输出:
function Person(name){ this.name = name; this.say = function(){ var self = this; setTimeout(function(){ console.log("hello " + self.name); },1000) } } var person = new Person("axuebin"); person.say(); //hello axuebin
没错,这里咱们就能够用到bind
了:
function Person(name){ this.name = name; this.say = function(){ setTimeout(function(){ console.log("hello " + this.name); }.bind(this),1000) } } var person = new Person("axuebin"); person.say(); //hello axuebin
Function.prototype.bind = function (oThis) { var aArgs = Array.prototype.slice.call(arguments, 1); var fToBind = this; var fNOP = function () {}; var fBound = function () { fBound.prototype = this instanceof fNOP ? new fNOP() : fBound.prototype; return fToBind.apply(this instanceof fNOP ? this : oThis || this, aArgs ) } if( this.prototype ) { fNOP.prototype = this.prototype; } return fBound; }
this
指向this
指向的对象bind
是返回一个绑定函数可稍后执行,call
、apply
是当即调用call
给定参数须要将参数所有列出,apply
给定参数数组