這篇文章帶給大家的內容是關於new操作符的詳細用法介紹,有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。
相信很多才接觸前端的小夥伴甚至工作幾年的前端小夥伴對new這個操作符的了解還停留在一知半解的地步,比較模糊。
就例如前不久接觸到一個入職兩年的前端小伙伴,他告訴我new是用來創建對象的,無可厚非,可能很多人都會這麼答!
那這麼答到底是錯很是對呢?
下面我們全面來討論一下這個問題:
我們要拿到一個對象,有很多方式,其中最常見的一種便是對象字面量:
var obj = {}
但是從語法來看,這就是一個賦值語句,是把對面字面量賦值給了obj這個變數(這樣說或許不是很準確,其實這裡是得到了一個物件的實例!!)
很多時候,我們說要創建一個對象,很多小夥伴雙手一摸鍵盤,啪啪幾下就敲出了這句代碼。
上面說了,這句話其實只是得到了一個物件的實例,那這句程式碼到底還能不能和創建物件畫上等號呢?我們繼續往下看。
要拿到一個物件的實例,還有一個和物件字面量等價的做法就是建構子:
var obj = new Object()
這句程式碼一敲出來,相信小夥伴們對剛才我說的obj
只是一個實例物件沒有異議了吧!那很多小夥伴又會問了:這不就是new
了一個新物件出來嘛!
沒錯,這確實是new了一個新對像出來,因為javascript之中,萬物解釋對象,obj是一個對象,而且是透過new運算子得到的,所以說很多小夥伴就肯定的說:new就是用來創建物件的!
這就不難解釋很多人把創建對象和實例化對象混為一談!!
我們在換個思路看看:既然js一切皆為對象,那為什麼還需要創建對象呢?本身就是對象,我們何來創建一說?那我們可不可以把這是一種繼承
呢?
說了這麼多,相信不少夥伴已經看暈了,但是我們的目的就是一個:理清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運算子的實作
主要步驟:
可能很多小夥伴看到這裡覺得new不就是做了這些事情嗎,然而~~
然而我們卻忽略了一點,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
這兩個欄位如期輸出,age
、nation
為undefined,say()
報錯。
改一下程式碼建構函式的程式碼:
function Person(name, age) { this.name = name this.age = age this.gender = '男' // return { // name: name, // gender: '男' // } return 1 } // ...
執行程式碼,發現所有欄位終於如期輸出。
這裡做個小結:
那我們現在來考慮下New函數要怎麼改才能實現上面總結的兩點函數呢?繼續往下看:
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) // ...
執行此程式碼,發現已經實現了上面總結的兩點。
解決方案:使用變數接收建構函式的回傳值,然後在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__属性,指向构造函数的原型对象。
以上是new操作符的詳細用法介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!