apply, call, bind 是什么
apply, call, bind 都是用于改变 this 指向的方法
- fn.apply(thisArg, argsArray) 方法:接收两个参数,参数1 thisArg 就是 this 指向的对象,参数2 argsArray 是一个数组或者类数组对象,其中的数组元素将作为单独的参数传给调用函数fn,返回调用函数fn的执行结果,属于立即执行函数。
- fn.call(thisArg, arg1, arg2, ...) 方法:接收一个或多个参数,参数1 thisArg 就是 this 指向的对象,其余参数传给调用函数 fn,返回调用函数的执行结果,属于立即执行函数。
- fn.bind(thisArg, arg1, arg2, ...) 方法:接收一个或多个参数,和 call 方法一致,但是其返回一个函数,而不是返回调用函数的执行结果。
有什么作用
apply 作用
- 改变 this 指向
var name = '我是 window 对象'
function getName (arg1, arg2) {
console.log(`${arg1} ${arg2}, ${this.name}`)
}
let obj = {
name: '我是 obj'
}
getName('你好', '朋友') // 你好 朋友, 我是 window 对象
getName.apply(obj, ['你好', '朋友']) // 你好 朋友, 我是 obj
- 求数组的最大值和最小值
const numbers = [7, 8, 4, 9, 2]
const max = Math.max.apply(null, numbers)
console.log(max) // 9
const min = Math.min.apply(null, numbers)
console.log(min) // 2
- 将数组各项添加到另一个数组
const array = ['a', 'b']
const elements = [0, 1, 2]
array.push.apply(array, elements)
console.log(array) // ['a', 'b', 0, 1, 2]
有小伙伴会问,为啥要用 apply 呢?直接用push, concat 或 es6 扩展运算符都可以实现,举例:
const array = ['a', 'b']
const elements = [0, 1, 2]
array.push(elements)
console.log(array) // ['a', 'b', Array(3)] 如果直接用 push, 会把 elements 整个数组当成 array 的元素
const array = ['a', 'b']
const elements = [0, 1, 2]
const temp = array.concat(elements)
console.log(temp) // ['a', 'b', 0, 1, 2] concat 结果符合要求,但它并不是将元素添加到现有数组,而是创建并返回一个新数组
const array = ['a', 'b']
const elements = [0, 1, 2]
const temp = [...array, ...elements]
console.log(temp) // ['a', 'b', 0, 1, 2] es6 扩张运算符, 结果符合要求,但它并不是将元素添加到现有数组,而是创建并返回一个新数组
call 作用
- 改变 this 指向
var name = '我是 window 对象'
function getName (arg1, arg2) {
console.log(`${arg1} ${arg2}, ${this.name}`)
}
let obj = {
name: '我是 obj'
}
getName('你好', '朋友') // 你好 朋友, 我是 window 对象
getName.call(obj, '你好', '朋友') // 你好 朋友, 我是 obj
- 实现将一个具有 length 属性的对象转化为数组(Array.prototype.slice.call)
/**
* 这里的对象属性名除了 length 属性,其他的属性都只能是数字,否者结果为空
* 而且数字不能超过 length 属性值, 否者超过 length 值的属性名对应的值将不显示
/*
let obj1 = {
0: '张三',
1: 20,
length: 2
}
console.log(Array.prototype.slice.call(obj1, 0)) // ['张三', 20]
let obj2 = {
0: '李四',
1: 20
}
console.log(Array.prototype.slice.call(obj2, 0)) // []
- 返回数据结构的类型 (Object.prototype.toString.call),解决 typeof 只能准确判断基本类型的弊端
let string = '大不了从头再来'
let boolean = true
let number = 10
let nullType = null
let undefinedType = undefined
let object = { name: '张三'}
console.log(typeof string, Object.prototype.toString.call(string)) // string [object String]
console.log(typeof boolean, Object.prototype.toString.call(boolean)) // boolean [object Boolean]
console.log(typeof number, Object.prototype.toString.call(number)) // number [object Number]
console.log(typeof nullType, Object.prototype.toString.call(nullType)) // object [object Null]
console.log(typeof undefinedType, Object.prototype.toString.call(undefinedType)) // undefined [object Undefined]
console.log(typeof object, Object.prototype.toString.call(object)) // object [object Object]
- 实现函数的继承
function Product (name, price) {
this.name = name
this.price = price
}
function Food (name, price) {
this.category = 'food'
// 此 this 指 Food, 通过改变 this 指向, 使 Product 里的 this 指向 Food
// 从而实现 Food 拥有 Product 里的 name 属性和 price 属性,也就实现了继承
Product.call(this, name, price)
}
let food = new Food('cheese', 5)
console.log(food.name) // cheese
bind 作用
- 改变 this 指向
var name = '我是 window 对象'
function getName (arg1, arg2) {
console.log(`${arg1} ${arg2}, ${this.name}`)
}
let obj = {
name: '我是 obj'
}
getName('你好', '朋友') // 你好 朋友, 我是 window 对象
let getNewName = getName.bind(obj, '你好', '朋友')
getNewName() // 你好 朋友, 我是 obj
- 偏函数(使一个函数拥有预设的初始参数)
function list() {
return Array.prototype.slice.call(arguments);
}
function addArguments(arg1, arg2) {
return arg1 + arg2
}
var list1 = list(1, 2, 3); // [1, 2, 3]
var result1 = addArguments(1, 2); // 3
// 创建一个函数,它拥有预设参数列表。
var leadingThirtysevenList = list.bind(null, 37);
// 创建一个函数,它拥有预设的第一个参数
var addThirtySeven = addArguments.bind(null, 37);
var list2 = leadingThirtysevenList();
// [37]
var list3 = leadingThirtysevenList(1, 2, 3);
// [37, 1, 2, 3]
var result2 = addThirtySeven(5);
// 37 + 5 = 42
var result3 = addThirtySeven(5, 10);
// 37 + 5 = 42,第二个参数被忽略
手写 apply
// apply 方法是属于函数原型上的方法,因此 myApply 绑定到函数原型上
Function.prototype.myApply = function (thisObj, args) {
// 此 this 就是调用 myApply 的函数,如果不是函数,抛出异常
if (typeof this !== 'function') {
throw new TypeError('not a function')
}
// thisObj 就是改变 this 指向的对象,
// 如果thisObj为 null 或 undefined, thisObj 指向 window
if (thisObj === null || thisObj === undefined) {
thisObj = window
} else {
// thisObj如果是原始值,转换为原始对象的实例对象, 如果是对象,总是返回对象
thisObj = Object(thisObj)
}
let fn = Symbol() // 避免和thisObj对象上原来的属性重名
thisObj[fn] = this // 在thisObj对象上绑定fn,并把this对象帮定到fn属性上(this 就是调用myApply的函数)
// 将第二个参数开始的所有参数以参数列表的方式传入,并保留执行的结果(相当于执行了调用myApply的函数)
// 由于此时的fn是thisObj的属性,故fn函数中的this指向是thisObj,即调用myApply传入的第一个参数的对象
// 从而达到改变this指向的功能(fn函数中this是指向thisObj而thisObj是自己传入的对象)
let result = thisObj[fn](...args)
delete thisObj[fn] // 删除thisObj 的属性fn
return result
}
手写 call
// call 方法是属于函数原型上的方法,因此 myCall 绑定到函数原型上
Function.prototype.myCall = function (thisObj, ...args) {
// 此 this 就是调用 myCall 的函数,如果不是函数,抛出异常
if (typeof this !== 'function') {
throw new TypeError('not a function')
}
// thisObj 就是改变 this 指向的对象,
// 如果thisObj为 null 或 undefined, thisObj 指向 window
if (thisObj === null || thisObj === undefined) {
thisObj = window
} else {
// thisObj如果是原始值,转换为原始对象的实例对象, 如果是对象,总是返回对象
thisObj = Object(thisObj)
}
let fn = Symbol() // 避免和thisObj对象上原来的属性重名
thisObj[fn] = this // 在thisObj对象上绑定fn,并把this对象帮定到fn属性上(this 就是调用 myCall 的函数)
// 将第二个参数开始的所有参数以参数列表的方式传入,并保留执行的结果(相当于执行了调用myCall的函数)
// 从而达到改变this指向的功能(fn函数中this是指向thisObj而thisObj是自己传入的对象)
let result = thisObj[fn](...args)
delete thisObj[fn] // 删除thisObj 的属性fn
return result
}
手写 bind
// bind 方法是属于函数原型上的方法,因此 myBind 绑定到函数原型上
Function.prototype.myBind = function (thisObj, ...args) {
if (typeof this !== 'function') {
throw new TypeError('not a function')
}
// 将原函数进行保存(调用myBind的函数)
const _this = this
// 定义需要返回新函数(新函数和原函数一样,也可以接受参数)
let funBind = function(...secondArgs) {
// 判断返回的新函数funBind是不是作为构造函数使用
// this 是否是funBind的实例,也就是返回funBind是否通过new调用
const isNew = this instanceof funBind
// new调用就绑定到this上,否则就绑定到传入的thisObj上
const context = isNew ? this : Object(objThis)
// 新函数的返回结果要与原函数一致,因此原函数调用call 传入this指向对象,并传入myBind的入参和新函数的入参数
return _this.call(context, ...args, ...secondArgs)
}
// 判断原函数有无prototype
if (_this.prototype) {
// 将原函数的prototype 赋给funBind
funBind.prototype = Object.create(_this.prototype)
}
// 返回新的函数
return funBind
}