精讀JavaScript模式(三),new一個構造函數究竟發生了什麼?

1、前言html

上個月底,爸爸由於事故忽然離世,說內心話,如今看到'去世','爸爸'這樣的字眼,眼淚都會忍不住在眼眶打轉,仍是須要時間治癒。最近也只是零碎的看了下東西,始終沉不下心去讀書,直到今天仍是決定撿起以前看的JS模式。函數

前面兩篇博客大概記錄了書中前兩章節我以爲一些好用的知識,從這篇開始就是第三章--直接量和構造函數了,難度也不算大,最近下班了在公司花點時間慢慢寫。測試

從第三篇開始,我想在介紹每一個知識點前,先以概要的形式將這一部分介紹的東西以問題的形式列出來,方便你們帶着問題去讀,讀完了再回頭看問題,看本身能不能答出來,這樣也方便檢驗本身對於知識的掌握狀況。this

2、對象直接量spa

概要:什麼是對象,什麼是對象直接量寫法,空對象不是真正的空對象prototype

咱們能夠將js中的對象簡單理解爲名值對組成的散列表,其中值都是名的屬性,值能夠是原始值(string,number...),也能夠是對象,而當對象是函數時,咱們通常稱之爲方法。code

js中自定義的對象任什麼時候候都是可變的,內置本地對象的屬性也是能夠修改的,好比你能夠先建立一個空對象,而後在給它添加一些屬性或方法。而在建立對象時,對象直接量寫法是較爲理想的方式。htm

var me = {};
me.name = "echo";
me.getName = function () {
  return me.name;
};

你能夠刪除對象的某個屬性或方法對象

delete me.name;

其實咱們建立一個對象也不必像上面先建立空對象,再一步步添加屬性方式,在對象建立時能夠同時把你須要定義的屬性同時添加好。blog

let me = {
  name = "echo",
  getName = function () {
    return this.name;
  }
};

那麼這種直接用 = 建立對象的方式就是對象直接量的寫法,很直接不是嗎。對象直接量語法包括:

• 將對象主體包含在一對花括號內。
• 對象內的屬性或方法之間使用逗號分隔。最後一個名值對後也能夠有逗號,但 在IE下會報錯,因此儘可能不要在最後一個屬性或方法後加逗號。
• 屬性名和值之間使用冒號分隔
• 若是將對象賦值給一個變量,不要忘了在右括號以後補上分號

我在上方提到的空對象能夠說是算是一種簡稱,它們並非真正的空對象,即使申明一個{},它也會從Object.prototype繼承不少屬性和方法,可是咱們其實有方法建立嚴格意義上的空對象,我在上一篇文章中就列出了兩種方法,有興趣能夠去看看。

3、經過構造函數建立對象

概要:爲何推薦對象直接量寫法而不是構造函數寫法

儘管JS中沒有類的概念,但當你想快速建立多個具備共同特徵的實例時仍是可使用構造函數,JS中內置了很多構造函數,例如Object(),Date(),String()等等。

咱們用構造函數的寫法來創造上面的對象:

var me = new Object();
me.name = "echo";
me.getName = function () {
  return me.name;
}

相比之下,對象直接量的寫法與構造函數寫法相比代碼更少,推薦直接量寫法的還有兩個緣由

一是它能夠強調對象是一個簡單的可變的散列表,而沒必要必定派生自某個類。

二是當你使用Object()建立對象時,解析器須要順着做用域鏈開始查找,直到找到Object構造函數爲止,而直接量的寫法是不會存在做用域解析行爲。

 4、自定義構造函數

概要:當你new一個構造函數時發生了什麼?

除了對象直接量和內置構造函數以外,咱們還能夠經過自定義的構造函數來建立實例對象,像這樣。

var Person = function () {
  this.name = "echo";
  this.sayName = function () {
    console.log('my name is '+ this.name);
  };
}
var me = new Person();
me.sayName();//my name is echo

說個小插曲,這裏我自定義的構造函數名Person的字母P其實能夠小寫,但咱們都知道,內置構造函數都是大寫開頭,因此爲了讓構造函數更爲醒目,推薦首字母大寫!

很奇怪對不對,咱們new一個構造函數獲得一個實例,這個實例就繼承了構造函數的屬性方法,那new這個過程當中到底發生了什麼?

1.建立一個空對象,將它的引用賦給this,繼承函數的原型。

2.經過this將屬性和方法添加至這個對象。

3.最後返回this指向的新對象。

咱們用代碼模擬這三句話,就像這樣:

var Person = function () {
  // var this = {};
  this.name = "echo";
  this.sayName = function () {
    console.log('my name is '+ this.name);
  };
  // return this; 這裏隱性返回的其實就是上面建立的空對象,這個空對象被賦予了name屬性和一個sayName方法
}
var me = new Person();
me.sayName();//my name is echo

 在這段代碼中,sayName()方法被添加到了this中,但有個問題,無論咱們執行幾回new Person(),sayName()方法會反覆的被添加到this中,且每次sayName()方法都會在內存中新開內存。

當咱們全部實例中的sayName()方法都是如出一轍時,這種作法是很浪費內存的,推薦作法是將sayName()方法添加在Person的原型中。

var Person = function () {
  this.name = "echo";
};
Person.prototype.sayName = function () {
  console.log('my name is '+ this.name);
};
var me = new Person();
me.sayName();//my name is echo

關於new一個構造函數到底發生了什麼,我在前面說會隱性的新建一個空對象賦予this,仍是那句話,這裏的空對象並非嚴格意義上的空,它仍是繼承了Person的原型,準確來講應該是這樣。

// var this = Object.create(Person.prototype);

 5、構造函數的返回值

概要:構造函數能返回什麼?默認返回什麼?

當咱們new一個構造函數老是會返回一個對象,默認返回this所指向的對象。若是咱們沒有在構造函數內爲this賦予任何屬性,則會返回一個集成了構造函數原型,沒有本身屬性的'空對象'。(若是讀不懂這句話,請結合new發生的過程去理解)

儘管咱們沒在構造函數內寫return語句,也會隱性的返回this,但其實咱們能夠返回自定義的對象。像這樣:

var Person = function () {
  this.name = "echo";
  var that = {};
  that.name = "wl";
  return that;
};
var me = new Person();
me.name;//wl

構造函數能夠返回任意對象,只要你返回的是個對象。假設你返回的不是對象,程序也不會報錯,但這個返回值會被忽略,最終仍是隱性的返回this所指向的對象。

var Person = function () {
  this.name = "echo";
  var name = "wl";
  return name;
};
var me = new Person();
me.name; //echo

6、強制使用new 的模式

概要:不使用new調用構造函數會怎樣?構造函數內能自定義對象嗎?不使用new也能繼承構造函數原型的作法

構造函數與普通函數無異,只是調用須要使用new,當咱們不使用new調用時,語法也不會出錯,但函數中的this會指向全局對象(非嚴格模式是window)。

var Person = function () {
  this.name = "echo";
};
var me = Person();
window.name//echo

在這段代碼中實際上建立了一個全局對象屬性name,你能夠經過window.name訪問到它。那麼說到這裏對於構造函數咱們強調兩點。

1.構造函數名首字母大寫

2.調用構造函數使用new

遵照這些約定確定是好的,但在實際開發的構造函數中,咱們經常看看使用that等其它字面量代替this的作法。這麼作的目的是爲了確保構造函數按照本身定義的方式執行,而不存建立空對象賦予this等隱性不可見的行爲,更可預測。

var Person = function () {
  var that = {};
  that.name = "echo";
  return that;
};
var me = new Person();
me.name;//echo

對於上述代碼中,咱們使用that代替了this,使用that只是一種命名約定,你可使用self,me甚至任意非js語言保留字的字段。

或者that都不建立,直接返回一個對象。

var Person = function () {
  return {
    name : "echo"
  };
};
var me = new Person();
var you = Person();
me.name//echo
you.name//echo

這種寫法無論咱們是否使用new去調用,都能獲得一個實例,但這種模式丟失了原型,全部的實例都不會繼承Person()原型上的屬性。

var Person = function () {
  return {
    name:'echo'
  }
};
Person.prototype.sayName = function () {
  console.log(1);
};

me.sayName();//報錯,自定義對象未指向Person,沒繼承Person的方法
you.sayName();//報錯,同上

咱們在前面說,不使用new時,this指向window(非嚴格模式),沒法繼承Person的任何屬性。

var Person = function () {
  this.name = "echo"
};
Person.prototype.sayName = function () {
  console.log(1);
};
var me = new Person();
var you = Person();
me.name;//echo
you.name;//報錯,此時的name是window的屬性
me.sayName();//1
you.sayName();//報錯,在實例化過程當中this指向window,並未繼承Person的方法

那有辦法可讓不使用new狀況下實例也能繼承Person屬性的作法嗎,固然有,好比調用自身的構造函數:

var Person = function () {
  if(!(this instanceof Person)){
    return new Person();
  }
  this.name = "echo"
};
Person.prototype.sayName = function () {
  console.log(1);
};
var me = new Person();
var you = Person();
me.name;//echo
you.name;//echo
me.sayName();//1
you.sayName();//1

看到沒,沒使用new的實例you也繼承了Person的屬性和方法,若是看不懂那應該是對於instanceof運算符不太瞭解,這裏順帶說下

instanceof運算符用於測試構造函數的prototype屬性是否出如今對象的原型鏈中的任何位置---MDN

object instanceof constructor   object:要檢測的對象.    constructor:某個構造函數

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}
var auto = new Car('Honda', 'Accord', 1998);
console.log(auto instanceof Car);//true
console.log(auto instanceof Object);//true

那麼上述代碼中,new Person()好理解,this就是隱性返回的實例,this instanceof Person爲true跳過判斷,直接走new一個構造函數時發生的過程,獲得實例天然會繼承Person的屬性和方法。

Person()呢,this指向window,很明顯this instanceof Person爲false,假假爲真,執行判斷內的代碼new Person();同理,也走了new過程的三部曲,獲得的實例也繼承了Person的屬性和方法。

有疑問或者錯誤也歡迎你們指出來。

最近老是以爲沒什麼值得開心的事情,趁着雙十二,給本身換了個鍵盤,畢竟天天都是要寫代碼的,也算換一個心情。

儘管除了吃飯交房租就沒什麼開銷,仍是挺心疼的。

若是你們對於new過程還有疑惑,以及如何實現一個new方法,歡迎閱讀博主這篇文章 js new一個對象的過程,實現一個簡單的new方法

那麼就先寫到這裏了。

相關文章
相關標籤/搜索