建站教程

建站教程

Products

当前位置:首页 > 建站教程 >

Javascript的New、Apply、Bind、Call知多少(手动实现js中的call、bind、instanceof的详细操作)

GG网络技术分享 2025-03-18 16:14 0


Javascript的New、Apply、Bind、Call知多少

作者:一川来源:前端万有引力

1 写在前面

Javascript中的apply、call、bind方法是前端代码开发中相当重要的概念,并且与this的指向密切相关。本篇文章我们将深入探讨这个关键词的作用,并尝试进行手动编写复现。

阅读文章前,我们带着几个问题进行研究:

  • 用什么样的思路可以new关键字?
  • apply、bind、call这三个方法有什么区别?
  • 怎样手动实现一个apply、bind和call?

2 new关键词

new关键词的作用是执行一个构造函数,返回一个实例对象,根据构造函数的情况来确定是否可以接受参数的传递。

2.1 new的原理

使用new进行实例化对象,其步骤是:

  1. 创建一个新的空对象,即{}
  2. 将该对象构造函数的作用域赋给新对象,this指向新对象(即将新对象作为this的上下文)
  3. 执行构造函数中的代码,为这个新对象添加属性
  4. 如果该对象构造函数没有返回对象,则返回this

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语句指定的对象。

2.2 手写new的实现

new被调用后大致做了哪些事情?

  1. 让实例可以访问私有属性
  2. 让实例可以访问构造函数原型(constructor.prototype)所在原型链上的属性
  3. 构造函数返回的最后结果是引用数据类型

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;

};

3 apply、bind以及call

apply、bind和call是挂载Function对象上的三个方法,调用这三个方法的必须是一个函数。

3.1 apply

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;

}

3.2 bind

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;

}

补充说明:

  • this instanceof fun返回为true时,表示的是fun是一个构造函数,其this指向实例,直接将context作为参数输入
  • this instanceof fun返回为false时,表示的是fun是一个普通函数,其this指向顶级对象window,将绑定函数的this指向context对象

当然,我们也可以写成这种形式:

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的浏览器是不支持的。

3.3 call

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;

}

4 参考文章

  • 《解析 bind 原理,并手写 bind 实现》
  • 《解析 call/apply 原理,并手写 call/apply 实现》
  • 《Javascript核心原理精讲》

5 写在最后

在这篇文章中,我们知道apply、bind、call的区别在于:

  • apply、call改变了this指向后,会立即进行调用函数,返回的是执行结果
  • bind在改变this指向后,返回的是一个函数,需要另外再进行调用一次
  • bind、call传递的第一个参数都是this将要指向的对象,后面都是一个一个参数的形式输入
  • apply传递的第一个参数也是this将要指向的对象,后面传递的第二个参数是一个参数数组

手动实现js中的call、bind、instanceof的详细操作

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的详细内容,更多请关注网站的其它相关文章!

手动实现js中的call、bind、instanceof的详细操作 (https://www.wpmee.com/) javascript教程 第1张

标签:

提交需求或反馈

Demand feedback