blog
blog copied to clipboard
JS中Call、Apply、Bind解析
一句话概括 call、apply、bind 主要就是改变this的指向
call
call语法:
fun.call(thisArg, arg1, arg2, ...),调用一个函数,具有一个指定的this值和干个指定的参数值(参数列表)
例子
var foo = {
value: 1
}
function bar () {
console.log(this.value)
}
bar.call(foo) // 1
注意:
- call改变了this的指向,把bar的this指向了foo
- bar函数执行
简单点来说就是在调用call的时候把函数改造成这样
var foo = {
value: 1,
bar: function () {
console.log(this.value)
}
}
foo.bar()
如何实现call核心:
- 把函数设为对象的属性
- 执行并且删除这个函数
- 指定this到函数并传入给定参数执行函数
- 若不传入参数,默认指向window
const selfCall = function (context, ...args) {
console.log(context, 'context')
let func = this
console.log(func, 'func')
console.log(args, 'args')
context = context || window // context带着value
if (typeof func !== 'function') throw new TypeError('this is no function')
let caller = Symbol('caller')
// 把函数放到context里面,执行以后删除,使用symbol的原因就是防止属性冲突咯!
context[caller] = func
let res = context[caller](...args)
console.log(res)
delete context[caller]
return res
}
Function.prototype.selfCall = selfCall
let foo = { value: 1 }
function bar (name, age) {
console.log('--------------------------------')
console.log(name, 'bar---name')
console.log(age, 'bar---age')
console.log(this.value, 'foo---value')
}
bar.selfCall(foo, 'black', 18)
apply
apply的实现方式和call其实是一样的,只是第二个参数是否数组的原因
const selfApply = function (context) {
context = context || window
console.log(arguments)
let func = this
let caller = Symbol('caller')
context[caller] = func
let res
if (arguments[1]) {
res = context[caller](...arguments[1])
} else {
res = context[caller]()
}
return res
}
Function.prototype.selfApply = selfApply
let person = {
name: 'Tom'
}
function sayMsg (a, b, c) {
console.log(`${this.name} - ${a} - ${b} - ${c}`)
}
sayMsg.selfApply(person, ['a', 'b', 'c'])
bind
bind()方法
会创建一个新函数,当这个新函数被调用时,bind()的第一个参数作为它运行时的this,之后的一序列参数将会在传递的实参前作为它的参数(bind的实现必须考虑实例化后对原型链的影响)
const selfBind = function (context) {
console.log(context, '=----=> context')
if (typeof this !== 'function') return
let self = this
console.log(this, '=====> this')
let arg = [].slice.call(arguments, 1) // 不要被绑定方法的那个对象,就是context
console.log('第一个args -> ', arg)
console.log('-------分割线-------')
let fNop = function () {}
let fBound = function () {
let bindArg = [].slice.call(arguments)
console.log('第二个args -> ', bindArg)
console.log('-------分割线-------')
/**
* 这里的 this instanceof 中的 this 是指的是调用func()时的执行环境
* 判断是否用了new关键字 使用忽略bind(obj)的obj,不改变this
* 没有就让this指向obj
*/
console.log('new this---->', this instanceof fNop)
return self.apply(this instanceof fNop ? this : context, arg.concat(bindArg))
}
fNop.prototype = this.prototype
fBound.prototype = new fNop()
return fBound
}
Function.prototype.selfBind = selfBind
// 测试
function func (name, age) {
console.log(this.value)
console.log(name)
console.log(age)
}
let obj = {
value: '一个值'
}
let bind = func.selfBind(obj, [1, 2, 3], 4)
bind(333, 'bind-333')
new bind()
实现函数bind的方法核心就是利用call绑定this的指向,同时也要考虑一些其他的问题:
- 旧函数调用bind(obj)会返回一个新函数
- 新函数使用旧函数的功能且判断是否改变this指向
- 调用bind函数可传递参数,作为返回的新函数的默认参数
- 调用bind函数后返回的新函数的实际参数包含 (this, [默认参数], [新函数参数])
- 旧函数调用bind函数返回的新函数new出的实例化对象的constructor是旧函数, 利用继承的方式