call的语法
function.call(thisArg, arg1, arg2, ...),第一个参数是可选的在,function 函数运行时使用的 this 值。可选是因为假如没有指定函数的使用的this的时候,就为默认为运行环境的window(浏览器)或者node(global)。第二个参数为指定的参数列表,传入调用call的函数中。
实现代码
// 任何函数都可以调用call方法所以需要把call方法写在Function.prototype上
// 方法传入两个参数,第一参数为调用函数的this,第二个为调用这个函数传入的参数
Function.prototype.MyCall = function (thisArg = window, ...args) {
const prop = Symbol()
// 处理传入进来的函数,将它作为thisArg的一个属性
thisArg[prop] = this
let res = thisArg[prop](...args)
delete thisArg[prop]
return res
}
function fn(a, b) {
console.log(a, b)
console.log('this:', this)
return a + b
}
const obj = {
name: 'qingcc'
}
const res1 = fn.MyCall(obj,10,20)
console.log(res3)
// 构造函数
function Product(name, price) {
this.name = name;
this.price = price;
}
function Food(name, price) {
Product.call(this, name, price);
this.category = 'food';
}
function Toy(name, price) {
Product.call(this, name, price);
this.category = 'toy';
}
var cheese = new Food('feta', 5);
var fun = new Toy('robot', 40);
核心思路:
- 为传入的上下文对象扩展一个属性并将这个属性指向调用的函数
- 将上下文之外的所有参数传入这个新的属性
- 调用这个添加的属性
- 删除新属性
- 返回得到的结果
实现的细节
- ...args收集多余的参数并传入函数
- 使用Symbol为上下文添加新属性,Symbol是独一无二的不会影响上下文原来的属性值
apply的语法
function.apply(thisArg,argsArr),其语法几乎与call是一样的,只不过其第二参数为数组或者是类数组对象,而call的第二个参数是一个参数列表。
实现代码
// ----------------------------------------------------------------------手写apply的实现
function testApply(a = 12, b = 10) {
console.log('this:', this)
console.log(a, b)
return a + b
}
Function.prototype.MyApply = function (obj = window, arr) {
obj = (obj !== undefined && obj !== null) ? Object(obj) : window
const prop = Symbol()
obj[prop] = this
const argArr = arr || []
const res = obj[prop](...argArr)
delete obj[prop]
return res
}
const res = testApply.MyApply({ name: 'string', age: 18 }, [1, 2])
console.log(res)
bind的语法
bind和call与apply一样都会改变函数this的指向,但是bind不会执行而是会返回一个函数,这个函数的this指向就是apply绑定的对象,其内部可以通过apply绑定函数的this,这个绑定的this无法通过apply和bind进行修改。
函数柯里化的介绍:
//函数柯里化,函数调用的时候先传递一部分参数进行调用,函数返回新函数再处理剩下的参数
function fn(x, y) {
return function (y) {
console.log(x + y);
};
};
var fn_ = fn(0); // 先传递一个x参数
fn_(1); // 2 此时返回了一个新的fn_函数,在传入一个参数y处理剩余的参数
fn(1)(1) // 2
可以看出上边先给fn传递了一个参数,然后返回了一个fn_函数,然后给fn_又传递了一个参数,这样的话就相当于先给函数传递一部分参数调用,此例先传递了x进行参数调用,然后再给函数返回值传递参数y,新函数再处理剩余部分的参数。
版本一
版本一:实现了改变this指向的功能
Function.prototype.MyBind = function (obj) {
// 提前保存调用bind的函数
const self = this
return function () {
self.call(obj)
}
}
function testBind(a = 1, b = 2) {
console.log('this:', this.name);
return a + b
}
const bindObj = { name: 'qing', age: 18 };
const bindFun = testBind.MyBind(bindObj)
bindFun()
版本二
这个版本实现的bind改变了this的指向,但是可见其并不支持传参,修改之后使其支持参数传递的功能。
Function.prototype.MyBind = function (obj) {
const args = Array.prototype.splice.call(arguments,1)
const self = this
return function () {
self.call(obj,args)
}
}
但是这个版本只是支持简单的参数传递,不支持先传递一部分参数,然后再传递一部分函数也就是函数柯里化。
Function.prototype.MyBind = function (obj) {
// 获取MyBind传入的参数,这个类数组的第一位是this所以索引从1开始
const args = Array.prototype.slice.call(arguments, 1)
const self = this
return function () {
// 二次调的时候同样抓取参数
const params = Array.prototype.slice.call(arguments)
return self.apply(obj, args.concat(params))
}
}
那么我们测试一下
function testBind(a, b) {
console.log('this:', this.name);
console.log(a+b)
return a + b
}
const bindObj = { name: 'qing', age: 18 };
const bindFun = testBind.MyBind(bindObj,1)
// 这边加了一个对象的话会出现错误,因为调用返回的bind函数(MyBind)在取参数的时候把arguments和之前的参数都合并了,arguments第一项为这个对象。
console.log(bindFun({name:'change'},2));
这时候这个函数的this既不会失去绑定也完成参数调用以及简单的函数柯里化。还有一个问题是通过bind返回的函数仍然能够通过new 构造,这样通过this绑定的参数会自动失效,且返回的实例还是会继承构造函数的构造器属性与原型属性,并且能正常接收参数。