一、Function.prototype.call()
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
说白了就是改变当前调用函数的this指向,并向调用函数中传参。
语法: function.call(thisArg, arg1, arg2, …)
call方法中也可以不传参,这时函数中的this在严格模式(use strick)下为 undefined ,非严格模式指向全局对象 window 。
// 严格模式
'use strick';
var name= 'zhangsan';
function display() {
console.log('name is ', this.name);
}
display.call(); // Uncaught TypeError: Cannot read properties of undefined (reading 'name')
// 非严格模式
var name= 'zhangsan';
function display() {
console.log('name is ', this.name);
}
display.call(); // name is zhangsan
手写call方法:
原理:foo.call(obj, a, b) 相当于 obj.foo(a, b)
逻辑梳理:
- 接受多个参数,第一个参数为this对象
- 若无传参时,this默认指向window
- 函数中若存在返回值时,return返回值
// ES6写法
Function.prototype.myCall = function (thisArg, ...args) {
// call必须由函数调用
if (typeof this !== 'function') {
throw new Error(`${this} must be a function`)
}
const fn = this;
thisArg = thisArg == null ? window : Object(thisArg);
args = args || [];
thisArg.fn = fn;
const result = thisArg.fn(...args);
delete thisArg.fn;
return result;
}
// ES5写法
Function.prototype.myCall = function () {
// call必须由函数调用
if (typeof this !== 'function') {
throw new Error(`${this} must be a function`)
}
var fn = this;
var args = [];
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i]); // 将arguments类数组转化为数组
}
var thisArg = args.shift(); // 获取this值,thisArg = args[0]
thisArg = thisArg == null ? window : Object(thisArg); // this为空时默认指定为window对象,否则包装为对象使用
thisArg.fn = fn; // 改变this指向
var result = args.length ? eval('thisArg.fn(' + args + ')') : thisArg.fn(); // 执行函数,获取返回结果
delete thisArg.fn; // 执行完成后删除添加的方法fn
return result;
}
二、Function.prototype.apply()
apply() 方法调用一个具有给定 this 值的函数,以及以一个数组(或一个类数组对象)的形式提供的参数。
apply 与 call 非常相似,不同之处在于提供参数的方式。apply 使用参数数组而不是一组参数列表。
语法: apply(thisArg) | apply(thisArg, argsArray)
// call()
fn.call(this, a, b);
// apply()
fn.apply(this, [a, b])
手写apply方法:
原理:foo.apply(obj, [a, b]) 相当于 obj.foo([a, b])
逻辑梳理:
- 接受2个参数,第一个参数为this对象,第二个参数(可忽略)为数组或类数组
- 若无传参时,this默认指向window
- 函数中若存在返回值时,return返回值
// ES6写法
Function.prototype.myApply = function (thisArg, args) {
// apply必须由函数调用
if (typeof this !== 'function') {
throw new Error(`${this} must be a function`)
}
var fn = this;
thisArg = thisArg == null ? window : Object(thisArg);
args = Array.isArray(args) ? args : [];
thisArg.fn = fn;
const result = thisArg.fn(...args);
delete thisArg.fn;
return result;
}
// ES5写法
Function.prototype.myApply = function (thisArg, args) {
// apply必须由函数调用
if (typeof this !== 'function') {
throw new Error(`${this} must be a function`)
}
var fn = this;
thisArg = thisArg == null ? window : Object(thisArg); // this为空时默认指定为window对象,否则包装为对象使用
args = args instanceof Array ? args : []; // 判断参数是否为数组,不为数组时使用空数组
thisArg.fn = fn; // 切换this指向到thisArg
const result = args.length ? eval('thisArg.fn(' + args + ')') : thisArg.fn(); // 执行函数,获取返回结果
delete thisArg.fn; // 执行完成后删除添加的方法fn
return result;
}
三、Function.prototype.bind()
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
语法: function.bind(thisArg[, arg1[, arg2[, …]]])
手写bind方法:
逻辑梳理:
- bind() 函数会创建一个新的绑定函数,绑定函数包装了原函数对象。调用绑定函数通常会导致执行包装函数。
- thisArg 调用绑定函数时作为 this 参数传递给目标函数的值。
- 如果使用new运算符构造绑定函数,则忽略thisArg,使用new构造函数的返回值规则。
- 如果 bind 函数的参数列表为空,或者 thisArg 是 null 或 undefined ,执行作用域的 this 将被视为新函数的 thisArg。
使用到的知识点:
- 闭包
- 函数柯理化、合并参数
- new构造函数
- 原型、原型链
- 改变this指向
// ES6写法
Function.prototype.myBind = function (thisArg, ...args) {
// bind必须由函数调用
if (typeof this !== 'function') {
throw new Error(`${this} must be a function`)
}
// this就是当前调用bind的函数
let fn = this;
const boundFn = function () {
// 合并参数
const concatArgs = [...args, ...arguments];
// 判断是否是new调用方式
if (this instanceof boundFn) {
// obj构造函数的返回值,注意这里的this为实例对象
const obj = fn.apply(this, concatArgs);
// 判断返回值是否为对象,是对象则返回,否则返回当前实例
if (obj instanceof Object) {
return obj;
} else {
return this;
}
} else {
// 不是new调用时,绑定this为传参对象thisArg
return fn.apply(thisArg, concatArgs);
}
}
if (fn.prototype) {
// 此处使用 Object.create ,是为了防止修改 boundFn.prototype 属性时,将 fn.prototype 的属性误修改
// Object.create 会隔离两个对象之间的修改关系,但能保持访问关系。
// 若直接使用 boundFn.prototype = fn.prototype,则为引用传递,不安全。
boundFn.prototype = Object.create(fn.prototype);
boundFn.prototype.constructor = fn; // 修正构造函数
}
return boundFn;
}
参考文章:
Function.prototype.bind() - JavaScript | MDN
面试让写一个“bind”函数,详解五层bind函数进阶写法,带你写出一个让面试官满意的 “bind” 函数
柯里化函数(Currying),什么是柯里化,为什么要进行柯里化,高级柯里化函数的实现
Object.create() 详解