call
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
语法
function.call(thisArg, _arg1**, **arg2_, ...)
| 参数 | 说明 |
|---|---|
| thisArg | 可选的。在 function 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。 |
| arg1, arg2, ... | 指定的参数列表。 |
思考
var people={
sex:'man',
age:27
}
function sayPeople(a,b){
console.log(this.sex,this.age,a,b)
return false
}
sayPeople.call(people,2,3) //man 27 2 3
在这里call实现效果是外部函数可以调用对象的内部属性(this指向改变).可以简单理解:只要把外部函数设置为对象的内部方法,就可以调用对象的内部属性.
var people={
sex:'man',
age:27
}
function sayPeople(a,b){
console.log(this.sex,this.age,a,b)
}
sayPeople.call(people,2,3)//man 27 2 3
//按照思路改写代码,可以发现输出效果一致
var people2={
sex:'man',
age:27,
sayPeople2:sayPeople2
}
function sayPeople2(a,b){
console.log(this.sex,this.age,a,b)
}
people2.sayPeople2(4,5)//man 27 4 5
模拟实现
第一步 先实现_function_.call(thisArg)形式,
var people={
sex:'man',
age:27
}
function sayPeople(){
console.log(this.sex,this.age)
}
Function.prototype.newCall=function(obj) {
// 将调用函数设置为obj的内置函数
obj.fn=this
obj.fn()
// 删除fn ,避免对象上多出fn方法
delete obj.fn
}
sayPeople.call(people) //man 27
sayPeople.newCall(people) //man 27
第二步 除了_function_.call(thisArg)形式,我们还需要考虑_function_.call(thisArg, arg1, arg2, ...)的形式.
关键点:
- 获取除第一个以外的其余参数
- 如何将获取的参数传入obj.fn函数中
第一个很好解决,通过对arguments进行遍历就可以解决,第二个我们无法直接将获取的参数数组直接传入fn函数中,这时可以使用eval()来实现效果.具体代码如下:
**eval() **函数会将传入的字符串当做 JavaScript 代码进行执行
var people = {
sex: 'man',
age: 27
}
function sayPeople(a,b) {
console.log(this.sex, this.age,a,b)
}
Function.prototype.newCall = function (obj) {
// 将调用函数设置为obj的内置函数
obj.fn = this
//获取除第一个以外的其他所有参数
var arr = []
for (var i = 1; i < arguments.length; i++) {
//注意我们要获取的字符串,不是具体的值,因为eval()是将字符串当作代码执行
arr[i - 1] = 'arguments[' + i + ']'
}
//通过eval()实现多参数调用fn方法
eval('obj.fn(' + arr + ')')
// 删除fn ,避免对象上多出fn方法
delete obj.fn
}
sayPeople.call(people,1,2) //man 27 1 2
sayPeople.newCall(people,1,2)//man 27 1 2
第三步 除此以外,我们还需要考虑两点问题.第一是如果sayPeople函数中有return返回时;第二是如果_thisArg_传入为null或者undefined时的情况处理.第一个很好处理只要返回调用函数的结果就行.第二做个判断处理如果传入的是null或者undefined时,指向改为window.
最终效果
var people = {
sex: 'man',
age: 27
}
function sayPeople(a,b) {
console.log(this.sex, this.age,a,b)
return false
}
Function.prototype.newCall=function(obj) {
//判断传入的对象是否为null或者undefined,如果是指向为window
obj=obj || window
// 将调用函数设置为obj的内置函数
obj.fn=this
//获取除第一个以外的其他所有参数
var arr=[]
for(var i=1;i<arguments.length;i++) {
arr[i-1]='arguments['+i+']'
}
//通过eval()实现多参数调用fn方法
var result=eval('obj.fn('+arr+')')
// 删除fn ,避免对象上多出fn方法
delete obj.fn
//考虑调用函数sayPeople有return的情况
return result
}
console.log(sayPeople.call(people,1,2))
//man 27 1 2
//false
console.log(sayPeople.newCall(people,1,2))
//man 27 1 2
//false
apply
apply() 方法调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数。 备注:call()方法的作用和 apply() 方法类似,区别就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。
语法
func.apply(thisArg, [argsArray])
| 参数 | 说明 |
|---|---|
| thisArg | 必选的。在 func 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。 |
| argsArray | 可选的。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。 浏览器兼容性 请参阅本文底部内容。 |
思考
apply与call的区别就在于第二个参数不同
具体实现
var people = {
sex: 'man',
age: 27
}
function sayPeople(...args) {
console.log(this.sex, this.age,...args);
return false
}
Function.prototype.newApply = function (obj, arr) {
var result,args=[];
//判断传入的对象是否为null或者undefined,如果是指向为window
obj = obj || window
// 将调用函数设置为obj的内置函数
obj.fn = this
// apply只有两个参数,通过判断arr是否为空,分别执行fn
if (!arr) {
result = obj.fn()
} else {
for (var i = 0; i < arr.length; i++) {
args[i] = 'arr[' + i + ']'
}
//通过eval()实现多参数调用fn方法
result = eval('obj.fn(' + args + ')')
}
// 删除fn ,避免对象上多出fn方法
delete obj.fn
//考虑有调用函数return的情况
return result
}
console.log(sayPeople.apply(people, [1, 2,8,9,5]))
//man 27 1 2 8 9 5
//false
console.log(sayPeople.newApply(people, [1, 2,8,9,5]))
//man 27 1 2 8 9 5
//false
bind
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
语法
function_.bind(thisArg[, arg1[, arg2[, ...]]])_
| 参数 | 说明 |
|---|---|
| thisArg | 调用绑定函数时作为 this 参数传递给目标函数的值。 如果使用new运算符构造绑定函数,则忽略该值。当使用 bind 在 setTimeout 中创建一个函数(作为回调提供)时,作为 thisArg 传递的任何原始值都将转换为 object。如果 bind 函数的参数列表为空,或者thisArg是null或undefined,执行作用域的 this 将被视为新函数的 thisArg。 |
| arg1, arg2, | 当目标函数被调用时,被预置入绑定函数的参数列表中的参数。 |
| 返回值 | 返回一个原函数的拷贝,并拥有指定的 this 值和初始参数。 |
|---|
模拟实现
第一步 发现bind与call/apply的相同与不同
不同:bind会返回一个原函数的拷贝
相同:this指向改变原理相同
所以根据上面思考我们可以得到下面代码
var people = {
sex: 'man',
age: 27
}
function sayPeople(a,b) {
console.log(this.sex, this.age,a,b)
return false
}
Function.prototype.newBind=function(obj){
//因为函数里面返回函数, 防止this丢失,提前保存this
var that = this
var args=Array.prototype.slice.call(arguments,1)
return function () {
return that.apply(obj, args)
}
}
sayPeople.bind(people,2,2)()//man 27 2 2
sayPeople.newBind(people,2,2)()//man 27 2 2
我们发现bind的还会接受返回函数的参数,所以需要将参数合并
var people = {
sex: 'man',
age: 27
}
function sayPeople(a,b) {
console.log(this.sex, this.age,a,b)
return false
}
Function.prototype.newBind=function(obj){
// 防止this丢失
var that = this
var args=Array.prototype.slice.call(arguments,1)
return function () {
// 这个时候的arguments是指bind返回的函数传入的参数
//注意这个时候的arguments不需要切割,只需要将类数组转化为数组
return that.apply(obj, args.concat(Array.prototype.slice.call(arguments)))
}
}
sayPeople.bind(people,2)(2)//man 27 2 2
sayPeople.newBind(people,2)(2)//man 27 2 2
第二步 在这里我们需要考虑一个难点问题,那就是通过new 创建bind返回函数的实例.
var sex= 'woman';
var people = {
sex: 'man',
age: 27
}
function sayPeople(a,b) {
console.log(this.sex, this.age,a,b)
return false
}
var sayPeople2 = sayPeople.bind(people,2)
var people2=new sayPeople2(2)
console.log(people2)
//undefined undefined 2 2
//sayPeople {}
一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
通过上面代码输出,我们可以发现sayPeople内部this指向丢失了,既不指向people,也不指向window;但是传入的参数不受影响.造成这现象的原因是使用了new.所以解决问题的方法就是修改原型和修改传入apply的第一个参数.
关键点
- 判断什么时候用了new
- 改变返回函数的原型,使其指向sayPeople
思考
- 如果使用了new,则当前this指向是返回函数的实例,所以可以通过当前this的原型是否是返回函数,可以得出new的使用情况
- 通过修改prototype属性,改变其原型
具体实现
var sex= 'woman';
var people = {
sex: 'man',
age: 27
}
function sayPeople(a,b) {
console.log(this.sex, this.age,a,b)
return false
}
Function.prototype.newBind=function(obj){
// 防止this丢失
var that = this
var args=Array.prototype.slice.call(arguments,1)
function newFn () {
// this instanceof newFn 结果为true,说明调用new,反则无
return that.apply(this instanceof newFn ? this:obj, args.concat(Array.prototype.slice.call(arguments)))
}
// 修改返回函数的 prototype 为绑定函数的(在这里是sayPeople)prototype
newFn.prototype=that.prototype
return newFn
}
var sayPeople2 = sayPeople.newBind(people,2)
var people2=new sayPeople2(2)
console.log(people2)
代码优化
因为我们直接赋值修改newFn的prototype,所以如果修改newFn中的prototype,也会影响绑定函数sayPeople中的prototype,所以需要通过一个空函数进行中转.
Function.prototype.newBind=function(obj){
//如果调用的bind不是函数,直接报错
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
// 防止this丢失
var that = this
var emptyFn=function (){}
var args=Array.prototype.slice.call(arguments,1)
function newFn () {
// this instanceof newFn 结果为true,说明调用new,反则无
return that.apply(this instanceof newFn ? this:obj, args.concat(Array.prototype.slice.call(arguments)))
}
// 修改返回函数的 prototype 为绑定函数的(在这里是sayPeople)prototype
emptyFn.prototype=that.prototype
newFn.prototype=new emptyFn()
return newFn
}
小结
- 对于call/apply/bind的实现理解,关键在于对于call的理解.简单理解call:call可以使外部函数可以调用对象的内部属性;那怎么样可以使用对象的内部属性——内部方法;所以对象内新增一个与外部函数一样的方法,在调用后删除其方法,最后再其返回结果.就可以实现其效果(这个只是简单的理解,考虑不周请指正)
- apply实现关键再第二个参数处理上(注意第二个参数传入可以是数组,也可以是类数组对象)
- 对bind实现关键需要理解new和原型
以上是我学习思路和学习想法,如若有误,请帮忙指正.