JavaScript 面向對象 (1)

<i>以下 JS 為 ECMA-262 第五版</i>


什么是面向對象

面向對象是一種和面向過程不同的思維方法。用類作為模版做蛋糕。 但是 JS 沒有類,它的對象和其他基于類的語言的對象不同;
Java 的對象: 有三個主要特征,行為·狀態·標志。即為有自己的可調用方法,保存著描述當前特征的信息,唯一身份;
Python的對象: 包含特性和方法的合集;
JS 的對象: 無序屬性的集合,其屬性可以包含基本值、對象或者函數; (ECMA-262)實際上是散列表;


既然 JS 的對象與典型面向對象的語言不同,為什么 JS 可以面向對象?

可能需要回答的問題:

  • JS 的對象是怎樣的?
  • 面向對象的需求是什么?
  • JS 的對象是否可以符合面向對象的需求?要怎么做才可以符合?
  • JS 的【引用類型】和【類】的區別
1. JS 的對象是怎樣的?
  1. JS 的對象是無序屬性的集合,其屬性可以包含基本值、對象或者函數; (ECMA-262) 實際上是散列表;
  2. JS 的對象是 Object 的實例(即為引用類型的實例)也被稱為引用類型的值。每個實例都具有7個屬性/方法,它們分別是: constructor, hasOwnProperty(), isPrototypeOf(), propertyIsEnumerable(), toLocaleString(), toString(), valueOf()。
  3. JS 的對象有兩種屬性: 數據屬性和訪問器屬性。數據屬性包括 [[Configurable]], [[Enumberable]], [[Writable]], [[Value]]。訪問器屬性包括一對 getter, setter 函數,有以下4個特性 [[Configurabel]], [[Enumberable]], [[Get]], [[Set]]。這兩種屬性均使用 Object.defineProperty() 修改。 如下:
 //ECMAScript 5 Object.defineProperty() method (IE9 and Firefox 4).
        var book = {
            _year: 2004,
            name: "javascript",
            edition: 1
        };
        // 修改數據屬性
        Object.defineProperty(book, "name" , {
            configurable : false,
            value : "python"
        })

        // 修改訪問器屬性
        Object.defineProperty(book, "year", {
            get: function(){
                return this._year;
            },
            set: function(newValue){
            
                if (newValue > 2004) {
                    this._year = newValue;
                    this.edition += newValue - 2004;
                
                }
            }
        });
        
        alert(book.name) //python
        delete book.name  //因設置可刪除性 configurable 為 false ,所以不可刪除
        alert(book.name) //python

        book.year = 2005;
        alert(book.edition);   //2

2. 面向對象的需求是什么?

封裝、繼承、多態
<b>封裝</b> 可以隱藏實現細節,使得代碼模塊化;
<b>繼承</b> 可以擴展已存在的代碼模塊(類);它們的目的都是為了——代碼重用。
<b>多態</b> 則是為了實現另一個目的——接口重用!
點擊查看摘自

3. JS 是否可以符合面向對象的要求?
  • JS 是否可以實現封裝?
    此問題可以演化為JS是否可以隱藏細節,使得代碼模塊化?
    已知的模式為以下: 工廠模式 、 構造函數模式 、 原型模式 、ES6的Class
    <b>工廠模式</b> :解決了相似對象的問題,卻沒有解決對象識別的問題,不知道對象的類型。
    <b>構造函數模式</b> :
        function Person(name, age, job){
            this.name = name;
            this.age = age;
            this.job = job;
            this.sayName = function(){
                alert(this.name);
            };    
        }
        
        var person1 = new Person("Nicholas", 29, "Software Engineer");
        var person2 = new Person("Greg", 27, "Doctor");
        
        person1.sayName();   //"Nicholas"
        person2.sayName();   //"Greg"
        
        alert(person1 instanceof Object);  //true
        alert(person1 instanceof Person);  //true
        alert(person2 instanceof Object);  //true
        alert(person2 instanceof Person);  //true
        
        alert(person1.constructor == Person);  //true
        alert(person2.constructor == Person);  //true      

      // 相對于 constructor, instanceof 對于對象類型的檢測更為可靠

      //每個方法都要在每個實例上重新創建
        alert(person1.sayName == person2.sayName); //false
      //可以把函數定義的方法轉移到函數外 如下
        function Person(name, age, job){
            this.name = name;
            this.age = age;
            this.job = job;
            this.sayName = sayName;
        }
        
        function sayName(){
            alert(this.name);
        }

構造函數實際上經歷了: 創建對象 ==》 this 指向新對象 ==》 為新對象添加屬性 ==》返回新對象
缺點是 - 轉移了函數定義的方法,方法放到全局,破壞了封裝性。
<b>原型模式</b> :

        function Person(){
        }
        
        Person.prototype.name = "Nicholas";
        Person.prototype.age = 29;
        Person.prototype.job = "Software Engineer";
        Person.prototype.sayName = function(){
            alert(this.name);
        };
        
        var person1 = new Person();
        person1.sayName();   //"Nicholas"
        
        var person2 = new Person();
        person2.sayName();   //"Nicholas"
      
        alert(person1.sayName == person2.sayName);  //true
        
        alert(Person.prototype.isPrototypeOf(person1));  //true
        alert(Person.prototype.isPrototypeOf(person2));  //true
        
        //only works if Object.getPrototypeOf() is available
        if (Object.getPrototypeOf){
            alert(Object.getPrototypeOf(person1) == Person.prototype);  //true
            alert(Object.getPrototypeOf(person1).name);  //"Nicholas"
        }

<b>ES6 class 模式</b> :

class Point{
  constructor(x,y){
        this.x = x
        this.y = y
    }
    toString(){
        return "(" + this.x + "," + this.y +")"
    }
}

ES6的語法實際上是一個語法糖, 上述模式相當于組合使用函數模式和原型模式:

function Point(x,y){
    this.x = x
    this.y = y
}
Point.prototype.toString = function(){
    return "(" + this.x + "," + this.y +")"
}

<b>動態原型模式</b> :只有在方法不存在的情況下才添加方法。

      function Person(name, age, job){
      
          //properties
          this.name = name;
          this.age = age;
          this.job = job;
          
          //methods
          if (typeof this.sayName != "function"){
          
              Person.prototype.sayName = function(){
                  alert(this.name);
              };
              
          }
      }

      var friend = new Person("Nicholas", 29, "Software Engineer");
      friend.sayName();

  • JS 如何繼承?
    JS 的函數沒有簽名,所以不支持接口繼承,只支持實現繼承,是依靠原型鏈來實現的。
    每個函數都有原型對象 prototype,原型對象的 constructor 屬性是一個指向protoype 屬性所在函數的指針。讀取對象的某個屬性時,會一級一級向上搜索,從對象本身開始。如果在實例中找到了具有給定名字的屬性,就返回該屬性的值,如果沒有找到,則繼續搜索指針指向的原型對象。

關于幾種繼承方式,在下一篇

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容