面向對象的小九九

面向對象

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

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

本文學習思惟java

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

獲取對象屬性的三個方法

  1. for...in..
  2. Object.keys() ie9以上放心使用

keys的支持git

  1. Object.getOwnPeropertyNames 會把全部屬性枚舉出來(如數組的length)

建立對象的方法

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

object.create()有兩個參數第一個是要新建立對象的原型對象, 第二個可選:本身定義的屬性,須要配置麻煩通常不用,返回一個新對象,帶着指定的原型對象和屬性es6

經過給Object.create()參數傳null能夠得到一個純淨的沒有原型的對象。緣由是null是原型鏈的鏈末github

輸出查看原型鏈發現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關鍵字

參考:app

  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);

MDN原型簡圖

阮老師的版本能夠傳參,並且很詳細了
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屬性全部函數的指針。經過這個構造函數,咱們還能夠繼續爲原型對象添加其餘屬性和方法

按照書上的理解:
MDN-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與阮一峯老師的博客,結合起來更加好理解
相關文章
相關標籤/搜索