面向對象的小九九 面向對象

面向對象

本人能力有限,有誤請斧正

本文旨在複習面向對象(不包含es6)

本文學習思維

  1. 創建對象的方式,獲取對象屬性
  2. 構造函數,構造函數的new 做了什麼
  3. 原型與原型對象
  4. 原型鏈
  5. 繼承(借用構造繼承、原型繼承、組合繼承、寄生組合繼承)

獲取對象屬性的三個方法

  1. for...in..

  2. Object.keys() ie9以上放心使用
    keys的支持

  3. Object.getOwnPeropertyNames 會把所有屬性枚舉出來(如數組的length)

創建對象的方法

  1. 字面量 var o = {};
  2. 構造函數 var o = new Object()
  3. Object.create() var o = Object.create( )

object.create()有兩個參數第一個是要新創建對象的原型對象, 第二個可選:自己定義的屬性,需要配置麻煩一般不用,返回一個新對象,帶着指定的原型對象和屬性

通過給Object.create()參數傳null可以獲得一個純淨的沒有原型的對象。原因是null是原型鏈的鏈末

輸出查看原型鏈發現create在創造一個對象會存在傳入的屬性與方法
這個方法太適合繼承了,他會直接繼承傳入的屬性和方法
通過測試傳入{}時,也會存在Object.__proto__ 指向 Object.__proto__;

// Object.create() 實現方式 
// 實際這個是原型式繼承的核心
var object = function(proto){
    var F = function(){}; // 創建一個對象
    F.prototype = proto; //變量原型指向傳入對象
    return new F();
}

構造函數、實例、原型、原型鏈

參考資料:MDN-繼承與原型鏈

構造函數:

  1. 是一個函數
  2. 首字母大寫
  3. 使用new關鍵字創建

構造函數(函數聲明或者函數表達式)本質還是函數,只是用來創建對象,還有個慣例就是首字母大寫

// 構造函數
function Person(){}
// 調用構造函數,創建對象
var p1 = new Person();

爲什麼說構造函數特殊呢,首先聊聊new關鍵字

參考:

  1. MDN-new運算符
  2. 高程3--p145
  3. 阮一峯博客

通過new關鍵字可以創建新對象!

在new一個對象的時候做了什麼事?4個步驟(高程3)

  1. 創建一個新對象
  2. 將構造函數的作用域賦給新對象 (因此this就指向了這個新對象)
  3. 執行構造函數中的代碼(爲這個新對象添加屬性)
  4. 返回新對象(如果構造函數中返回了其他對象,則返回其他對象)

MDN簡化版,只討論過程,無法傳參

    // MND簡化
    
    /*
    一個繼承自 Foo.prototype 的新對象被創建。
    
    使用指定的參數調用構造函數 Foo ,並將 this 綁定到新創建的對象。
    new Foo 等同於 new Foo(),也就是沒有指定參數列表,Foo 不帶任何參數調用的情況。
    
    由構造函數返回的對象就是 new 表達式的結果。如果構造函數沒有顯式返回一個對象,則使用步驟1創建的對象。
    (一般情況下,構造函數不返回值,但是用戶可以選擇主動返回對象,來覆蓋正常的對象創建步驟)
    */
    
    var Foo = function(){};
    var _new2 = function(fn){
        var o = Object.create(fn.prototype);
        var k = fn.call(o);
        if(typeof k === 'object'){
            return k;
        }else{
            return o;
        }
    }
    var f = _new2(Foo);

阮老師的版本可以傳參,而且很詳細了

    function _new(/* 構造函數 */ constructor, /* 構造函數參數 */ params) {
      // 將 arguments 對象轉爲數組
      var args = [].slice.call(arguments);
      // 取出構造函數
      var constructor = args.shift();
      // 創建一個空對象,繼承構造函數的 prototype 屬性
      var context = Object.create(constructor.prototype);
      // 執行構造函數
      var result = constructor.apply(context, args);
      // 如果返回結果是對象,就直接返回,否則返回 context 對象
      return (typeof result === 'object' && result != null) ? result : context;
    }

    // 實例
    var actor = _new(Person, '張三', 28);

實例是什麼

構造函數是對一個對象的抽象描述,實例則是對象的具體表現

原型對象(prototype)

好吧!大boss出場,都說javaScript最具有特色的就是原型

參考:

  1. 高程3 --p147

原型是什麼?(高程3)

我們創建的每個函數都有一個prototype(原型) 屬性,這個屬性是一個指針,指向一個對象

  1. 函數的屬性
  2. 原型指向一個對象

理解原型對象 高程3 -- p148有興趣可以去讀一下

無論什麼時候只要創建了一個新函數,就會根據一組特定的規則爲該函數創建一個prototype屬性,這個屬性將指向函數的原型對象。在默認情況下,所有的原型對象會自動獲得一個constructor(構造函數)屬性,這個屬性包含一個指向prototype屬性所有函數的指針。通過這個構造函數,我們還可以繼續爲原型對象添加其他屬性和方法

按照書上的理解:


簡述:([[Prototype]] === __proto__

  1. 所有構造函數有一個屬性指向原型對象(prototype)
  2. 所有由構造器生成的實例對象中有個__poroto__指向原型對象
  3. 原型對象中都有一個constructor的屬性,指向構造函數
        var Person = function() {};
        Person.prototype.age = 1;
        var p = new Person();
        var p2 = new Person();
        console.log(p.__proto__ === Person.prototype); // true
        console.log(p2.__proto__ === Person.prototype); // true
        console.log(Person === Person.prototype.constructor); // true

原型鏈

原型鏈就是在查找到某個屬性或者方法不斷向上查找的一個過程

MDN-非常具有代表的簡化原型鏈

// 讓我們假設我們有一個對象 o, 其有自己的屬性 a 和 b:
// {a: 1, b: 2}
// o 的 [[Prototype]] 有屬性 b 和 c:
// {b: 3, c: 4}
// 最後, o.[[Prototype]].[[Prototype]] 是 null.
// 這就是原型鏈的末尾,即 null,
// 根據定義,null 沒有[[Prototype]].
// 綜上,整個原型鏈如下: 
// {a:1, b:2} ---> {b:3, c:4} ---> null

console.log(o.a); // 1
// a是o的自身屬性嗎?是的,該屬性的值爲1

console.log(o.b); // 2
// b是o的自身屬性嗎?是的,該屬性的值爲2
// 原型上也有一個'b'屬性,但是它不會被訪問到.這種情況稱爲"屬性遮蔽 (property shadowing)"

console.log(o.c); // 4
// c是o的自身屬性嗎?不是,那看看原型上有沒有
// c是o.[[Prototype]]的屬性嗎?是的,該屬性的值爲4

console.log(o.d); // undefined
// d是o的自身屬性嗎?不是,那看看原型上有沒有
// d是o.[[Prototype]]的屬性嗎?不是,那看看它的原型上有沒有
// o.[[Prototype]].[[Prototype]] 爲 null,停止搜索
// 沒有d屬性,返回undefined


再用對象表示一個

// 屬性遮蔽
function Person() {
    this.name = '111';
}
Person.prototype.name = '222';

var p1 = new Person();
console.log(p1.name); // 111
console.log(p1.__proto__.name); // 222


var p2  = new Person();
console.log(p2.age);

現在要查找p2.age屬性

  1. 實例對象中有沒有?沒有
  2. 實例對象通過__prope__找到原型對象,原型對象中有麼?沒有
  3. 找到Object的原型中查找有麼?沒
  4. 找到null這個對象,作爲作用域的鏈末,也沒有,這個值就是undefined

屬性屏蔽就是找到了就不會再找了(實例上的屬性>原型鏈上的屬性),實際還是存在

幾種能遇到的操作符

  1. in操作符
  2. isPrototypeOf()
  3. Object.getPrototypeOf()
  4. instanceof (對象)
  5. typeof
in操作符

如果指定的屬性在指定的對象或其原型鏈中,則in 運算符返回true。語法:prop in object

isPrototypeOf()

isPrototypeOf() 方法用於測試一個對象是否存在於另一個對象的原型鏈上。

function Foo() {}
function Bar() {}
function Baz() {}

Bar.prototype = Object.create(Foo.prototype);
Baz.prototype = Object.create(Bar.prototype);

var baz = new Baz();

console.log(Baz.prototype.isPrototypeOf(baz)); // true
console.log(Bar.prototype.isPrototypeOf(baz)); // true
console.log(Foo.prototype.isPrototypeOf(baz)); // true
console.log(Object.prototype.isPrototypeOf(baz)); // true

instanceof運算符返回一個布爾值,表示對象是否爲某個構造函數的實例。

instanceof的原理是檢查右邊構造函數的prototype屬性,是否在左邊對象的原型鏈上。(判斷他們的地址指向是否一致)。有一種特殊情況,就是左邊對象的原型鏈上,只有null對象。這時,instanceof判斷會失真。

幾點instanceof的注意

  1. 用於對象(由於instanceof的原理)
  2. 與null有關要注意
//instanceof判斷會失真
var obj = Object.create(null);
typeof obj // "object"
Object.create(null) instanceof Object // false


//null作爲一個特殊的Object卻不屬於Object創建的實例,null原型鏈的鏈末
undefined instanceof Object // false
null instanceof Object // false

// instanceof 用於對象
var str = '1'
var str2 = new String('2');
str instanceof String // false
str2 instanceof String // true
typeof
typeof 1 //number
typeof '' // string
typeof undefined //undefined
typeof true // boolean
typeof function(){} // function
typeof {}  // object
typeof []  // object
typeof null // object
typeof Symbol() //symbol ES6

繼承:

繼承有幾種

我用我總結了一些思維導圖

  1. 創建對象
  2. 繼承的優缺點

這裏把繼承的幾種方式羅列出來方便查閱,以下大多是代碼,簡易的我總結在思維導圖中了

1.原型(鏈)繼承

關鍵點是要打通原型鏈
由於原型對象是函數初次創建就會存在的對象,所以會共享
共享就會存在共享問題

優點:

  1. 共享屬性與方法
  2. 可以通過instanceof來判斷關係

缺點:

  1. 共享問題
  2. 不能傳遞參數
        // 父類
        function SuperType() {
            this.property = true;
        }
        SuperType.prototype.getSuperValue = function () {
            return this.property;
        }
        
        // 子類
        function SubType() {
            this.subproperty = false;
        }
        // 繼承父類 打通原型鏈
        SubType.prototype = new SuperType();
        SubType.prototype.getSubValue = function () {
            return this.subproperty;
        }
        
        var instance = new SubType();
        console.log(instance.getSuperValue()); // true

借用構造函數

關鍵在於環境變量(this)的指向,由於每次創建都會創建一個新的this所以會擁有自己的屬性與方法,由於是改變this指向所以無法共享原型對象

優點:

  1. 私有屬性與方法
  2. 可以傳參數

缺點:

  1. 引用類型,重複創建,冗餘浪費內存
  2. 無法共享
  3. 無法判斷關係
        // 父類
        function SuperType() {
            this.colors = ['red'];
        }
        // 子類
        function SubType() {
            // 繼承父類
            SuperType.call(this);
        }
        var instance1 = new SubType();
        colors.push('black');
        cosnole.log(instance1.colors); // red,black
        var instance2 = new SubType();
        console.log(instance2.colors); // red

組合式繼承

組合了原型繼承與借用構造函數繼承繼承了優點,但是由於組合,所以創建了兩次對象,造成輕微的浪費空間

優點:

  1. 私有屬性和方法
  2. 共享屬性與方法
  3. 可以確認實例與構造函數之間的關係

缺點

  1. 造成內存的冗餘浪費
        // 父類
        function SuperType(name) {
            this.name = name;
            this.colors = ['red'];
        }
        SuperType.prototype.sayName = function () {
            console.log(this.name);
        }
        // 子類
        function SubType(name, age) {
            // 繼承屬性
            SuperType.call(this, name);
            this.age = age;
        }
        // 繼承方法
        SubType.prototype = new SuperType();
        SubType.prototype.sayAge = function () {
            console.log(this.age);
        }
        var instance1 = new SubType('name1', 1);
        instance1.colors.push('black');
        console.log(instance1.colors); // red,black
        instance1.sayName(); // name1
        instance1.sayAge(); // 1

        var instance2 = new SubType('name2', 2);
        console.log(instance2.colors); // red
        instance2.sayName(); // name2
        instance2.sayAge(); // 2
寄生組合繼承

寄生組合繼承是把原型繼承給改掉,實際上就是想要父級的原型鏈,不一定要創建對象所以有了寄生組合繼承,該繼承是目前最完善的繼承方式

        // 寄生繼承
        function inheritPrototype(subType, superType) {
            var prototype = Object.create(superType.prototype);
            prototype.constructor = subType;
            subType.prototype = prototype;
        }
        // 父類
        function SuperType(name) {
            this.name = name;
            this.colors = ['red'];
        }
        SuperType.prototype.sayName = function () {
            console.log(this.name);
        }   
        // 子類繼承
        function SubType(name, age) {
            SuperType.call(this, name); 
        }
        inheritPrototype(SubType, SuperType);
        SubType.prototype.sayAge = function () {
            console.log(this.age)
        }


參考資料:

  1. MDN-對象
  2. 冴羽的博客
  3. 阮一峯的網絡日誌
  4. javaScript高級程序設計第三版

阮一峯的網絡日誌:

  1. Javascript繼承機制的設計思想
  2. Javascript 面向對象編程(一):封裝
  3. Javascript面向對象編程(二):構造函數的繼承
  4. Javascript面向對象編程(三):非構造函數的繼承
  5. 《JavaScript 標準參考教程(alpha)》,by 阮一峯

看了高程3與阮一峯老師的博客,結合起來更加好理解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章