JS入門難點(diǎn)解析11-構(gòu)造函數(shù),原型對象,實(shí)例對象

(注1:如果有問題歡迎留言探討,一起學(xué)習(xí)!轉(zhuǎn)載請注明出處,喜歡可以點(diǎn)個贊哦?。?br> (注2:更多內(nèi)容請查看我的目錄。)

1. 簡介

在前面,我們對這三個概念已經(jīng)有所涉及,但是卻并未深究。事實(shí)上,如果能熟練理解掌握這三個概念和他們之間的關(guān)系,那么在學(xué)習(xí)原型鏈和繼承的知識時,會有一種撥云見霧之感。

2. 構(gòu)造函數(shù)

構(gòu)造函數(shù)其實(shí)與普通函數(shù)本身并無區(qū)別,普通函數(shù)通過new調(diào)用時,我們就稱其為構(gòu)造函數(shù)。當(dāng)然,為了區(qū)分其與普通函數(shù),構(gòu)造函數(shù)約定首字母需要大寫。下面,我們就來看一下構(gòu)造函數(shù)和普通函數(shù)使用時的區(qū)別(簡單來講就是一個函數(shù)通過new調(diào)用和不通過new調(diào)用的區(qū)別)。

2.1 一個空函數(shù)

// 空函數(shù)
function A() {}
var a1 = A();
var a2 = new A();
console.log('a1:', a1);  //undefined
console.log('a2:', a2);  //{}

在chrome的控制臺console運(yùn)行結(jié)果如圖所示:

2.1

直接調(diào)用返回undefined,而使用new調(diào)用返回的卻是一個空對象。這里,我們暫且不去討論_proto_和constructor的含義。

2.2 無this有return,但是return后面無返回值,或者返回基本類型值。

// 無返回值
function A() {
    return;
}
//返回undefined類型值
function B() {
    return undefined;
}
// 返回Number類型值
function C() {
    return 1;
}
// 返回String類型值
function D() {
    return '1';
}
// 返回Boolean類型值
function E() {
    return true;
}
// 返回Null類型值
function F() {
    return null;
}
var a1 = A();
var a2 = new A();
console.log('a1:', a1);
console.log('a2:', a2);
var b1 = B();
var b2 = new A();
console.log('b1:', b1);
console.log('b2:', b2);
var c1 = C();
var c2 = new A();
console.log('c1:', c1);
console.log('c2:', c2);
var d1 = D();
var d2 = new A();
console.log('d1:', d1);
console.log('d2:', d2);
var e1 = E();
var e2 = new A();
console.log('e1:', e1);
console.log('e2:', e2);
var f1 = F();
var f2 = new A();
console.log('f1:', f1);
console.log('f2:', f2);
2.2

可以看到,普通調(diào)用會返回return后面的值,而new調(diào)用返回空對象{}。

2.3 無this有return,但是return后面是一個對象(包括函數(shù))。

// 返回對象
function A() {
    return {m: 1};
}
//返回函數(shù)
function B() {
    return function () {
        return 123;
    }
}
var a1 = A();
var a2 = new A();
console.log('a1:', a1);
console.log('a2:', a2);
var b1 = B();
var b2 = new B();
console.log('b1:', b1);
console.log('b2:', b2);
2.3

可以看出,不管是普通調(diào)用還是new調(diào)用都是返回return后面的值。

2.4 有this,無return。

function A() {
    this.m = 1;
    this.n = function () {
        console.log(123);
    };
}
var a1 = A();
var a2 = new A();
console.log('a1:', a1);
console.log('a2:', a2);
2.4

普通調(diào)用返回undefined,而new調(diào)用返回一個對象,構(gòu)造函數(shù)A中的this指向了該對象,所以返回對象的屬性和方法由構(gòu)造函數(shù)中的this語句初始化。
ps: 需要注意的是,普通調(diào)用的時候,this指向了undefined,非嚴(yán)格模式下指向了widow。

2.5 有this,有return。但是return后面無返回值,或者返回基本類型值。

// 無返回值
function A() {
    this.m = 1;
    return;
}
//返回undefined類型值
function B() {
    this.m = 1;
    return undefined;
}
// 返回Number類型值
function C() {
    this.m = 1;
    return 1;
}
// 返回String類型值
function D() {
    this.m = 1;
    return '1';
}
// 返回Boolean類型值
function E() {
    this.m = 1;
    return true;
}
// 返回Null類型值
function F() {
    this.m = 1;
    return null;
}
var a1 = A();
var a2 = new A();
console.log('a1:', a1);
console.log('a2:', a2);
var b1 = B();
var b2 = new B();
console.log('b1:', b1);
console.log('b2:', b2);
var c1 = C();
var c2 = new C();
console.log('c1:', c1);
console.log('c2:', c2);
var d1 = D();
var d2 = new D();
console.log('d1:', d1);
console.log('d2:', d2);
var e1 = E();
var e2 = new E();
console.log('e1:', e1);
console.log('e2:', e2);
var f1 = F();
var f2 = new F();
console.log('f1:', f1);
console.log('f2:', f2);
2.5

可以看到,普通調(diào)用會返回return后面的值,而new調(diào)用返回一個對象,構(gòu)造函數(shù)A中的this指向了該對象,所以返回對象的屬性和方法由構(gòu)造函數(shù)中的this語句初始化。
ps: 需要注意的是,普通調(diào)用的時候,this指向了undefined,非嚴(yán)格模式下指向了widow。

2.6 有this,有return。return后面是一個對象(包括函數(shù))。

// 返回對象
function A() {
    this.m = 1;
    return {n: 2};
}
//返回函數(shù)
function B() {
    this.f = function () {
        return 1;
    };
    return function () {
        return 2;
    }
}
var a1 = A();
var a2 = new A();
console.log('a1:', a1);
console.log('a2:', a2);
var b1 = B();
var b2 = new B();
console.log('b1:', b1);
console.log('b2:', b2);
2.6

可以看到,不管是普通調(diào)用還是new調(diào)用都是返回return后面的值。
ps: 需要注意的是,普通調(diào)用的時候,this指向了undefined,非嚴(yán)格模式下指向了widow。

總結(jié):對于構(gòu)造函數(shù)調(diào)用,有如下特點(diǎn):

  1. 如果沒有return,返回一個新的對象,構(gòu)造函數(shù)的this指向該對象。
  2. 如果有return且后面的返回值不是對象(包括函數(shù)),則return語句會被忽略。
  3. 如果有return且后面返回一個對象(包括函數(shù)),則返回該對象。

3. 實(shí)例對象

第2節(jié)我們已經(jīng)闡述了構(gòu)造函數(shù)的定義和使用方法,現(xiàn)在我們來看一下實(shí)例對象的定義。

實(shí)例對象:通過構(gòu)造函數(shù)的new操作創(chuàng)建的對象是實(shí)例對象,又常常被稱為對象實(shí)例??梢杂靡粋€構(gòu)造函數(shù),構(gòu)造多個實(shí)例對象。下面的f1和f2就是實(shí)例對象。

function Foo(){};
var f1 = new Foo;
var f2 = new Foo;
console.log(f1 === f2);//false

4. 原型對象

首先,我們來看兩段《JavaScrpit高級程序設(shè)計》對原型模式和原型對象的闡述:

我們創(chuàng)建的每個函數(shù)都有一個prototype(原型)屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含可以由特定類型的所有實(shí)例共享的屬性和方法。如果按照字面意思來理解,那么prototype就是通過調(diào)用構(gòu)造函數(shù)而創(chuàng)建的那個對象實(shí)例的原型對象。使用原型對象的好處是可以讓所有對象實(shí)例共享它所包含的屬性和方法

無論什么時候,只要創(chuàng)建了一個新函數(shù),就會根據(jù)一組特定的規(guī)則為該函數(shù)創(chuàng)建一個prototype屬性,這個屬性指向函數(shù)的原型對象。在默認(rèn)情況下,所有原型對象都會自動獲得一個constructor(構(gòu)造函數(shù))屬性,這個屬性包含一個指向prototype屬性所在函數(shù)的指針。

簡而言之,任何一個函數(shù),都擁有一個prototype屬性,指向其原型對象,該原型對象也是由該函數(shù)new調(diào)用創(chuàng)造的所有實(shí)例對象的原型對象。

5. 構(gòu)造函數(shù),原型對象和實(shí)例對象的關(guān)系

5.1 指向關(guān)系

構(gòu)造函數(shù)A的prototype屬性指向F與其實(shí)例對象(a1,a2,...)的原型對象A.prototype,該原型對象的constructor屬性指向構(gòu)造函數(shù)A,實(shí)例對象擁有[[Prototype]]屬性(在firefox,safari和chrome上該屬性實(shí)現(xiàn)為_proto_)指向原型對象A.prototype

function A() {
}
var a1 = new A();
var a2 = new A();
5.1

還記得我們在前面2.1節(jié)的空函數(shù)為構(gòu)造函數(shù)的圖片嗎?現(xiàn)在來看是不是就很清晰了。明白了其中的指向關(guān)系,我們再來看一下,構(gòu)造函數(shù)中添加this語句以及在原型對象中添加屬性以后是怎樣的情況。

5.2 實(shí)例化時的數(shù)據(jù)關(guān)系

// 代碼段5.2
function A() {
    this.m = 1;
    this.n = [1, 2];
}

A.prototype.p = 2;
A.prototype.q = [3, 4];

var a1 = new A();
var a2 = new A();

當(dāng)使用構(gòu)造函數(shù)新建實(shí)例對象時,各個實(shí)例對象都會擁有由this指定的屬性。


5.2

5.3 實(shí)例對象屬性賦值和使用時的關(guān)系(可以類比LHS和RHS)

5.3.1 使用時的繼承關(guān)系

使用實(shí)例對象屬性時,如果該屬性不存在于實(shí)例對象,就會使用其原型對象該屬性。
在代碼段5.2執(zhí)行之后做如下操作:

// 代碼段5.3.1,承接代碼段5.2
console.log('a1.m:', a1.m);
console.log('a2.m:', a2.m);

console.log('a1.n:', a1.n);
console.log('a2.n:', a2.n);

console.log('a1.p:', a1.p);
console.log('a2.p:', a2.p);

console.log('a1.q:', a1.q);
console.log('a2.q:', a2.q);
5.3.1

如圖所示,打印a1.m會找到其實(shí)例對象屬性m,而a1.p會找到其原型對象屬性p。

5.3.2 使用查找時的先后關(guān)系(賦值時的覆蓋關(guān)系)

使用實(shí)例對象屬性時,優(yōu)先從實(shí)例對象查找該屬性,如果該屬性不存在,就會使用其原型對象該屬性。而對實(shí)例對象屬性的賦值操作,將會直接使用實(shí)例對象屬性。

// 代碼段5.3.2.1,承接代碼段5.3.1
a1.p = 11;

console.log('a1.p:', a1.p);
console.log('a2.p:', a2.p);
5.3.2.1

說明,a1.p是給a1添加了屬性p并賦值11,但是此時a2是沒有該屬性的,所以對a2.p的使用會查找到A.prototype。

要注意的是,這里實(shí)例對象屬性之間是互相獨(dú)立的,而原型對象屬性是共享的。

// 代碼段5.3.2.2,承接代碼段5.3.2.1
a1.n.push(3);
a1.q.push(5);

console.log('a1.n:', a1.n);
console.log('a2.n:', a2.n);

console.log('a1.q:', a1.q);
console.log('a2.q:', a2.q);
5.3.2.2

可以看到,對原型對象屬性為對象時的操作( 堆操作)會影響到其他的實(shí)例對象對該屬性的使用。

另外,還有一點(diǎn)要注意,如果你對對象使用的是賦值操作,并不會影響到原型屬性。不明白的同學(xué)再看一下5.3.2.1。

6. 總結(jié)

其實(shí),我們用代碼解釋一下new函數(shù)構(gòu)造一個實(shí)例的過程。
對于

function A(m, n) {
    this.m = m;
    this.n = n;
}

var a = new A(1, 2);
console.log(a);
6.1

中的 new A(1,2)這一步操作,其實(shí)可以分解為如下四個步驟:

// 新建一個空對象obj
let obj  ={};
// obj的__proto__屬性指向原型對象
obj.__proto__ = A.prototype;
// 將構(gòu)造函數(shù)的this綁定obj,傳入構(gòu)造函數(shù)的參數(shù),并將返回結(jié)果賦值給result
let result = A.apply(obj, arguments);
// 如果result存在且result是對象或者函數(shù),則構(gòu)造函數(shù)返回result,否則將返回obj
return (result && (typeof(result) === 'object' || typeof(result) === 'function')?result:obj);
  1. 新建一個空對象obj
  2. obj的proto屬性指向原型對象
  3. 將構(gòu)造函數(shù)的this綁定obj,傳入構(gòu)造函數(shù)的參數(shù),并將返回結(jié)果賦值給result
  4. 如果result存在且result是對象或者函數(shù),則構(gòu)造函數(shù)返回result,否則將返回obj

我們可以試著模擬一個函數(shù)myNewA,如下:

function A(m, n) {
    this.m = m;
    this.n = n;
}

function myNewA() {
    let obj  ={};
    obj.__proto__ = A.prototype;
    let result = A.apply(obj, arguments);
    return (result && (typeof(result) === 'object' || typeof(result) === 'fucntion')?result:obj);
}
var a = myNewA(1, 2)
console.log(a);
6.2

可以看到,結(jié)果和6.1一模一樣,當(dāng)然了,真正的new構(gòu)造函數(shù)的過程不會是這么簡單,我們只是通過這個例子使大家能夠加深對構(gòu)造函數(shù),原型對象和實(shí)例對象的理解。

參考

javascript面向?qū)ο笙盗械谝黄獦?gòu)造函數(shù)和原型對象
JS入門難點(diǎn)解析10-創(chuàng)建對象
深入理解js構(gòu)造函數(shù)
JavaScript構(gòu)造函數(shù)詳解
BOOK-《JavaScript高級程序設(shè)計(第3版)》第6章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本文把程序員所需掌握的關(guān)鍵知識總結(jié)為三大類19個關(guān)鍵概念,然后給出了掌握每個關(guān)鍵概念所需的入門書籍,必讀書籍,以及...
    dle_oxio閱讀 11,384評論 6 244
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,554評論 19 139
  • (注1:如果有問題歡迎留言探討,一起學(xué)習(xí)!轉(zhuǎn)載請注明出處,喜歡可以點(diǎn)個贊哦!)(注2:更多內(nèi)容請查看我的目錄。) ...
    love丁酥酥閱讀 855評論 3 2
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,034評論 25 709
  • 總會有些不懂我們的人,對我們產(chǎn)生偏見。 裝純。有同事,也有同學(xué)這樣說過我。不明白他(她)為什么會這樣說?說實(shí)話,...
    佛林閱讀 553評論 0 0

友情鏈接更多精彩內(nèi)容