一、call/apply/bind的区别
// call
obj.fn.call(newObj, 1, 2, 3)
// apply
obj.fn.apply(newObj, [1, 2, 3])
// bind
var newfn = obj.fn.bind(newObj, 1, 2, 3)
newfn()
三者都可以用来改变this指针的方向
call和apply绑定的函数都是立即执行,而bind会返回一个函数不会立即执行
call和bind的传参形式相同,而apply的第二个参数需要为数组形式
二、手写实现call
分析:
需要定义在Function.prototype上
两个参数:第一个参数为this指向的目标对象,后面的参数是传入fn的参数,个数不定
要改变fn里的this指向obj,同时会立即执行该函数fn,返回函数执行的结果
// 手写实现call
Function.prototype.myCall = function (obj, ...args) {
obj = obj == null ? Window : new Object(obj)
let fnkey = Symbol() // 唯一key,不会出现属性名称的覆盖
obj[fnkey] = this // this就是当前的函数fn,此时函数内部的this就会指向obj
let result = obj[fnkey](...args) // 相当于执行fn,此时fn里的this是指向obj的
delete obj[fnkey] // 清理掉obj[fnkey],防止污染
return result // 返回结果
}
// 测试用例
function fn(a, b) {
console.log(a + b)
console.log(this.name)
}
let obj = {
name: 1
}
fn.myCall(obj, 1, 2)
三、手写实现apply
分析:与call的实现相比,就是传参形式不同而已,其他都一样
// 手写实现apply
Function.prototype.myApply = function (obj, args) { // 注意这里传参与call不同
obj = obj == null ? Window : new Object(obj)
let fnkey = Symbol() // 唯一key,不会出现属性名称的覆盖
obj[fnkey] = this // this就是当前的函数fn,此时函数内部的this就会指向obj
let result = obj[fnkey](...args) // 相当于执行fn,此时fn里的this是指向obj的
delete obj[fnkey] // 清理掉obj[fnkey],防止污染
return result // 返回结果
}
// 测试用例
function fn(a, b) {
console.log(a + b)
console.log(this.name)
}
let obj = {
name: 1
}
fn.myCall(obj, [1, 2])
四、手写实现bind
分析:
需要定义在Function.prototype上
两个参数: 第一个参数为this指向的目标对象,后面的参数是传入fn的参数,个数不定
返回一个函数,函数执行的时候里面的this指向目标对象obj
可能这样使用:fn.bind(obj, 1)(2),所以要进行参数拼接
返回一个函数,要考虑到返回的函数作为普通函数和构造函数的两种使用场景
普通函数:改变this指向,函数执行的时候里面的this指向目标对象obj
构造函数:不能改变this指向,还要保留原函数原型上的属性
// 手写实现bind
Function.prototype.myBind = function (obj, ...args) {
let self = this // 这里的this就是调用bind的函数fn
let newfn = function (...innerArgs) {
// 作为构造函数时,this指向实例,不能改变this指向;作为普通函数时,该项this指向,指向obj
// 注意参数拼接
return self.apply(this instanceof newfn ? this : obj, args.concat(innerArgs))
}
// 如果是构造函数,需要继承原函数原型上的属性和方法
newfn.prototype = new Object(this.prototype)
return newfn
}
// 测试用例
function Person(name, age) {
console.log('Person name:', name)
console.log('Person age:', age)
console.log('Person this:', this) // 构造函数this指向实例对象
}
// 构造函数原型的方法
Person.prototype.say = function() {
console.log('person say')
}
// 普通函数
function normalFun(name, age) {
console.log('普通函数 name:', name)
console.log('普通函数 age:', age)
console.log('普通函数 this:', this) // 普通函数this指向绑定bind的第一个参数 也就是例子中的obj
}
var obj = {
name: 'poetries',
age: 18
}
// 先测试作为构造函数调用
var bindFun = Person.myBind(obj, 'poetry1') // undefined
var a = new bindFun(10) // Person name: poetry1、Person age: 10、Person this: fBound {}
a.say() // person say
// 再测试作为普通函数调用
var bindNormalFun = normalFun.myBind(obj, 'poetry2') // undefined
bindNormalFun(12)
// 普通函数name: poetry2
// 普通函数 age: 12
// 普通函数 this: {name: 'poetries', age: 18}