Function.prototype.call()
💪 作用:
可实现继承、方法的绑定、this值的绑定等,具体看示例
🤔 区别:
该方法的语法和作用与 apply()
方法类似,但就是只有一个区别:
就是 call()
方法接受的是一个参数列表,
而 apply()
方法接受的是一个包含多个参数的数组
⚠️ 注意:当没有传入任何参数作为this值时,this的值将会绑定为全局对象。(严格模式下为undefined)
🏠 返回值:
调用call方法的函数的返回值,如果没有返回值,则返回 undefined。
⭐️ 用法示例:
1、用call调用父构造函数,实现继承
function Father(home, hobby){
this.home = home
this.hobby = hobby
}
function Son(home, hobby, weight, dead){
father.call(this, home, hobby, weight)
this.weight = weight
}
console.log(new Son('Shen Zhen','football','60kg','dead'))
// Son {home: 'Shen Zhen', hobby: 'football', weight: '60kg'}
// 可以看到,Son的home和hobby都继承了Father的,
// 而Son也拥有着自己的weight,但是father和Son都没有让他们自己的this.dead = dead,
// 所以Son也没有dead。
2、用call调用匿名函数,可以让数组元素作为this值,这样每个数组元素就都拥有了匿名函数内的方法
const studentsArr = [{a:'小红', b:'小蓝'},{a:'小紫', b:'小绿'}]
for(let i = 0; i < studentsArr.length; i++){
// 匿名函数,立即调用
(function(i){
this.get = function(){
console.log(`第${i}组的人有${JSON.stringify(studentsArr[i])}`)
}
// 每个数组元素调用get方法
this.get()
}).call(studentsArr[i],i)
// 调用call方法,传入数组的每个元素作为this值,传入索引i作为argument。
// 在匿名函数内让this绑定get方法,即让每个数组元素都有了get方法。
}
// 第0组的人有{"a":"小红","b":"小蓝"}
// 第1组的人有{"a":"小紫","b":"小绿"}
3、改变函数的this指向
function boyFriend(){
console.log(this.love)
return 'xxx'
}
const girlFriend = {
love: 'each other'
}
boyFriend.call(girlFriend) // 返回值为 'xxx'
// each other
// 当然这里girlFriend.call(boyFriend)是会报girlFriend.call is not a function的(笑)
⭐️ 原理及实现思路:
在Function.prototype上定义myCall方法,保存新对象(context)的function,再给新对象(context)指定一个调用myCall方法的function作为需要调用的函数,结束调用完之后将新对象(context)的function还原。注意入参及返回值,比如call方法的第一个参数是thisArg,第二个及之后的参数则为指定的参数列表。
⭐️ 具体代码:
Function.prototype.myCall = function(context){
// 这里不需要再判断是因为我们在Function.prototype上定义的myCall
// if(typeof this !== 'function'){
// throw new TypeError('Not a Function')
// }
ctx = ctx || window
const tempFunction = ctx.function
ctx.function = this
let args = Array.from(arguments).slice(1)
const result = ctx.function(...args)
ctx.function = tempFunction
return result
}
⭐️ 结合例子理解:
// 结合以下例子进行理解
function boyFriend() {
console.log(arguments) // 这里arguments即myCall里的args
return 'returnValue'
}
const girlFriend = {
love: 'each other',
function: function() {
console.log('function')
}
}
const otherFriends = {
noLove: 'otherFriends'
}
Function.prototype.myCall = function (context) {
// 不传参数context(上下文)默认为window
context = context || window
// context: { love: 'each other', function: [Function: boyFriend] }
// 保存ctx.function
const tempFunction = ctx.function
// 保存this,this指向[Function: boyFriend],即boyFriend这个函数
context.function = this
// 保存参数arguments为args,arguments是一个对应于传递给函数的参数的类数组对象。
// 同时用Array.from把(伪数组)对象转为数组
const args = Array.from(arguments).slice(1)
// [Arguments] {
// '0': { love: 'each other', function: [Function: boyFriend] },
// '1': { noLove: 'otherFriends' }
// }
// 如果不slice,则boyFriend的console.log(arguments)会输出以上Arguments
// 和call方法有出入。
// 使用新对象(context)来调用函数
const result = context.function(...args)
// 还原ctx.function
ctx.function = tempFunction
return result
}
const myCallReturn = boyFriend.myCall(girlFriend, otherFriends)
console.log(myCallReturn) // 返回值为 'returnValue'
console.log(girlFriend.function) // [Function: function]
console.log('compared with call method.')
const callReturn = boyFriend.call(girlFriend, otherFriends)
console.log(callReturn) // 返回值为 'returnValue'
console.log(girlFriend.function) // [Function: function]
// 输出为
// [Arguments] { '0': { noLove: 'otherFriends' } }
// returnValue
// [Function: function]
// compared with call method.
// [Arguments] { '0': { noLove: 'otherFriends' } }
// returnValue
// [Function: function]
📖 友情链接:
Function.prototype.call() - JavaScript | MDN
Arguments 对象 - JavaScript | MDN
Function.prototype.apply()
⚠️ 建议先看call方法,再看apply方法
💪 作用:
可实现节省循环的数组合并、将函数和一个对象构造器链接起来等,具体看示例
🤔 区别:
该方法的语法和作用与 apply()
方法类似,但就是只有一个区别:
就是 call()
方法接受的是一个参数列表,
而 apply()
方法接受的是一个包含多个参数的数组
⚠️ 注意:当没有传入任何参数作为this值时,this的值将会绑定为全局对象。(严格模式下为undefined)
🏠 返回值:
调用apply
方法的函数的返回值。比如push返回数组新的length,那么apply也返回数组新的length。
⭐️ 用法示例:
1、用 apply
将数组各项添加到另一个数组
const arr_1 = [1,2,3]
const arr_2 = [4,5,6]
arr_1.push.apply(arr_1,arr_2)
// 6
// 返回值是push的返回值——数组长度
console.log(arr_1)
// [1, 2, 3, 4, 5, 6]
// 然鹅一般我都是这样写的
arr_1.push(...arr_2)
console.log(...arr_2)
// 4 5 6
console.log(arr_1)
// [1, 2, 3, 4, 5, 6]
// 对于一些需要写循环以遍历数组各项的需求,我们可以用 `apply` 完成以避免循环。
// 比如求最小最大值,但还是要注意:如果按上面方式调用 `apply`,有超出 JavaScript 引擎参数长度上限的风险。
2、 将函数和一个对象构造器链接起来
// 在Function的prototype原型上定义一个construct(构造)方法
Function.prototype.construct = function (aArgs) {
// 创建一个对象构造器
let oNew = Object.create(this.prototype);
// 使用apply将对象构造器和参数链接起来
this.apply(oNew, aArgs);
// 返回构造器
return oNew;
};
// 定义函数MyConstructor
function MyConstructor() {
for (let nProp = 0; nProp < arguments.length; nProp++) {
this['property' + nProp] = arguments[nProp];
}
}
let myArray = [4, 'Hello world!'];
// 让MyConstructor函数调用函数原型上的construct方法
let myInstance = MyConstructor.construct(myArray);
// 输出第n个属性,myInstance的原型是否为MyConstructor的原型,以及myInstance的构造器
console.log(myInstance.property0); // logs 4
console.log(myInstance.property1); // logs 'Hello world!'
console.log(myInstance instanceof MyConstructor); // logs 'true'
console.log(myInstance.constructor); // logs 'MyConstructor'
// 这里因为MyConstructor.construct()出来的等于来myInstance,所以myInstance的constructor(构造器)是MyConstructor
⭐️ 原理及实现思路:
在Function.prototype上定义myApply方法,保存新对象(context)的function,再给新对象(context)来根据arguments的区别来给context的function传入不同的参数,结束调用完之后将新对象(context)的function还原。注意入参及返回值。
⭐️ 结合例子理解:
// 弄清楚和call的区别,第二个参数是一个数组或者类数组对象,那么就很好实现了
Function.prototype.myApply = function (context) {
context = context || window
const tempFunction = context.function
context.function = this
let result
if (arguments[1]) {
result = context.function(...arguments[1])
} else {
result = context.function()
}
context.function = tempFunction
return result
}
const array = ['a', 'b'];
const elements = [0, 1, 2];
array.push.myApply(array, elements);
console.info(array)
// [ 'a', 'b', 0, 1, 2 ]
// [Arguments] {
// '0': [ 'a', 'b', function: [Function: push] ],
// '1': [ 0, 1, 2 ]
// }
📖 友情链接:
Function.prototype.apply() - JavaScript | MDN
Function.prototype.bind()
💪 作用:
创建一个新的函数,可绑定新函数的this指向、使新函数拥有预设的参数等,具体看示例
⚠️ 注意:如果
bind
函数的参数列表为空,或者thisArg
是null
或undefined
,执行作用域的this
将被视为bind创建出来的新函数的thisArg
🏠 返回值:
返回一个原函数的拷贝,并拥有指定的 this
值和初始参数。
⭐️ 用法示例:
1、创建绑定this指向的函数(我们把MDN里的举例分开来看,会更清晰)
// 在浏览器中,this 指向全局的 "window" 对象
this.x = 9;
// 创建一个作用域存放值为81的x
const modules = {
x: 81,
getX: function() { return this.x; }
};
// 错误示范举例:在全局作用域中调用函数
const retrieveX = modules.getX; // this指向了全局的 "window" 对象
retrieveX(); // 9
// 正确示范举例:在modules里调用函数getX
modules.getX(); // 81
使用bind
// 在浏览器中,this 指向全局的 "window" 对象
this.x = 9;
const modules = {
x: 81,
getX: function() { return this.x; }
};
const retrieveX = module.getX;
// boundGetX是一个新的函数,他的this指向modules
const boundGetX = retrieveX.bind(modules);
boundGetX();
// 81
2、创建偏函数(拥有预设参数的函数,预设参数会在传入参数的前面)
function list() {
return Array.prototype.slice.call(arguments);
}
// 创建一个函数,它拥有预设参数列表。
const leadingThirtysevenList = list.bind(null, 37);
const list2 = leadingThirtysevenList();
// [37]
const list3 = leadingThirtysevenList(1, 2, 3);
// [37, 1, 2, 3]
function addArguments(arg1, arg2) {
return arg1 + arg2
}
const result1 = addArguments(1, 2); // 3
// 创建一个函数,它拥有预设的第一个参数
const add_37 = addArguments.bind(null, 37); //这里为了好看命名为add_37了
const result2 = add_37(5);
// 37 + 5 = 42
const result3 = add_37(5, 10);
// 37 + 5 = 42 ,第二个参数被忽略
3、“拯救”setTimeout的this指向
var xxx = 1 // 这里得用var才能把xxx放到window对象里,具体涉及到const和let的原理
// this指向了window对象
setTimeout(()=>{
const xxx = 2
console.log(this.xxx)
},1000)
// 1
使用bind
// 创建一个 Son 函数, son 属性设置为'love'
function Son() {
this.son = 'love';
}
// 在 Son 的原型上定义 father 函数
Son.prototype.father = function() {
// this指向Son实例
// 类的方法中this指向类的实例时,把this绑定到回调函数,就不会丢失Son这个类的实例的引用
window.setTimeout(this.mother.bind(this), 1000);
};
// 在son的原型上定义mother函数
Son.prototype.mother = function() {
console.log('we have ours ' + this.son + '!');
};
// 执行son原型上的father函数
const son = new Son()
son.father(); // father函数在一秒钟后,调用mother方法
// we have ours love!
4、覆盖new出来的原本的this
function Father(x, y) {
this.x = x;
this.y = y;
}
Father.prototype.toString = function() {
return this.x + ',' + this.y;
};
const father = new Father('age', '30');
father.toString(); // 'age,30'
const emptyObj = {};
const Mother = Father.bind(emptyObj, height/*x*/);
const son = new Mother(163);
son.toString(); // 'height,163'
son instanceof Father; // true
son instanceof Mother; // true
5、快捷调用
// 可以用 Array.prototype.slice
// 来将一个类似于数组的对象(array-like object)转换成一个真正的数组
const slice = Array.prototype.slice;
slice.apply(arguments); //arguments 是类似于数组的对象
// 用 bind()
// 在下面这段代码里面,slice 是 Function.prototype的 apply() 方法的绑定函数
// 并且将 Array.prototype.slice 方法作为 this 的值。
// 与前一段代码的 "slice" 效果相同
const unbindSlice = Array.prototype.slice;
const bindSlice = Function.prototype.apply.bind(unbindSlice);
bindSlice(arguments);
⭐️ 原理及实现思路:
通过Array.prototype.slice.call(arguments, 1)将arguments转成数组。返回函数,判断函数的调用方式,是否是被new出来的。new出来的话返回空对象,但是实例的_proto_指向this的prototype,完成函数柯里化。
⭐️ 具体代码:
Function.prototype.myBind = function (context) {
const _this = this
const args = Array.prototype.slice.call(arguments, 1)
return function Fun() {。
if (this instanceof Fun) {
return new _this(...args, ...arguments)
} else {
return _this.apply(context, args.concat(...arguments))
}
}
}
⭐️ 结合例子理解:
Function.prototype.myBind = function (context) {
const _this = this
// const args = arguments.slice(1) 这样写不行,因为arguments是一个伪(类)数组
const args = Array.prototype.slice.call(arguments, 1)
// const args = Array.prototype.slice.apply(arguments, [1]) 也可以这样写
// console.log(this) // [Function: getX]
// console.log(...args) // 34
// console.log(...arguments) // { x: 12, getX: [Function: getX] } 34
return function Fun() {
// instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
if (this instanceof Fun) {
// 如果this是new Fun这样new出来的,也就是说this的prototype在Fun的原型链上
// 则返回一个实例,且使用new创建出来的实例的_proto_指向_this的prototype,同时完成函数柯里化
return new _this(...args, ...arguments) // new了一个getX函数,它的arguments是...arguments
} else {
// 如果不是new出来的就改变this指向为context,同时完成函数柯里化
return _this.apply(context, args.concat(...arguments))
}
}
}
// 结合例子
this.x = 8 // 在浏览器中,this 指向全局的 "window" 对象
const example = {
x: 12,
getX: function() { return this.x }
}
// 创建一个新函数,把 'this' 绑定到 example 对象
const myBoundGetX = example.getX.myBind(example, 34)
console.log(myBoundGetX()) // 12
const boundGetX = example.getX.bind(example, 34)
console.log(boundGetX()) // 12
❓ 什么是函数柯里化?
在计算机科学中,柯里化(Currying)是指把(接受多个参数的)函数变换成一个接受单一参数(最初函数的第一个参数)的函数,并且返回(接受余下的参数且返回结果的)新函数的技术。
❓ instanceof 使用的例子?
function Year(year) {
this.year = year
}
const newYear = new Year(1998)
console.log(newYear instanceof Year)
// expected output: true
📖 友情链接:
Function.prototype.bind() - JavaScript | MDN