call
call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。
- call 改变了this的指向,并执行函数
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
bar.call(foo); // 1
// 可以看成如下实现
/**
将函数设为对象的属性
执行该函数
删除该函数
**/
var foo = {
value: 1,
bar: function() {
console.log(this.value)
}
};
foo.bar(); // 1
delete foo.bar
Function.prototype.call = function(context) {
// 用this获取调用call的函数
context.fn = this
context.fn()
delete context.fn
}
- call 函数还能给定参数执行函数
-
思路: Arguments 对象中取值,取出第二个到最后一个参数,然后放到一个数组里
-
把参数数组放到要执行的函数的参数里面去,用 eval 方法拼成一个函数
-
eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。
-
// 此时的arguments为:
// arguments = {
// 0: foo,
// 1: 'kevin',
// 2: 18,
// length: 3
// }
// 因为arguments是类数组对象,所以可以用for循环
Function.prototype.call = function(context) {
context.fn = this
let arr = []
for(let i = 1, i< arguments.length, i++) {
arr.push(`arguments[${i}]`)
}
// `${arr}` = arr.toString() , [1,2,3].toString() = '1,2,3'
eval(`context.fn(${arr})`)
// 或: context.fn(...arr)
delete context.fn
}
- this 参数可以传 null,当为 null 的时候,视为指向 window
- 函数是可以有返回值的!
Function.prototype.call = function(context) {
context = context || window
context.fn = this
let arr = []
for(let i = 1, i< arguments.length, i++) {
arr.push(`arguments[${i}]`)
}
let result = eval(`context.fn(${arr})`)
delete context.fn
return result
}
apply
- apply 实现同call,注意arguments不同
Function.prototype.apply = function(context,arr) {
context = context || window
context.fn = this
let result
if(!arr){
result = context.fn()
} else {
let args = []
for(let i = 1, i< arr.length, i++) {
args.push(`arr[${i}]`)
}
result = eval(`context.fn(${args})`)
}
delete context.fn
return result
}
bind
bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。
- 返回一个函数
- 可以传入参数
- 思考:bind传入参数类似于call
- Array.prototype.slice.call()将类数组转为数组
Function.prototype.bind = function(context) {
let that = this
let args = Array.prototype.slice.call(arguments, 1)
return function () {
that.apply(context, args)
}
}
- 返回函数可以有返回值
- 返回函数也可以传入参数
Function.prototype.bind = function(context) {
let that = this
// 这里的arguments是通过bind传入的参数
let args = Array.prototype.slice.call(arguments, 1)
return function () {
// 这里的arguments是返回函数传入的参数
let bindArgs = Array.prototype.slice.call(arguments)
// 使用return, 函数可以有返回值
return that.apply(context, args.concat(bindArgs))
// 或 return that.apply(context, [...args,...bindArgs])
}
}
- 一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
- 思考: 当 bind 返回的函数作为构造函数的时候,bind 时指定的 this 值失效
Function.prototype.bind = function(context) {
let that = this
let args = Array.prototype.slice.call(arguments, 1)
let funBind = function () {
let bindArgs = Array.prototype.slice.call(arguments)
// 这里的this,当返回的函数为构造函数的时候会指向funBind
// 当返回的函数作为普通函数的时候会指向window
// this指向返回函数的this,可以使用绑定函数的属性值
return that.apply(
this instanceof funBind? this : context,
args.concat(bindArgs)
)
}
// 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
funBind.prototype = this.prototype
return funBind
}
funBind.prototype = this.prototype的情况下修改funBind.prototype的时候也会修改到绑定函数的prototype,所以用一个空函数来轮转
Function.prototype.bind = function(context) {
let that = this
let args = Array.prototype.slice.call(arguments, 1)
let funNo = function () {}
let funBind = function () {
let bindArgs = Array.prototype.slice.call(arguments)
return that.apply(
this instanceof funNo? this : context,
args.concat(bindArgs)
)
}
funNo.prototype = this.prototype
funBind.prototype = new funNo()
return funBind
}
- 防止调用bind的不是函数
Function.prototype.bind = function(context) {
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
let that = this
let args = Array.prototype.slice.call(arguments, 1)
let funNo = function () {}
let funBind = function () {
let bindArgs = Array.prototype.slice.call(arguments)
return that.apply(
this instanceof funNo? this : context,
args.concat(bindArgs)
)
}
funNo.prototype = this.prototype
funBind.prototype = new funNo()
return funBind
}
参考文章: