一、call
1、call是什么
call是Function.prototype上的一个方法,通过函数去调用,如:fn.call(thisArg, arg1, arg2, ...)
2、call的参数
- thisArg:可选,fn运行时使用的this值
- 在非严格模式下,不传值,传null或undefined,会自动替换为window
- 在严格模式下,不传值和传undefined,fn中this指向undefined;传null时fn中this指向null
- 原始值会被包装类包装
- arg1, arg2, ...:从第二个参数开始都是传递给fn的实参
3、call的返回值
返回fn函数中的返回值,如果fn函数没有返回值,则默认返回undefined
4、call的使用场景
1、用来改变调用call的函数中this的指向
var myName = '全局name:小明'
function animal() {
console.log(this.myName)
}
var dog = {
myName: '旺财'
}
animal.call() // 全局name:小明,call()中没有传参时,相当于传递了window
animal.call(dog) // 旺财
animal.call(1) // undefined,传递原始值会被包装类包装,所以不会报错,但是在new Number()上又找不到myName,所以结果为undefined
Number.prototype.myName = 'number name'
animal.call(1) // number name
2、子类借用父类的属性或方法 juejin.cn/post/714088…
function Animal(name, food) {
this.eat = function () {
console.log(`${name}吃${food}`)
}
}
function Dog(name, food) {
Animal.call(this, name, food)
}
const dog = new Dog('旺财', '骨头')
dog.eat()
console.log(dog instanceof Animal) // false
注意,这是借用,不是继承,只有拥有了父类的原型才是继承
Dog.prototype = Animal.prototype // 这种的才是继承,或者Dog.prototype = new Animal()
const dog1 = new Dog()
console.log(dog1 instanceof Animal) // true
5、手动实现call
animal.call(dog) // 将animal方法添加到dog中,执行完再删除dog中的这个方法
传递到call中的对象(dog),在这个对象身上添加一个方法(fn),这个方法(fn)指向调用call的函数(animal),执行这个方法(fn),call第二个及后面的参数传到fn方法中,执行完删除fn。
在这个过程中,由于fn是对象dog的方法,fn中的this指向的是obj
ES5:
Function.prototype.myCall = function (obj) {
if (typeof obj === 'number') obj = new Number(obj) // 原始值会包装
obj = obj || window // null和undefined会自动替换为window
var arr = []
for (var i = 1, len = arguments.length; i < len; i++) arr.push('arguments[' + i + ']') // 从第二个参数开始都是传到函数中的实参
obj.fn = this // 往传入的参数上添加一个方法,方法名任意,值为animal
var res = eval('obj.fn(' + arr + ')') // 调用fn,也就是调用animal,并接收返回值
delete obj.fn
return res
}
ES6:
Function.prototype.myCall = function (obj, ...args) {
if (typeof obj === 'number') obj = new Number(obj)
obj.fn = this
const res = obj.fn(...args)
delete obj.fn
return res
}
以上实现myCall方法时,手动将obj对象中的fn函数删除,万一obj中也有一个函数叫fn,便会影响到原obj,这是不可取的,当然你可以随意起一个几乎不会在obj中出现的属性名,但是ES6提供了一个symbol基本数据类型,它一定是obj中属性的唯一标识符:
Function.prototype.myCall = function (obj, ...args) {
if (typeof obj === 'number') obj = new Number(obj)
const symbol = Symbol()
obj[symbol] = this
const res = obj[symbol](...args)
delete obj[symbol]
return res
}
二、apply
1、概念:apply和call的唯一区别是传参,apply将call的第2个及以后的参数整合成一个数组
2、apply的手动实现
Function.prototype.myApply = function (obj = window, args) {
obj.fn = this
const res = args ? obj.fn(...args) : obj.fn()
delete obj.fn
return res
}
3、apply的使用场景
- 实现类似concat的功能
const fruits = ['apple', 'banana']
const nums = [1, 2, 3]
const newArr = fruits.concat(nums)
const newArr1 = fruits.push.apply(fruits, nums) && fruits // 将nums追加到fruits中
console.log(newArr) // ['apple', 'banana', 1, 2, 3]
console.log(newArr1) // ['apple', 'banana', 1, 2, 3]
要注意:concat并不会改变fruits,而apply改变了fruits
- 有一些需要循环去遍历数组的操作,可以使用apply去完成,避免循环
const nums = [1, 5, 2, 3]
const max = Math.max.apply(null, nums) // 不使用扩展运算符的话,一般用apply
console.log(max)
- 通过apply获取实参
function test(...rest) {
var args = []
;[].push.apply(args, arguments)
var args1 = Array.prototype.slice.apply(arguments) // 这里使用call是一样的
console.log(args)
console.log(args1)
console.log(rest) // 剩余参数不能完全替代arguments
}
test(1, 2, 3)
三、bind
1、bind和call的区别是,bind返回一个偏函数,使用柯里化传参
var username = '小明'
function animal(a, b, c) {
console.log(this.username, a, b, c)
}
var dog = {
username: '旺财'
}
animal.bind(dog, 1, 2)(50) // 旺财 1 2 50
2、bind的手动实现
ES5:
Function.prototype.myBind = function (obj) {
var _this = this
var outerArgs = Array.prototype.slice.call(arguments, 1)
return function () {
var innerArgs = Array.prototype.slice.call(arguments)
return _this.apply(obj, outerArgs.concat(innerArgs))
}
}
ES6:
Function.prototype.myBind = function (obj, ...outerArgs) {
return (...innerArgs) => this.apply(obj, [...outerArgs, ...innerArgs])
}
bind返回的函数,当做构造函数使用时,new操作符会将this指向实例,此时的this指向构造函数中新创建的对象,并且实例对象的原型会继承调动bind函数的原型。
那么实现起来就需要考虑到new的情况:
ES5:
Function.prototype.myBind = function (obj) {
var _this = this
var outerArgs = Array.prototype.slice.call(arguments, 1)
var Buffer = function () {}
var Callback = function () {
var innerArgs = Array.prototype.slice.call(arguments)
var args = outerArgs.concat(innerArgs)
// 闭包中的this指向window,但如果使用new操作符,this指向Callback函数
_this.apply(this instanceof Buffer ? this : obj, args)
}
Buffer.prototype = this.prototype
Callback.prototype = new Buffer() // Buffer仅作为继承的中间件
return Callback
}
function person(a, b) {
console.log(this.name, a, b)
}
var obj = {
name: '小明'
}
var callback = person.myBind(obj, 1)
callback(2) // 小明 1 2
var c = new callback(20) // undefined 1 20
console.log(c) // 继承person
3、bind的使用场景:bind更适合用于事件的回调
<button id="我是button的id值">按钮</button>
<div id="我是div的id值">div</div>
<script>
const btn = document.querySelector('button'),
div = document.querySelector('div')
btn.addEventListener('click', handleBtnClick.bind(div))
function handleBtnClick() {
console.log(this.id)
}
</script>
如果要使用call,就得手动返回handleBtnClick函数
btn.addEventListener('click', () => handleBtnClick.call(div))