JavaScript을 학습하는 과정에서 필연적으로 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 연산자는 생성자를 사용하여 사용자 정의 객체 유형의 인스턴스 또는 내장 객체의 인스턴스를 생성합니다.
- 이 문장에서는
예 1이생성자가 있습니다
라는 키워드를 살펴보겠습니다. 이것은 무엇을 의미합니까? 먼저 몇 가지 예를 살펴보겠습니다.//例3let testObj={ Fn(){ console.log("构造成功!") } }let t3=new testObj.Fn;//=>Uncaught TypeError: testObj.Fn is not a constructor复制代码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)
- If the prototype argument was not passed, then let prototype be the intrinsic object %FunctionPrototype%.
- If "kind" is not Normal, let allocKind be "non-constructor".
这个函数的定义可以看出
- 只有当类型为
Normal
的函数被创建时,它才是可构造的函数,否则他就是不可构造的。在我们这个例子中,
Arrow
的类型为Arrow
,而ShortHand
的类型是Method
new
操作符可以操作的目标,终于可以神清气爽的来看看它的作用了(不容易呀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
函数原型上的方法,说明Animal
在animal
对象的原型链上,那么在哪一层呢?我们验证一下:function Animal(name){ this.name=name; return 1; }new Animal("test"); //Animal {name: "test"}复制代码那我们就知道了,
는 다음을 의미합니다. 🎜🎜🎜🎜생성자는 먼저 객체여야 합니다. 그렇지 않으면 조건이 충족되지 않습니다.🎜🎜🎜🎜두 번째로, 객체에는animal
的__proto__
直接指向了Animal
的prototype
[[Construct]]의 내부 메서드가 있어야 합니다. code> 생성자로 사용할 수 있습니다. 🎜 🎜🎜여기에 있는 <code>{}
는 객체이고 첫 번째 조건을 충족하므로 당연히{}에는 [[Construct]]
가 내부 메서드이므로new
연산자를 사용하여 생성할 수 없습니다. 🎜🎜이제new
연산자의 동작 가능한 객체를 알아냈으니, 그 기능을 살펴볼까요? 대답은: 아니오! 또 다른 예를 살펴보겠습니다. 🎜function Animal(name){ this.name=name; return {}; }new Animal("test"); //{}复制代码🎜뭐죠? 방금 성공적으로 구축한 함수가 왜 메소드로 작동하지 못하는 걸까요? 실제로 이는 MDN에도 직접 도입되었습니다:🎜🎜메소드는 생성자가 될 수 없습니다! 인스턴스화하려고 하면 TypeError가 발생합니다.🎜🎜🎜즉, 🎜메소드는 생성자가 될 수 없습니다🎜. 메서드 인스턴스를 사용하면 유형 오류가 발생합니다. 이렇게 말하는 것이 타당하지만 아직 끝나지 않았습니다. 이 문은 원리를 완전히 설명하지 않습니다. 다른 예를 살펴보겠습니다. 🎜Function:🎜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🎜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__
가Animal
의prototype
을 직접 가리킨다는 것을 알 수 있습니다. 게다가 생성자의 본문에 값을 반환하면 어떤 일이 일어나는지 살펴보겠습니다. 🎜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 중국어 웹사이트의 기타 관련 기사를 참조하세요!