1.回顾函数对象的原型关系
当我们创建一个函数, 这个函数就可以使用apply、call、bind方法
function foo() {}
foo.apply()
foo.call()
foo.bind()
我们知道foo的隐式原型是绑定在Function的显式原型上的
function foo() {}
console.log(foo.__proto__ === Function.prototype) // true
显然apply、call、bind方法是来自Function的原型对象
console.log(Function.prototype.apply); // ƒ apply()
console.log(Function.prototype.call); // ƒ call()
console.log(Function.prototype.bind); // ƒ bind()
意味着, 在Function的原型对象上添加的属性或方法, 可以被所有的函数获取
function foo() {}
Function.prototype.address = "成都市"
Function.prototype.bar = function() {
console.log("bar函数被调用了")
}
console.log(foo.address)
foo.bar()
2.手写apply函数
-
获取thisArg, 我们要确保是一个对象类型
用户在传参时, 可能会传入”abc”, 123, undefined, null, 我们需要对这些进行边界判断
function foo(name, age) { console.log(this, name, age); } Function.prototype.myapply = function (thisArg) { // 进行边界判断 thisArg = (thisArg === undefined || thisArg === null) ? window : Object(thisArg); }; foo.myapply("abc");
-
将this绑定到传入的参数thisArg上
function foo(name, age) { console.log(this, name, age); } Function.prototype.myapply = function (thisArg) { thisArg = thisArg === undefined || thisArg === null ? window : Object(thisArg); // 当读取thisArg中的fn时返回this 相当于thisArg.fn = this Object.defineProperty(thisArg, "fn", { configurable: true, value: this, }); // 通过隐式绑定, 对this进行调用, 让this指向thisArg thisArg.fn(); // 删除thisArg中的fn属性 delete thisArg.fn; }; foo.myapply({ name: "kaisa" });
-
将剩余的其他参数传入thisArg
function foo(name, age) { console.log(this, name, age); // {name: 'kaisa'} 'kaisa' 18 } Function.prototype.myapply = function (thisArg, arrArgs) { thisArg =thisArg === undefined || thisArg === null ? window : Object(thisArg); Object.defineProperty(thisArg, "fn", { configurable: true, value: this, }); // 通过展开运算符, 将其他参数传入thisArg thisArg.fn(...arrArgs); delete thisArg.fn; }; foo.myapply({ name: "kaisa" }, ["kaisa", 18]);
3.手写call函数
call函数与apply的实现是类似的, 不同的是call传入的其他参数是参数列表, apply是一个数组
-
获取thisArg, 我们要确保是一个对象类型
function foo(name, age) { console.log(this, name, age); } Function.prototype.mycall = function (thisArg) { // 进行边界判断 thisArg = (thisArg === undefined || thisArg === null) ? window : Object(thisArg); }; foo.mycall("abc");
-
将this绑定到传入的参数thisArg上
function foo(name, age) { console.log(this, name, age); } Function.prototype.mycall = function (thisArg) { // 进行边界判断 thisArg = (thisArg === undefined || thisArg === null) ? window : Object(thisArg); thisArg.fn = this // 隐式绑定让this指向thisArg thisArg.fn() delete thisArg.fn }; foo.mycall("abc");
-
将剩余的其他参数传入thisArg
function foo(name, age) { console.log(this, name, age); // {name: 'kaisa', fn: ƒ} 'kaisa' 18 } Function.prototype.mycall = function (thisArg, ...otherArgs) { thisArg = (thisArg === undefined || thisArg === null) ? window : Object(thisArg) thisArg.fn = this // 将剩余参数传入thisArg thisArg.fn(...otherArgs) delete thisArg.fn }; foo.mycall({ name: "kaisa" }, "kaisa", 18);
4.封装函数实现apply和call
我们发现实现自己的apply和call函数时, 有许多重复代码, 我们可以将重复代码封装成一个函数(封装思想很重要)
function execFn(thisArg, otherArgs, fn) {
thisArg = (thisArg === undefined || thisArg === null) ? window : Object(thisArg);
Object.defineProperty(thisArg, "fn", {
configurable: true,
value: fn,
});
thisArg.fn(...arrArgs);
delete thisArg.fn;
}
这样我们可以利用封装的函数快速实现自己的apply和call方法
Function.prototype.myapply = function (thisArg, otherArgs) {
execFn(thisArg, otherArgs, this);
};
Function.prototype.mycall = function (thisArg, ...otherArgs) {
execFn(thisArg, otherArgs, this);
};
测试代码
function execFn(thisArg, otherArgs, fn) {
thisArg = (thisArg === undefined || thisArg === null) ? window : Object(thisArg);
Object.defineProperty(thisArg, "fn", {
configurable: true,
value: fn,
});
thisArg.fn(...otherArgs);
delete thisArg.fn;
}
Function.prototype.myapply = function (thisArg, otherArgs) {
execFn(thisArg, otherArgs, this);
};
Function.prototype.mycall = function (thisArg, ...otherArgs) {
execFn(thisArg, otherArgs, this);
};
function foo(name, age) {
console.log(this, name, age); // {name: 'kaisa'} 'kaisa' 18
}
function bar(name, age) {
console.log(this, name, age); // String {'aaa'} 'coder' 15
}
foo.myapply({ name: "kaisa" }, ["kaisa", 18]);
bar.mycall("aaa", "coder", 15);
5.手写bind函数
bind与apply、call的区别是, 调用bind函数会返回一个新的函数, 同时返回的新的函数中依然可以传参数
源代码
function foo(name, age, height) {
console.log(this, name, age, height);
}
// 封装自己的bind
Function.prototype.mybind = function (thisArg, ...otherArgs) {
// 边界判断
thisArg = thisArg === undefined || thisArg === null ? window : Object(thisArg);
Object.defineProperty(thisArg, "fn", {
configurable: false,
value: this,
});
// 返回一个新的函数
return (...newArgs) => {
var allArgs = [...otherArgs, ...newArgs];
thisArg.fn(...allArgs);
};
};
var newFoo = foo.mybind({ name: "kaisa" }, "coder", 18);
newFoo(1.88);
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/120136.html