Products
GG网络技术分享 2025-03-18 16:14 0
作者:一川来源:前端万有引力
Javascript中的apply、call、bind方法是前端代码开发中相当重要的概念,并且与this的指向密切相关。本篇文章我们将深入探讨这个关键词的作用,并尝试进行手动编写复现。
阅读文章前,我们带着几个问题进行研究:
new关键词的作用是执行一个构造函数,返回一个实例对象,根据构造函数的情况来确定是否可以接受参数的传递。
使用new进行实例化对象,其步骤是:
function Person(){
this.name = "yichuan"
}
const p = new Person();
console.log(p.name);//"yichuan"
我们可以看到当使用new进行实例化时,可以将构造函数的this指向新对象p。当不使用new时,此时构造函数的this指向window。
function Person(){
this.name = "yichuan"
}
const p = Person();
console.log(p);//undefined
console.log(name);//"yichuan" window.name
console.log(p.name);//"yichuan" is undefined
当我们在构造函数中直接返回一个和this无关的对象时,使用new关键字进行实例化对象,新生成的对象就是构造函数返回的对象,而非构造函数的this的对象。
function Person(){
this.name = "yichuan";
return {age:18};
}
const p = new Person();
console.log(p);//{age:18}
console.log(p.name);//"undefined"
console.log(p.age);/18
此外,当构造函数返回的不是一个对象,而是基础数据类型的值时,使用new创建新对象,会将构造函数返回的值以对象形式给新对象。
function Person(){
this.name = "yichuan";
return "onechuan";
}
const p = new Person();
console.log(p);//{name:"yichuan"}
console.log(p.name);//"yichuan"
new关键词执行之后总是会返回一个对象,要么是实例,要么是return语句指定的对象。
new被调用后大致做了哪些事情?
function new_object(ctor,...args){
//先要判断ctor是否为一个函数
if(typeof ctor !== "function"){
throw "ctor must be a function";
}
//创建一个空对象
const obj = new Object();
//将实例obj可以访问到ctor原型所在原型链的属性
obj.__proto__ = Object.create(ctor.prototype);
//将构造函数的this指向实例对象obj
const res = ctor.apply(obj,...args);
//确保最后new返回的是一个对象
const isObject = typeof res === "object" && typeof res!== null;
const isFunction = typeof res === "function";
return isObject || isFunction ? res : obj;
}
当然,我们还可以进行优化以下:
function new_object() {
// 1、获得构造函数,同时删除 arguments 中第一个参数
const ctor = [].shift.call(arguments);//其实这里是借用了数组的shift方法
// 2、创建一个空的对象并链接到原型,obj 可以访问构造函数原型中的属性
const obj = Object.create(ctor.prototype);
// 3、绑定 this 实现继承,obj 可以访问到构造函数中的属性
const ret =ctor.apply(obj, arguments);
// 4、优先返回构造函数返回的对象
return ret instanceof Object ? ret : obj;
};
apply、bind和call是挂载Function对象上的三个方法,调用这三个方法的必须是一个函数。
apply() 方法调用一个具有给定 this 值的函数,以及作为一个数组(或类似数组对象)提供的参数。apply()方法可以改变函数this的指向,且立即执行函数。
注意:Chrome 14 以及 Internet Explorer 9 仍然不接受类数组对象。如果传入类数组对象,它们会抛出异常。
func.apply(thisArg, [param1,param2,...]);
在使用apply时,会将func的this指向改变为指向thisArg,然后以[param1,param2,...]参数数组作为参数输入。
func(["red","green","blue"]);
func.apply(newFun, ["red","green","blue"]);
我们可以看到都执行func时,第一个func函数的this指向的是window全局对象,而第二个func函数的this指向的是newFun。
Function.prototype.apply = function (context, arr) {
context = context ? Object(context) : window;
context.fn = this;
let result;
//判断有没有参数数组输入
if (!arr) {
result = context.fn();
} else {
result = context.fn(...arr);
}
//此处也可以使用eval进行处理
// const result = eval("context.fn(...arr)");
delete context.fn
return result;
}
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
bind(thisArg,param1,param2,...);
其实,bind的实现思路基本和apply一致,但是在最后实现返回结果时,bind不需要直接执行,而是以返回函数的形式返回结果,之后再通过执行这个结果即可。
先分析下bind的特性:首先是指定新对象this指向,再传入参数返回一个定义的函数,最后使用柯里化进行调用。同样的,我们也可以根据这些特性进行手动封装一个bind函数:
Function.prototype.bind = function(context){
//先要判断调用bind函数的是不是函数,需要抛出异常
if(typeof this !== "function"){
throw new Error("this bind function must be userd to function");
}
//存储this的指向
const self = this;
//context是新对象this指向的目标对象,而参数就是在第一个参数之后的参数
const args = Array.prototype.slice.call(arguments,1);
//创建一个空对象
const fun = function(){}
//返回一个函数
const funBind = function(){
//返回所有的参数给bind函数
const bindArg = Array.prototype.slice.call(arguments);
//将传入的参数合并成一个新的参数数组,作为self.apply()的第二个参数
return self.apply(this instanceof fun ? this : context, args.concat(bindArgs));
/**********************说明************************************/
}
//空对象的原型指向绑定函数的原型
fun.prototype = this.prototype;
//空对象的实例赋值给 funBind.prototype
funBind.prototype = new fun();
return funBinf;
}
补充说明:
当然,我们也可以写成这种形式:
Function.prototype.bind = function(context,...args){
//先要判断调用bind函数的是不是函数,需要抛出异常
if(typeof this !== "function"){
throw new Error("this bind function must be userd to function");
}
//存储this的指向
const self = this;
const fBind = function(){
self.apply(this instanceof self ? this: context, args.concat(Array.prototype.slice.call(arguments)));
}
if(this.prototype){
fBind.prototype = Object.create(this.prototype);
}
return fBind;
}
注意:Object.create()是es2015语法引入的新特性,因此在IE<9的浏览器是不支持的。
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined。
function.call(thisArg, param1, param2, ...)
注意:该方法的语法和作用与 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组。
call函数的实现:
Function.prototype.call = function(context,...args){
//将函数设置为对象的属性
context = context || window;
context.fn = this;
//执行函数
const result = eval("context.fn(...args)");
//删除对象的这个属性
delete context.fn;
return result;
}在这篇文章中,我们知道apply、bind、call的区别在于:
s中call能够改变this的指向、bind能改变this的指向,并返回一个函数,这是怎么实现的呢?本文将带你一步步实现这些功能,希望对学习JavaScript的朋友有帮助。
现在的前端门槛越来越高,不再是只会写写页面那么简单。模块化、自动化、跨端开发等逐渐成为要求,但是这些都需要建立在我们牢固的基础之上。不管框架和模式怎么变,把基础原理打牢才能快速适应市场的变化。下面介绍一些常用的源码实现:
call实现 bind实现 new实现 instanceof实现 Object.create实现 深拷贝实现 发布订阅模式 |
call
call用于改变函数this指向,并执行函数
(推荐js教程,欢迎学习!)
一般情况,谁调用函数,函数的this就指向谁。利用这一特点,将函数作为对象的属性,由对象进行调用,即可改变函数this指向,这种被称为隐式绑定。apply实现同理,只需改变入参形式。
let obj = { name: \'JoJo\' } function foo(){ console.log(this.name) } obj.fn = foo obj.fn() // log: JOJO |
实现
Function.prototype.mycall = function () { if(typeof this !== \'function\'){ throw \'caller must be a function\' } let othis = arguments[0] || window othis._fn = this let arg = [...arguments].slice(1) let res = othis._fn(...arg) Reflect.deleteProperty(othis, \'_fn\') //删除_fn属性 return res } |
使用
let obj = { name: \'JoJo\' } function foo(){ console.log(this.name) } foo.mycall(obj) // JoJo |
bind
bind用于改变函数this指向,并返回一个函数
注意点:《作为构造函数调用的this指向维护原型链》
Function.prototype.mybind = function (oThis) { if(typeof this != \'function\'){ throw \'caller must be a function\' } let fThis = this //Array.prototype.slice.call 将类数组转为数组 let arg = Array.prototype.slice.call(arguments,1) let NOP = function(){} let fBound = function(){ let arg_ = Array.prototype.slice.call(arguments) // new 绑定等级高于显式绑定 // 作为构造函数调用时,保留指向不做修改 // 使用 instanceof 判断是否为构造函数调用 return fThis.apply(this instanceof fBound ? this : oThis, arg.concat(arg_)) } // 维护原型 if(this.prototype){ NOP.prototype = this.prototype } fBound.prototype = new NOP() return fBound } |
使用
let obj = { msg: \'JoJo\' } function foo(msg){ console.log(msg + \'\' + this.msg) } let f = foo.mybind(obj) f(\'hello\') // hello JoJo |
new
new使用构造函数创建实例对象,为实例对象添加this属性和方法
new的过程:
创建新对象 新对象__proto__指向构造函数原型 新对象添加属性方法(this指向) 返回this指向的新对象 |
function new_(){ let fn = Array.prototype.shift.call(arguments) if(typeof fn != \'function\'){ throw fn + \' is not a constructor\' } let obj = {} obj.__proto__ = fn.prototype let res = fn.apply(obj, arguments) return typeof res === \'object\' ? res : obj } |
instanceof
instanceof 判断左边的原型是否存在于右边的原型链中。
实现思路:逐层往上查找原型,如果最终的原型为null时,证明不存在原型链中,否则存在。
function instanceof_(left, right){ left = left.__proto__ while(left !== right.prototype){ left = left.__proto__ // 查找原型,再次while判断 if(left === null){ return false } } return true } |
Object.create
Object.create创建一个新对象,使用现有的对象来提供新创建的对象的__proto__,第二个可选参数为属性描述对象
function objectCreate_(proto, propertiesObject = {}){ if(typeof proto !== \'object\' || typeof proto !== \'function\' || proto !== null){ throw(\'Object prototype may only be an Object or null:\'+proto) } let res = {} res.__proto__ = proto Object.defineProperties(res, propertiesObject) return res } |
深拷贝
深拷贝为对象创建一个相同的副本,两者的引用地址不相同。当你希望使用一个对象,但又不想修改原对象时,深拷贝是一个很好的选择。这里实现一个基础版本,只对对象和数组做深拷贝。
实现思路:遍历对象,引用类型使用递归继续拷贝,基本类型直接赋值
function deepClone(origin) { let toStr = Object.prototype.toString let isInvalid = toStr.call(origin) !== \'[object Object]\' && toStr.call(origin) !== \'[object Array]\' if (isInvalid) { return origin } let target = toStr.call(origin) === \'[object Object]\' ? {} : [] for (const key in origin) { if (origin.hasOwnProperty(key)) { const item = origin[key]; if (typeof item === \'object\' && item !== null) { target[key] = deepClone(item) } else { target[key] = item } } } return target } |
发布订阅模式
发布订阅模式在实际开发中可以实现模块间的完全解耦,模块只需要关注事件的注册和触发。
发布订阅模式实现EventBus:
class EventBus{ constructor(){ this.task = {} } on(name, cb){ if(!this.task[name]){ this.task[name] = [] } typeof cb === \'function\' && this.task[name].push(cb) } emit(name, ...arg){ let taskQueen = this.task[name] if(taskQueen && taskQueen.length > 0){ taskQueen.forEach(cb=>{ cb(...arg) }) } } off(name, cb){ let taskQueen = this.task[name] if(taskQueen && taskQueen.length > 0){ let index = taskQueen.indexOf(cb) index != -1 && taskQueen.splice(index, 1) } } once(name, cb){ function callback(...arg){ this.off(name, cb) cb(...arg) } typeof cb === \'function\' && this.on(name, callback) } } |
使用
let bus = new EventBus() bus.on(\'add\', function(a,b){ console.log(a+b) }) bus.emit(\'add\', 10, 20) //30 |
以上就是手动实现js中的call、bind、instanceof的详细内容,更多请关注网站的其它相关文章!
Demand feedback