深入理解JavaScript的new关键字与自定义

298 阅读5分钟

前言:

在JavaScript的世界里,new操作符像是一个魔术般的存在,它赋予了我们创建对象的强大能力,尤其在面向对象编程中,通过new,我们可以根据构造函数,轻松地“克隆”出具有相似特性的多个实例,它丰富了我们的编程实践,又使得代码更加模块化、可复用了。我们将从零开始,手写一个模仿new操作符功能的函数,让我们一同进入手写new的精彩旅程,感受JavaScript编程的魅力,探索那些隐藏在日常编码之下的奇妙细节。准备好了吗?让我们开始吧!

感受new的功能特性

我们知道new可以让代码复用,那么它是怎么复用的呢?

function Dog(nickname){
    // console.log(this)
    this.nickname = nickname;
}
Dog.prototype.singing = function() {
    console.log('小狗汪汪')
}
Dog.prototype.walk = function() {
    console.log('小狗步伐')
}
let dog1 = new Dog('小黑');
dog1.walk();
dog1.singing();

这里我们定义了一个对象函数Dog,然后创建一个实例对象dog1 ,这就是用new来进行实例化的,让dog1可以用于Dog的方法。

image.png

但是如果我们有很多小狗呢?

let dog1 = new Dog('小黑');
dog1.walk();
dog1.singing();
console.log('----------------------')
let dog2 = new Dog('小白');
dog2.walk();
dog2.singing();
console.log('----------------------')
let dog3 = new Dog('阿黄');
dog3.walk();
dog3.singing();

我们用new一直创建就好了,每一个实例化对象都会继承到Dog的方法,这样就达到了复用的效果。

image.png

在JavaScript中,new关键字用于创建由构造函数定义的新对象。当使用new关键字调用一个函数时,这个函数会作为构造函数。

new关键字内部具体主要做了如下几步操作

  1. 创建新对象:new关键字首先会在内部创建一个空的JavaScript对象。

image.png

由于对象的创建是在堆内存中开辟一个新的空间,而接受函数返回值的对象dog2其实与new的对象Dog的存储地址一致,这也就是为什么创建dog2和dog3,他们的参数都一样,但是他们不是同一个对象的原因,他们分别指向不同的地址。

  1. 链接原型:新创建的对象的__proto__属性会被设置为构造函数的prototype属性所指向的对象。这样,新对象就可以通过原型链访问到构造函数原型上的方法和属性。

这里的__proto__prototype都是用于实现原型链的概念,存在于不同的上下文在中。 __proto__属性:是每一个JavaScript对象都拥有的内置属性,它指向这个对象的原型对象,如果调用了该对象没有的方法,则该对象会去它的原型对象上找该方法进行调用,类似java的继承关系,没找到就一直向上找,原型的末端通常是Object.prototype

prototype属性:函数对象的特有属性,它是一个对象。当新创建的对象的__proto__属性被设置为构造函数的prototype属性所指向的对象时,这个新创建的对象就可以与其原型对象共享方法了。

  1. 绑定this:构造函数内部的this被绑定到新创建的对象。这意味着在构造函数内部,this现在指向新创建的对象,允许构造函数初始化该对象的属性。 这个举个例子看一下:
let obj2 = function(name){
    this.name = name
}
obj2.prototype.getName = function(){
    console.log(this.name);
}

let obj3 = new obj2('小伟');
obj3.getName(); // 打印小伟

当我们obj3内部没name属性时,新创建的对象去它的原型上找,这个this是由指向obj3变为指向obj2了,所以打印小伟,但是如果在调用方法前,在obj3内添加name属性,再看结果:

let obj2 = function(name){
    this.name = name
}
obj2.prototype.getName = function(){
    console.log(this.name);
}

let obj3 = new obj2('小伟');
obj3.name = '小黄'
obj3.getName(); // 打印小黄

这样就很容理解this的绑定了。

  1. 执行构造函数:构造函数被调用,执行其中的代码,可以为新对象添加属性和方法。
  2. 返回对象:构造函数通常会隐式返回新创建的对象,除非构造函数明确返回一个非undefined的值。如果构造函数返回一个对象,那么这个对象将被new操作返回;否则,new操作返回新创建的对象。

自己手写new方法

看完上面,我们知道了new关键字的工作流程,下面的代码就会很容易接受了。

function Dog(nickname){
    this.nickname = nickname;
    console.log(this.nickname)
}
Dog.prototype.singing = function() {
    console.log('小狗汪汪')
}
Dog.prototype.walk = function() {
    console.log('小狗步伐')
}
// 手写new的过程
// es6 reset 运算符(...运算符 ,剩下的全部给它)
function myNew(Fun,...args){
    
    let obj = {};  //对象,内存在 堆内存之中 地址引用
    Fun.apply(obj,args);
    obj.__proto__ = Fun.prototype; // 原型对象
    return obj;
}
let dog1 = myNew(Dog,'旺财','1岁');
dog1.walk()
dog1.singing();
console.log('----------------------')
let dog2 = new Dog('小白');
dog2.walk()
dog2.singing();

image.png

这里的函数对象特有的prototype属性可以为函数添加方法,可以让它的实例共享这些方法。

...是一个运算符,这里的意思是除了第一个参数,后面的参数全部打包在一个args的数组内,也就是说,第一个参数是Dog函数的对象,后面args是数组。

Fun.apply(obj, args)这一行代码扮演着至关重要的角色,它实现了构造函数Fun对新创建对象obj的初始化。

它的作用是:

  • obj作为Fun函数执行时的上下文(即Fun函数内的this将指向obj)。
  • myNew函数接收到的参数以数组形式传递给Fun,让Fun能够正确处理这些参数。 image.png

运行结果和new一个对象,一毛一样!!

这就是我们熟悉的new关键字哦,它的原理你学会了吗?如果文章对你的学习有帮助,请点赞收藏加关注哦!