>  기사  >  웹 프론트엔드  >  JavaScript: 이번에는 new 연산자를 완전히 이해했습니다!

JavaScript: 이번에는 new 연산자를 완전히 이해했습니다!

coldplay.xixi
coldplay.xixi앞으로
2020-09-27 17:30:371658검색

JavaScript: 이번에는 new 연산자를 완전히 이해했습니다!

머리말

JavaScript을 학습하는 과정에서 필연적으로 new 연산자를 접하게 됩니다. 이번에는 좀 더 자세히 살펴보고 이해와 기억을 깊게 해보겠습니다. new操作符,这次就来好好刨根问底一下,也算是加深理解和记忆了。

什么是new操作符?

mdn中是这么定义new操作符的:

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。

在这句话里我们来看一个关键词:具有构造函数。这是个什么意思呢?我们先通过几个例子来看一下:

//例1let Animal1=function(){this.name=1};let animal=new Animal1; //这里不带()相当于不传参数//=>Animal1 {name: 1}//例2let TestObj={}let t1=new TestObj;//=>Uncaught TypeError: TestObj is not a constructor复制代码

我们可以看到,例1成功的执行了new语句,创建出了实例。例2在new一个{}对象时报错TypeError: TestObj is not a constructor,指出目标不是一个constructor。为什么普通的对象就不能执行new操作符呢?在ECMA规范里有相关的介绍:

If Type(argument) is not Object, return false.
If argument has a [[Construct]]

새 연산자는 무엇인가요?

new 연산자는 mdn에서 다음과 같이 정의됩니다:

new 연산자는 생성자를 사용하여 사용자 정의 객체 유형의 인스턴스 또는 내장 객체의 인스턴스를 생성합니다.
  • 이 문장에서는 생성자가 있습니다라는 키워드를 살펴보겠습니다. 이것은 무엇을 의미합니까? 먼저 몇 가지 예를 살펴보겠습니다.
    //例3let testObj={
        Fn(){        console.log("构造成功!")
        }
    }let t3=new testObj.Fn;//=>Uncaught TypeError: testObj.Fn is not a constructor复制代码
  • 예 1이 new 문을 성공적으로 실행하고 인스턴스를 생성한 것을 볼 수 있습니다. 예시 2 {} 객체가 TypeError: TestObj가 생성자가 아닙니다 오류를 보고하는 경우, 이는 대상이 가 아님을 나타냅니다. 생성자. 왜 일반 객체는 new 연산자를 실행할 수 없나요? ECMA 사양에 관련 소개가 있습니다:
  • Type(argument)이 Object가 아니면 false를 반환합니다.
    인수에 [[Construct]] 내부 메서드가 있는 경우, 사실을 반환합니다. false를 반환합니다.[[Construct]]内部方法,才可以作为构造函数
     

我们这里的{}就是一个对象,满足第一个条件,那么显然,肯定是因为{}没有[[Construct]]这个内部方法,所以无法使用new操作符进行构造了。

那么我们已经搞定了new操作符的可操作对象,是不是可以去看看它的作用了呢?答案是:NO!我们再来看一个例子:

//例4const example = {  Fn: function() { console.log(this); },  Arrow: () => { console.log(this); },
  Shorthand() { console.log(this); }
};new example.Fn();        // Fn {}new example.Arrow();     // Uncaught TypeError: example.Arrow is not a constructornew example.Shorthand(); // Uncaught TypeError: example.Shorthand is not a constructor复制代码

what?为什么刚刚还能成功构造的函数,作为方法就不行了呢?其实在MDN中也有直接介绍:

Methods cannot be constructors! They will throw a TypeError if you try to instantiate them.

意思就是,方法不能是构造函数,如果尝试创建一个方法的实例,就会抛出类型错误。这样说就懂了,但是还没完,这个说法没有完全解释清楚原理,我们再看个例子:

function Animal(name){    this.name=name;    console.log("create animal");
}let animal=new Animal("大黄");  //create animalconsole.log(animal.name);       //大黄Animal.prototype.say=function(){    console.log("myName is:"+this.name);
}
animal.say();                   //myName is:大黄复制代码

对照这个例子,我们在ECMA规范查阅,发现所有的函数在创建时都取决于FunctionCreate函数:

FunctionCreate (kind, ParameterList, Body, Scope, Strict, prototype)

  1. If the prototype argument was not passed, then let prototype be the intrinsic object %FunctionPrototype%.
  2. If "kind" is not Normal, let allocKind be "non-constructor".

这个函数的定义可以看出

  • 只有当类型为Normal的函数被创建时,它才是可构造的函数,否则他就是不可构造的。

在我们这个例子中,Arrow的类型为Arrow,而ShortHand的类型是Methodnew操作符可以操作的目标,终于可以神清气爽的来看看它的作用了(不容易呀TAT)。

new操作符实现了什么?

我们举一个简单的例子来具体看看它的作用:

let animal=new Animal("大黄");复制代码

我们从这个例子来分析一下,首先我们看这一句:

function Animal(name){    this.name=name;    console.log("create animal");
}复制代码

可以看到,执行new操作符后,我们得到了一个animal对象,那么我们就知道,new操作符肯定要创建一个对象,并将这个对象返回。再看这段代码:

console.log(animal.name);       //大黄复制代码

同时我们看到结果,确实输出了create animal,我们就知道,Animal函数体在这个过程中被执行了,同时传入了参数,所以才执行了我们的输出语句。但我们的函数体里还有一句this.name=name体现在哪里呢?就是这一句:

Animal.prototype.say=function(){    console.log("myName is:"+this.name);
}
animal.say();                   //myName is:大黄复制代码

执行完函数体后,我们发现返回对象的name值就是我们赋值给this的值,那么不难判断,在这个过程中,this的值指向了新创建的对象。最后还有一段:

animal.__proto__===Animal.prototype; //true复制代码

animal对象调用的是Animal函数原型上的方法,说明Animalanimal对象的原型链上,那么在哪一层呢?我们验证一下:

function Animal(name){    this.name=name;    return 1;
}new Animal("test"); //Animal {name: "test"}复制代码

那我们就知道了,animal__proto__直接指向了Animalprototype

는 다음을 의미합니다. 🎜🎜🎜🎜생성자는 먼저 객체여야 합니다. 그렇지 않으면 조건이 충족되지 않습니다.🎜🎜🎜🎜두 번째로, 객체에는 [[Construct]]의 내부 메서드가 있어야 합니다. code> 생성자로 사용할 수 있습니다. 🎜 🎜🎜여기에 있는 <code>{}는 객체이고 첫 번째 조건을 충족하므로 당연히 {}에는 [[Construct]]가 내부 메서드이므로 new 연산자를 사용하여 생성할 수 없습니다. 🎜🎜이제 new 연산자의 동작 가능한 객체를 알아냈으니, 그 기능을 살펴볼까요? 대답은: 아니오! 또 다른 예를 살펴보겠습니다. 🎜
function Animal(name){    this.name=name;    return {};
}new Animal("test"); //{}复制代码
🎜뭐죠? 방금 성공적으로 구축한 함수가 왜 메소드로 작동하지 못하는 걸까요? 실제로 이는 MDN에도 직접 도입되었습니다:🎜
🎜메소드는 생성자가 될 수 없습니다! 인스턴스화하려고 하면 TypeError가 발생합니다.🎜🎜🎜즉, 🎜메소드는 생성자가 될 수 없습니다🎜. 메서드 인스턴스를 사용하면 유형 오류가 발생합니다. 이렇게 말하는 것이 타당하지만 아직 끝나지 않았습니다. 이 문은 원리를 완전히 설명하지 않습니다. 다른 예를 살펴보겠습니다. 🎜
var _myNew = function (constructor, ...args) {    // 1. 创建一个新对象obj
    const obj = {};    //2. 将this绑定到新对象上,并使用传入的参数调用函数

    //这里是为了拿到第一个参数,就是传入的构造函数
    // let constructor = Array.prototype.shift.call(arguments);
    //绑定this的同时调用函数,...将参数展开传入
    let res = constructor.call(obj, ...args)

    //3. 将创建的对象的_proto__指向构造函数的prototype
    obj.__proto__ = constructor.prototype

    //4. 根据显示返回的值判断最终返回结果
    return res instanceof Object ? res : obj;
}复制代码
🎜이 예와 비교하면 ECMA 사양을 확인하고 모든 기능이 다음에 의존한다는 것을 알았습니다. FunctionCreate
Function:🎜
🎜FunctionCreate (kind, ParameterList, Body, Scope, Strict, 프로토타입)🎜
    🎜프로토타입 인수가 전달되지 않은 경우 프로토타입을 실행합니다. 🎜🎜"kind"가 Normal이 아닌 경우 allocKind를 "non-constructor"로 둡니다.🎜
🎜🎜이 함수의 정의는 🎜🎜🎜🎜유형이 Normal입니다. 함수는 생성될 때 생성 가능하며, 그렇지 않으면 생성 가능하지 않습니다. 🎜🎜🎜이 예에서 Arrow 유형은 Arrow이고 ShortHand 유형은 Method이므로 생성 가능한 함수가 아닙니다. 이는 예제 3에서 "메서드는 생성자로 사용할 수 없습니다"라고 말하는 내용도 설명합니다. <code>new 연산자가 작동할 수 있는 대상을 파악한 후 마침내 명확한 마음으로 해당 기능을 살펴볼 수 있습니다(쉬운 TAT는 아님). 🎜

새 연산자는 무엇을 구현하나요? 🎜🎜그 기능을 자세히 보기 위해 간단한 예를 들어보겠습니다.🎜
function _new(fn, ...arg) {    const obj = Object.create(fn.prototype);    const res = fn.apply(obj, arg);    return res instanceof Object ? res : obj;复制代码
🎜이 예를 통해 분석해 보겠습니다. 먼저 이 문장을 살펴보겠습니다.🎜rrreee🎜보시다시피 new를 실행하세요. 연산 연산자 다음에 animal 객체를 얻은 다음 new 연산자가 객체를 생성하고 이 객체를 반환해야 한다는 것을 알 수 있습니다. 이 코드를 다시 보세요: 🎜rrreee🎜동시에 create Animal이 실제로 출력되는 것을 볼 수 있습니다. 여기서 Animal 함수 본문이 실행된다는 것을 알 수 있습니다. 매개변수가 동시에 전달되므로 출력 문이 실행됩니다. 그런데 함수 본문에서 this.name=name 문장은 어디에 반영되어 있나요? 문장은 이렇습니다:🎜rrreee🎜함수 본문을 실행한 후 반환된 개체의 name 값이 this에 할당한 값이므로 그렇지 않다는 것을 발견했습니다. 이 과정에서 this의 값은 새로 생성된 객체를 가리킵니다. 끝에 또 다른 단락이 있습니다. 🎜rrreee🎜animal 개체는 Animal 함수 프로토타입에서 메서드를 호출하여 Animal이 animal code> 객체의 프로토타입 체인은 어느 레이어에 있나요? 확인해 보겠습니다: 🎜rrreee🎜그러면 animal__proto__Animalprototype을 직접 가리킨다는 것을 알 수 있습니다. 게다가 생성자의 본문에 값을 반환하면 어떤 일이 일어나는지 살펴보겠습니다. 🎜
function Animal(name){    this.name=name;    return 1;
}new Animal("test"); //Animal {name: "test"}复制代码

可以看到,直接无视了返回值,那我们返回一个对象试试:

function Animal(name){    this.name=name;    return {};
}new Animal("test"); //{}复制代码

我们发现返回的实例对象被我们的返回值覆盖了,到这里大致了解了new操作符的核心功能,我们做一个小结。

小结

new操作符的作用:

  • 创建一个新对象,将this绑定到新创建的对象
  • 使用传入的参数调用构造函数
  • 将创建的对象的_proto__指向构造函数的prototype
  • 如果构造函数没有显式返回一个对象,则返回创建的新对象,否则返回显式返回的对象(如上文的{}

模拟实现一个new操作符

说了这么多理论的,最后我们亲自动手来实现一个new操作符吧~

var _myNew = function (constructor, ...args) {    // 1. 创建一个新对象obj
    const obj = {};    //2. 将this绑定到新对象上,并使用传入的参数调用函数

    //这里是为了拿到第一个参数,就是传入的构造函数
    // let constructor = Array.prototype.shift.call(arguments);
    //绑定this的同时调用函数,...将参数展开传入
    let res = constructor.call(obj, ...args)

    //3. 将创建的对象的_proto__指向构造函数的prototype
    obj.__proto__ = constructor.prototype

    //4. 根据显示返回的值判断最终返回结果
    return res instanceof Object ? res : obj;
}复制代码

上面是比较好理解的版本,我们可以简化一下得到下面这个版本:

function _new(fn, ...arg) {    const obj = Object.create(fn.prototype);    const res = fn.apply(obj, arg);    return res instanceof Object ? res : obj;复制代码

大功告成!

总结

本文从定义出发,探索了new操作符的作用目标和原理,并模拟实现了核心功能。其实模拟实现一个new操作符不难,更重要的还是去理解这个过程,明白其中的原理。

更多相关免费学习推荐:javascript(视频)

위 내용은 JavaScript: 이번에는 new 연산자를 완전히 이해했습니다!의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.im에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제