關於Javascript中的new運算符,構造函數與原型鏈一些理解

前言

文章主要基於<< Javascrpt 高級程序設計3 >>總結的!!!
PS: 2018/05/09 基本重寫了全文,補充知識點,新增實例,優化排版
PS: 2018/05/11 新增檢測方法,技巧用法javascript

構造函數

new constructor(arguments):

建立一個用護定義的對象類型的實例或具備構造函數的內置對象類型之一java

  • new命令: 執行構造函數返回一個實例對象
  • 構造函數(constructor): 一個指定對象實例的類型的函數
  • 傳慘(arguments): 一個用來被構造函數調用的慘數列表

注意點幾個:segmentfault

  • 默認構造函數首字母大寫區分函數類型
  • 在不傳遞任何慘數的狀況new constructor能夠省略括號
  • 構造函數與其餘函數的惟一區別: 就在於調用它們的方式不一樣.(任何函數,只要經過 new 操做符來調用,那它就能夠做爲構造函數;而任何函數,若是不經過 new 操做符來調用,那它跟普通函數也不會有什麼兩樣)
  • 使用構造函數的主要問題,就是每一個方法都要在每一個實例上從新建立一遍
  • new存在的意義在於它實現了javascript中的繼承,而不只僅是實例化了一個對象

這是基本用法,你們都懂的,而後咱們往深層次裏挖掘下底層原理怎麼運做的.
假設有個函數瀏覽器

function Person(name) {
  this.name = name || 'mike',
    this.getAge = function () {
      console.log(10);
    }
}

var man = new Person;
var women = new Person('tracy')

console.log(man.name, women.name) // mike tracy

當代碼執行時會通過幾個步驟:安全

如下是我基於JS高程3總結的:函數

  1. 一個新的空對象被建立
  2. 建立執行的時候,將構造函數的做用域賦給新對象(所以 this 就指向了這個新對象)
  3. 執行構造函數中的代碼(爲這個新對象添加屬性)等初始化工做
  4. 若是構造函數返回了一個「對象」,那麼這個對象會取代整個new出來的結果.若是構造函數沒有返回對象,那麼new出來的結果爲步驟1建立的對象,(ps:通常狀況下構造函數不返回任何值,若是想覆蓋這個返回值,能夠選擇返回一個普通對象.);

如下是原版:優化

  1. 建立一個新對象
  2. 將構造函數的做用域賦給新對象(所以 this 就指向了這個新對象)
  3. 執行構造函數中的代碼(爲這個新對象添加屬性)
  4. 返回新對象.

下面詳細舉例
一、若是構造函數不返回任何值則按照其餘語言同樣返回實例化對象(即步驟1建立的對象).this

function Person() { }
var man = new Person();
console.log(man) // Person {}

二、若是構造函數返回的是基本類型 (string, number, boolean, null,undefined) 也按照其餘語言同樣返回實例化對象(即步驟1建立的對象).若是大家還搞不懂基本類型跟引用對象的區別,能夠參考我以前寫的文章關於javascript基本類型和引用類型小知識spa

function Person() {
  return '我是基本類型'
}

var man = new Person();
console.log(man)//Person {}

三、若返回值是引用類型,則實際返回值爲這個引用類型.prototype

function Person() {
  return {
    age: 18
  }
}

var man = new Person();
console.log(man) //Object {age: 18}

初學者特別應該注意的是他們之間是不一樣的,所謂的構造函數是建立一個用戶定義的對象類型的實例或具備構造函數的內置對象類型之一

正常來講構造函數不須要有返回值的,能夠認爲構造函數和普通函數的最大差異就是:構造函數中沒有return語句,普通函數能夠有return語句;構造函數中會使用this關鍵字定義成員變量和成員方法,普通的函數不會使用this關鍵字定義成員變量和方法.
像第二種狀況即便返回基本類型也會被忽略掉,只有選擇返回一個普通對象纔會取代整個new出來的結果

原型對象(prototype)

每一個函數都有一個 prototype 屬性, prototype 就是指向經過調用構造函數而建立的那個對象實例的原型對象,做用是可讓全部對象實例共享它所包含的屬性和方法.
JavaScript中一切皆對象,每一個對象都是繼承自另外一個對象,因此對象都有本身的原型對象(除了null之外).
因此除了在構建函數內部定義屬性方法共享以外,咱們還能夠在構造函數的原型對象上添加共享的自定義屬性方法.

function Person() { }
//原型鏈添加函數
Person.prototype.getAge = function () {
  console.log(18)
}

//實例化
var man = new Person(),
  women = new Person();

man.getAge() // 18
women.getAge() // 18

有一種狀況是對象實例自身已經賦有同名屬性方法會覆蓋 prototype 上的屬性方法.這個認知是不許確的,下面的原型鏈會講到這部分.

function Person() { }
//原型鏈添加函數
Person.prototype.getAge = function () {
  console.log(18)
}

//實例化
var man = new Person();
man.getAge = function () {
  console.log(81)
}

man.getAge() // 81

還有一種狀況是在構造函數的原型對象上添加共享的自定義屬性方法而且實例化對象以後,再次修改原型對象上的方法.一樣會影響到輸出結果,依然下面的原型鏈會講到這部分.

function Person() { }
//原型鏈添加函數
Person.prototype.getAge = function () {
  console.log(18)
}

//實例化
var man = new Person();
//修改原型鏈自定義函數
Person.prototype.getAge = function () {
  console.log(81)
}

man.getAge() // 81

原型鏈

下面引出JS高程三解析片斷----------

理解原型對象(關鍵詞 prototype, constructor, __proto__)
不管什麼時候,只要建立了一個新函數,就會根據一組特定的規則爲該函數建立一個 prototype 屬性,這個屬性 指向函數的原型對象.

在默認狀況下,全部原型對象都會自動得到一個 constructor(構造函數)屬性,這個屬性包含一個指向 prototype 屬性所在函數的指針.

就拿前面的例子來講,Person.prototype.constructor 指向 Person .而經過這個構造函數,咱們還可繼續爲原型對象添加其餘屬性和方法.建立了自定義的構造函數以後,其原型對象默認只會取得 constructor 屬性;至於其餘方法,則都是從 Object 繼承而來的.

當調用構造函數建立一個新實例後,該實例的內部將包含一個指針(內部屬性),指向構造函數的原型對象.

ECMA-262 第 5 版中管這個指針叫 [[Prototype]] .雖然在腳本中沒有標準的方式訪問 [[Prototype]] ,但 Firefox、Safari 和 Chrome 在每一個對象上都支持一個屬性__proto__ ;而在其餘實現中,這個屬性對腳本則是徹底不可見的.不過,要明確的真正重要的一點就是,這個鏈接存在於實例與構造函數的原型對象之間,而不是存在於實例與構造函數之間.

這裏引出兩個關鍵知識點:

  1. 全部對象都有屬性__proto__指向該對象的構造函數的原型對象,原型鏈就是靠它造成的
  2. 函數對象除了__proto__,還有屬性prototype指向該方法的原型對象,它的做用是:構造函數實例化對象的時候,告訴構造函數新建立的對象的原型是誰;

間單說構造函數、原型和實例的關係:每一個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個指向原型對象的內部指針.原型鏈可讓咱們作到利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法.

Javascript 一切皆對象(普通對象和函數對象).每一個函數對象都有一個內部連接到另外一個對象它的原型 prototype.該原型對象有本身的原型,等等,直到達到一個以null爲原型的對象.根據定義,null沒有原型,而且做爲這個原型鏈 prototype chain中的最終連接.

例如:

function Person() {
  this.name = 'mike'
}

var man = new Person;

console.log(man.__proto__ === Person.prototype); // true
//繼續深刻發掘它的原型
console.log(Person.prototype.__proto__ === Object.prototype); // true

//前面說的一切皆對象是這個意思
console.log(Function.prototype.__proto__); // Object {}
console.log(Array.prototype.__proto__); // Object {}
console.log(Number.prototype.__proto__); // Object {}


//繼續深刻發掘它的最終來源是什麼
console.log(Object.prototype); // Object {}
console.log(Object.prototype.__proto__); // null

上面一步一步挖到最初的原型,有個注意的點容易混淆的,再強調一遍prototype是函數對象繼承的原型,__proto__是指向建立它的函數對象的原型對象prototype.只有真的弄清楚關係你才知道下面的區別

console.log(Function.prototype); // function () {}
console.log(Array.prototype); // [Symbol(Symbol.unscopables): Object]
console.log(Number.prototype); // Number {[[PrimitiveValue]]: 0}
console.log(Object.prototype); // Object {}

//下面函數對象都是經過new Function()建立,因此指向一定都是Function.prototype.
console.log(Function.__proto__);
console.log(Array.__proto__);
console.log(Number.__proto__);
console.log(Object.__proto__);

//細細品味這句
console.log(Function.__proto__ === Function.prototype); // true

訪問一個對象的屬性時,它先在該對象上搜尋,若是該對象沒有就搜尋該對象的原型,若是尚未就繼續往該對象的原型的原型搜索,依此層層向上搜索,直到找到一個名字匹配的屬性或到達原型鏈的末尾(即undefined)位置.這就是上面說的實例方法覆蓋原型對象方法認知不許確的解釋了

function Person() {
    this.name = 'mike'
}

Person.prototype.age = 10;
Person.prototype.getAge = function() {
    console.log(20)
};

var man = new Person;

console.log(man.age) // 10
man.getAge() //20
console.log(man.sex) // undefined

原型對象除了__proto__以外還有一個constructor屬性,做用是返回對建立此對象的函數的引用.這個比較間單,直接上實例

function Person() {
  this.name = 'mike'
}

var man = new Person;
console.log(man.__proto__.constructor === Person); // true
console.log(new Array().constructor); // [Function: Array]
console.log(new Number().constructor); // [Function: Number]
console.log(new Object().constructor); // [Function: Object]
console.log(new Function().constructor); // [Function: Function]

實例化對象man打印結果以下.
man.__proto__.constructor === Person
圖片描述

獲取檢測原型對象方法

上面獲取原型對象的方法其實不安全,

  • 依賴瀏覽器環境暴露出來的訪問屬性,
  • 在實例對象變動原型對象指向的狀況會失效
function Person() {
  this.name = 'mike'
}

var man = new Person;
man.prototype = Object;
console.log(man.prototype); // [Function: Object]

因此咱們通常經過Object.getPrototypeOf方法去獲取.

function Person() {
  this.name = 'mike'
}

var man = new Person;
man.prototype = Object;
console.log(man.prototype);  // [Function: Object]
console.log(Object.getPrototypeOf(man)); // Person {}

圖片描述
若是是想要檢測某個對象是否構造函數的實例,可使用instanceof

function Person() {
  this.name = 'mike'
}
var man = new Person;
console.log(man instanceof Person); // true

技巧用法

還記得上面說的構造函數與其餘函數的惟一區別,就在於調用它們的方式不一樣.任何函數,只要經過 new 操做符來調用,那它就能夠做爲構造函數;而任何函數,若是不經過 new 操做符來調用,那它跟普通函數也不會有什麼兩樣;
用個小技巧可讓你沒有使用new操做符也能實例化構造函數,爲了區別改了點代碼.

function Person(name) {
  //若是是實例化對象直接賦值
  if (this instanceof Person) {
    this.name = name
  } else {
    //不然從新實例化
    return new Person(name)
  }
}
var man = new Person('mike'),
  women = Person('kitty');
console.log(man.name, women.name); // mike kitty
相關文章
相關標籤/搜索