この記事では、新しい演算子の使用法を詳しく紹介します。一定の参考価値があります。困っている友人は参考にしてください。お役に立てれば幸いです。
フロントエンドに触れたばかりの多くのフロントエンド パートナー、あるいは数年間働いているフロントエンド パートナーであっても、新しいオペレーターについてまだ漠然と理解していると思います。
たとえば、私は最近、勤続 2 年のフロントエンド パートナーと連絡を取りました。彼は、オブジェクトの作成には new が使用されていると言いました。それは当然です。多くの人がこれに答えるかもしれません。
それでは、この答えは間違っているのでしょうか、それとも正しいのでしょうか?
この問題について包括的に議論しましょう:
オブジェクトを取得する方法はたくさんありますが、最も一般的なのはオブジェクト リテラルです:
var obj = {}
ただし、文法の観点から見ると、これは代入ステートメントで、逆のリテラル値を変数 obj に代入します (これはあまり正確ではないかもしれませんが、実際にはオブジェクトのインスタンスがここで取得されます!!)
多くの場合、私たちがオブジェクトを作成したいと言うと、多くの友人が両手でキーボードをタッチし、数回クリックするだけでこのコードを入力します。
上で述べたように、この文は実際にはオブジェクトのインスタンスを取得するだけですが、このコードはオブジェクトの作成と同等と考えてよいでしょうか?読み続けてみましょう。
オブジェクトのインスタンスを取得するには、オブジェクト リテラルに相当する別のメソッドがコンストラクターです。
var obj = new Object()
このコードを入力すると、誰もがすぐに感心すると思います。私が今言ったこと obj
が単なるインスタンス オブジェクトであるという事実に異論はありません。そうなると、多くの友人はまた尋ねるでしょう。「これは単に新しいオブジェクトが登場するだけではないのですか!」 はい、これは確かに新しいオブジェクトの作成です。JavaScript ではすべてがオブジェクトとして説明され、obj はオブジェクトであり、new 演算子を通じて取得されるため、多くの友人がそれを確信しています。言った: new はオブジェクトの作成に使用されます。
多くの人がオブジェクトの作成とオブジェクトのインスタンス化を混同していることを説明するのは難しくありません!!
別の見方をしてみましょう: js 内のすべてのものはオブジェクトであるのに、なぜオブジェクトを作成する必要があるのでしょうか。 ? ?それ自体がオブジェクトですが、どうやって作成できるのでしょうか?では、これを
継承 の一種と考えてよいでしょうか? ここまで言って、多くのパートナーは唖然としたと思いますが、私たちの目的は 1 つです。new がいわゆるオブジェクトの作成ではなく継承に使用されることを明確にすることです。 !
継承されたインスタンス オブジェクトの特徴は何ですか?
コンストラクター内のプロパティへのアクセスfunction Person(name, age) { this.name = name this.age = age this.gender = '男' } Person.prototype.nation = '汉' Person.prototype.say = function() { console.log(`My name is ${this.age}`) } var person = new Person('小明', 25) console.log(person.name) console.log(person.age) console.log(person.gender) console.log(person.nation) person.say()
call
またはapply
function Parent() { this.name = ['A', 'B'] } function Child() { Parent.call(this) } var child = new Child() console.log(child.name) // ['A', 'B'] child.name.push('C') console.log(child.name) // ['A', 'B', 'C']
__proto__
次に、上記のウォームアップ コードを少し変更して、new を使用せずにインスタンスを作成しましょう。
function Person(name, age) { this.name = name this.age = age this.gender = '男' } Person.prototype.nation = '汉' Person.prototype.say = function() { console.log(`My name is ${this.age}`) } // var person = new Person('小明', 25) var person = New(Person, '小明', 25) console.log(person.name) console.log(person.age) console.log(person.gender) console.log(person.nation) person.say() function New() { var obj = {} Constructor = [].shift.call(arguments) // 获取arguments第一个参数:构造函数 // 注意:此时的arguments参数在shift()方法的截取后只剩下两个元素 obj.__proto__ = Constructor.prototype // 把构造函数的原型赋值给obj对象 Constructor.apply(obj, arguments) // 改变够着函数指针,指向obj,这是刚才上面说到的访问构造函数里面的属性和方法的方式 return obj }
上記のコードの New 関数は、new 演算子です。実装
主な手順:
空のオブジェクトを作成しますしかし、私たちが見落としていることが 1 つあります。それは、js の関数には戻り値があります。も例外ではありません。
コンストラクターでオブジェクトまたは基本値を返す場合、上記の New 関数はどうなりますか?
コードをもう一度見てみましょう。
function Person(name, age) { this.name = name this.age = age this.gender = '男' return { name: name, gender: '男' } } Person.prototype.nation = '汉' Person.prototype.say = function() { console.log(`My name is ${this.age}`) } var person = new Person('小明', 25) console.log(person.name) console.log(person.age) console.log(person.gender) console.log(person.nation) person.say()
がコードを実行すると、
name と gender
の 2 つのフィールドだけが次のように出力されることがわかります。期待されています、age
、nation
は未定義です。say()
はエラーを報告します。 コード コンストラクターのコードを変更します:
function Person(name, age) { this.name = name this.age = age this.gender = '男' // return { // name: name, // gender: '男' // } return 1 } // ...
コードを実行し、最終的にすべてのフィールドが期待どおりに出力されることを確認します。
要約は次のとおりです:
コンストラクターが参照型を返す場合、コンストラクター内のプロパティは使用できず、返されたオブジェクトのみが使用できます。function Person(name, age) { // ... } function New() { var obj = {} Constructor = [].shift.call(arguments) obj.__proto__ = Constructor.prototype // Constructor.apply(obj, arguments) var result = Constructor.apply(obj, arguments) // return obj return typeof result === 'object' ? result : obj } var person = New(Person, '小明', 25) console.log(person.name) // ...
このコードを実行すると、上で要約した 2 つの点が達成されていることを確認できます。
解決策: 変数を使用してコンストラクターの戻り値を受け取り、New 関数で戻り値の型を決定し、型に応じて異なる値を返します。
ここを参照してください。別の友人は、「New は完全に実装されましたね」と言いました。 ! !答えは間違いなく「ノー」です。コードの一部を見てみましょう:
function Person(name, age) { this.name = name this.age = age this.gender = '男' // 返回引用类型 // return { // name: name, // gender: '男' // } // 返回基本类型 // return 1 // 例外 return null }
コードを再度実行すると、また何か問題が発生したことがわかります。 ! !
それでは、なぜこの問題が発生するのでしょうか?
基本型を返す場合、コンストラクターは影響を受けず、null が基本型であると要約したばかりではありませんか?
今この瞬間、私の心の中には一万頭の草と泥の馬が疾走しています。 ! !
解惑:null是基本类型没错,但是使用操作符typeof后我们不难发现:
typeof null === 'object' // true
特例:typeof null
返回为'object'
,因为特殊值null
被认为是一个空的对象引用
。
明白了这一点,那问题就好解决了:
function Person(name, age) { // ... } function New() { var obj = {} Constructor = [].shift.call(arguments) obj.__proto__ = Constructor.prototype // Constructor.apply(obj, arguments) var result = Constructor.apply(obj, arguments) // return obj // return typeof result === 'object' ? result : obj return typeof result === 'object' ? result || obj : obj } var person = New(Person, '小明', 25) console.log(person.name) // ...
解决方案:判断一下构造函数返回值result,如果result是一个引用(引用类型和null),就返回result,但如果此时result为false(null),就使用操作符||
之后的obj
好了,到现在应该又有小伙伴发问了,这下New函数是彻彻底底实现了吧!!!
答案是,离完成不远了!!
别急,在功能上,New函数基本完成了,但是在代码严谨度上,我们还需要做一点工作,继续往下看:
这里,我们在文章开篇做的铺垫要派上用场了:
var obj = {}
实际上等价于
var obj = new Object()
前面说了,以上两段代码其实只是获取了object对象的一个实例。再者,我们本来就是要实现new,但是我们在实现new的过程中却使用了new
!
这个问题把我们引入到了到底是先有鸡还是先有蛋的问题上!
这里,我们就要考虑到ECMAScript底层的API了————Object.create(null)
这句代码的意思才是真真切切地创建
了一个对象!!
function Person(name, age) { // ... } function New() { // var obj = {} // var obj = new Object() var obj = Object.create(null) Constructor = [].shift.call(arguments) obj.__proto__ = Constructor.prototype // Constructor.apply(obj, arguments) var result = Constructor.apply(obj, arguments) // return obj // return typeof result === 'object' ? result : obj return typeof result === 'object' ? result || obj : obj } var person = New(Person, '小明', 25) console.log(person.name) console.log(person.age) console.log(person.gender) // 这样改了之后,以下两句先注释掉,原因后面再讨论 // console.log(person.nation) // person.say()
好了好了,小伙伴常常舒了一口气,这样总算完成了!!
但是,这样写,新的问题又来了。
小伙伴:啥?还有完没完?
function Person(name, age) { this.name = name this.age = age this.gender = '男' } Person.prototype.nation = '汉' Person.prototype.say = function() { console.log(`My name is ${this.age}`) } function New() { // var obj = {} // var obj = new Object() var obj = Object.create(null) Constructor = [].shift.call(arguments) obj.__proto__ = Constructor.prototype // Constructor.apply(obj, arguments) var result = Constructor.apply(obj, arguments) // return obj // return typeof result === 'object' ? result : obj return typeof result === 'object' ? result || obj : obj } var person = New(Person, '小明', 25) console.log(person.name) console.log(person.age) console.log(person.gender) // 这里解开刚才的注释 console.log(person.nation) person.say()
别急,我们执行一下修改后的代码,发现原型链上的属性nation
和方法say()
报错,这又是为什么呢?
从上图我们可以清除地看到,Object.create(null)
创建的对象是没有原型链的,而后两个对象则是拥有__proto__
属性,拥有原型链,这也证明了后两个对象是通过继承得来的。
那既然通过Object.create(null)
创建的对象没有原型链(原型链断了),那我们在创建对象的时候把原型链加上不就行了,那怎么加呢?
function Person(name, age) { this.name = name this.age = age this.gender = '男' } Person.prototype.nation = '汉' Person.prototype.say = function() { console.log(`My name is ${this.age}`) } function New() { Constructor = [].shift.call(arguments) // var obj = {} // var obj = new Object() // var obj = Object.create(null) var obj = Object.create(Constructor.prototype) // obj.__proto__ = Constructor.prototype // Constructor.apply(obj, arguments) var result = Constructor.apply(obj, arguments) // return obj // return typeof result === 'object' ? result : obj return typeof result === 'object' ? result || obj : obj } var person = New(Person, '小明', 25) console.log(person.name) console.log(person.age) console.log(person.gender) console.log(person.nation) person.say()
这样创建的对象就拥有了它初始的原型链了,这个原型链是我们传进来的构造函数赋予它的。
也就是说,我们在创建新对象的时候,就为它指定了原型链了,新创建的对象继承自传进来的构造函数!
现在,我们来梳理下最终的New函数做了什么事,也就是本文讨论的结果————new操作符到底做了什么?
Constructor
;Constructor
的原型链结合Object.create
来创建
一个对象,此时新对象的原型链为Constructor
函数的原型对象;(结合我们上面讨论的,要访问原型链上面的属性和方法,要使用实例对象的__proto__属性)Constructor
函数的this指向,指向新创建的实例对象,然后call
方法再调用Constructor
函数,为新对象赋予属性和方法;(结合我们上面讨论的,要访问构造函数的属性和方法,要使用call或apply)Constructor
函数的一个实例对象。现在我,我们来回答文章开始时提出的问题,new是用来创建对象的吗?
现在我们可以勇敢的回答,new是用来做继承的,而创建对象的其实是Object.create(null)。
在new操作符的作用下,我们使用新创建的对象去继承了他的构造函数上的属性和方法、以及他的原型链上的属性和方法!
写在最后:
补充一点关于原型链的知识:
- JavaScript中的函数也是对象,而且对象除了使用字面量定义外,都需要通过函数来创建对象
- prototype属性可以给函数和对象添加可共享(继承)的方法、属性,而__proto__是查找某函数或对象的原型链方式
- prototype和__proto__都指向原型对象
- 任意一个函数(包括构造函数)都有一个prototype属性,指向该函数的原型对象
- 任意一个实例化的对象,都有一个__proto__属性,指向构造函数的原型对象。
以上が新しいオペレーターの詳しい使い方紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。