前端基礎進階(九):詳解面向對象、構造函數、原型與原型鏈

http://www.javashuo.com/article/p-fnxpmwol-er.html  https://yangbo5207.github.io/wutongluo/前端

說明:此處只是記錄閱讀前端基礎進階的理解和總結,若有須要請閱讀上面的連接git

1、對象的定義github

在JavaScript中對象被定義爲無序屬性的集合,其屬性能夠是基本類型,也能夠是對象,函數segmentfault


1.建立對象的兩種方式app

1) 使用new關鍵字函數

var o =new Object()

2)使用字面量形式this

  var person = {
        name: "xiaoming",
        age: 13,
        getName: function () { return this .name}
    }

2.給對象添加屬性和方法的兩種方法spa

    //方法1
    var o = new Object();
    o.name = "lilei"; //添加屬性name並賦值
    o.getName = function () { return this .name}//添加方法

    //方法2
    var person = {
        name: "xiaoming",
        age: 13,
        getName: function () { return this .name}
    }

3.訪問對象屬性的兩種方式prototype

 //訪問name屬性的兩種方式
    person.name

    person["name"]

若是要同時訪問一個對象裏面的多個屬性能夠這樣。注:forEach不能寫成foreach,不然會報錯,而且上一個語句必須有分號結束3d

    //方法2
    var person = {
        name: "xiaoming",
        age: 13,
        getName: function () { return this.name }
    };//必須寫分號,不然下面的forEach會報錯

     ['name', 'age'].forEach(function (item) {//是forEach,不是foreach
        console.log(person[item]);
    });

 

2、工廠模式

使用上面的方法建立對象很簡單,可是若是須要用到兩個對象,好比Tom和Jack就不得不把代碼相似的代碼寫兩遍,浪費時間,這樣工廠模式就出現了。

工廠模式就是按照給定的模式建立出咱們想要的對象,想建立多少就建立多少

 //工廠模式
    function createPerson(name, age) {
        var o = new Object();
        o.name = name;
        o.age = age;
        o.getName = function () { return this.name; };
        return o;
    }

    var perTom = createPerson("Tom", 12);
    var perJack= createPerson("Jack",14);

 

使用工廠模式雖然簡單,可是有兩個缺陷:

1)不能使用instanceof判斷對象的實例類型

var person = {};
    var foo = function () { }

    console.log(obj instanceof Object);  // true
    console.log(foo instanceof Function); // true

2)當有不一樣的實例對象時,每一個對象都要生成一個getName方法,每次都要爲getName分配一個單獨的內存空間

 3、構造函數

爲了解決第一個問題就須要用到構造函數了。

 //構造函數
    function Person(name, age) {
        this.name = name;
        this.age = age;
        this.getName = function () { return this.name; };
    }

    var perNike = new Person("Nike", 13);
    console.log(perNike instanceof Person);//true
    console.log(perNike.getName());//Nike

構造函數與通常函數並無本質區別,首字母大寫只是約定用來區別構造函數和普通函數的。任何函數均可以當作是構造函數用來產生對象

new關鍵字能夠產生一個對象,調用new時函數內部執行了以下過程:

1)產生一個新對象,把函數的this指向這個對象

2)新對象的原型指向構造函數的原型

3)給新對象添加屬性和方法

4)返回新對象

4、原型

爲了解決工廠模式的第二個問題須要用到另外一個東西——原型

每個函數都有一個prototype屬性,該屬性指定一個對象,這個對象就是原型。

從構造函數部分咱們指定每個new出來的示例對象都有一個__proto__屬性,這個屬性指向構造函數的原型對象,若是咱們建立對象時經過prototype屬性掛載一些方法和屬性,那麼實例對象就能夠經過__proto__屬性訪問以前掛載的屬性和方法,而每一個實例對象都是訪問同一個原型對象這樣就解決了工廠模式的第二個問題。

咱們把掛載在原型對象上的屬性和方法叫作公有屬性和公有方法(相似C#的static屬性和方法),由於能夠被全部實例對象訪問;把在構造函數中經過this聲明的屬性和方法叫作私有屬性和方法,由於它只屬於一個實例對象。

能夠經過一個例子看構造函數,實例對象和原型三者的關係

// 聲明構造函數
function Person(name, age) {
    this.name = name;
    this.age = age;
}

// 經過prototye屬性,將方法掛載到原型對象上
Person.prototype.getName = function() {
    return this.name;
}

var p1 = new Person('tim', 10);
var p2 = new Person('jak', 22);
console.log(p1.getName === p2.getName); // true

 

 經過圖片能夠看出構造函數和實例對象都指向原型對象,而原型constructor屬性指向構造函數

若是構造函數中聲明瞭和原型中同樣的屬性和方法,會優先訪問實例對象中的屬性和方法

function Person(name,age){
  this.name = name;
  this.age=age;
  this.getName=function(){
    console.log('this is constuctor.');
}     
      
}

Person.prototype.getName(){return this .name;};

var p1=new Person('Tim',12);
p1.getName();//this is constuctor.

咱們能夠經過in判斷一個對象是否擁有某個屬性或方法無論這個屬性或對象是否是掛載在原型對象上

 

function Person(name,age){
  this.name = name;
  this.age=age;
  this.getName=function(){
    console.log('this is constuctor.');
}     
      
}

Person.prototype.getName(){return this .name;};

var p1=new Person('Tim',12);

console.log('name' in p1);//true

比較經常使用的用法是判斷頁面是否在移動端打開

var isMobile='ontouchstart' in document;

有多個原型能夠這樣寫

function Person(){}

Person.prototype.getName(){}
Person.prototype.getAge(){}
....

還有更簡單的寫法,可是這種寫法是給Prototype新建了一個原型對象,並非原來的原型對象,須要顯式指定constructor:Person

function Person(){}

Person.prtotype={
  constructor:Person,
  getName:function(){},
  getAge:function()(),
    ....      
}

 5、原型鏈

咱們指定每一個方法都有一個toString()方法,可是這個方法從哪裏來的呢

先聲明一個函數

function add() {}

能夠用下圖來展現這個函數的原型鏈

 

其中add是Function的實例,Function的原型又是Object的實例對象,這樣就構成了一條原型鏈。經過__proto__屬性,add能夠訪問原型鏈上面全部的屬性和方法。

 

6、繼承

通常咱們須要結合構造函數和原型來建立一個對象,因此當須要繼承時也要考慮構造函數和原型的繼承。

構造函數的繼承

構造函數的繼承只須要使用call/apply把父級構造函數調用一下就能夠了,這樣子對象就具備了父對象在構造函數中定義的屬性和方法

    function Person(name, age) {
        this.name = name;
        this.age = age;
    }

    Person.prototype.getName = function () { return this.name; };
    Person.prototype.getAge = function () { return this.age; };

    function Student(name, age, grade) {
        Person.call(this, name, age);
        this.grade = grade;
    }

原型的繼承須要把子對象的原型設置爲父級的一個實例,加入原型鏈中

// 繼承原型
Student.prototype = new Person(name, age);

// 添加更多方法
Student.prototype.getLive = function() {}

更好的方法是定義一個空對象,讓這個空對象的__proto__屬性指向父級對象的原型,並封裝成一個方法

    function create(proto, options) {
        var tmp = {};//聲明一個空對象
        tmp.__proto__ = proto; //把臨時對象的__proto__屬性指向父類的原型對象,使得子類能夠訪問原型鏈上的全部方法和屬性
        Object.defineProperties(tmp, options); //把須要公開的屬性和方法掛載再子類的原型tmp上面
        return tmp;
        };


    Student.prototype = create(Person.prototype, {
        constructor: { value: Student },
        getGrade: { value: function () { return this.grade; } }
    });

最後驗證一下繼承的正確性,完整代碼

 function Person(name, age) {
        this.name = name;
        this.age = age;
    }

    Person.prototype.getName = function () { return this.name; };
    Person.prototype.getAge = function () { return this.age; };

    function Student(name, age, grade) {
        Person.call(this, name, age);
        this.grade = grade;
    }

    function create(proto, options) {
        var tmp = {};//聲明一個空對象
        tmp.__proto__ = proto; //把臨時對象的__proto__屬性指向父類的原型對象,使得子類能夠訪問原型鏈上的全部方法和屬性
        Object.defineProperties(tmp, options); //把須要公開的屬性和方法掛載再子類的原型tmp上面
        return tmp;
        };


        Student.prototype = create(Person.prototype, {//除了使用本身封裝的create方法能夠用Object.create中現有的方法代替,和封裝的方法是同樣的
        constructor: { value: Student },
        getGrade: { value: function () { return this.grade; } }
    });

    var std1 = new Student("Nike", 13, 1);
    console.log(std1.getName()); //Nike
    console.log(std1.getAge()); //13
    console.log(std1.getGrade()); //1

 

7、屬性類型

繼承的時候用到了Object.defineProperties,這個方法能夠用來設置屬性類型。

屬性類型顧名思義就是屬性的類型,直白點說就是屬性的屬性,用來描述屬性的特定

有幾種屬性類型,分別是:

1)configurable:表示改屬性是否能被delete,默認爲true,若是爲false,其餘屬性類型不能被改變;

2)enumerable:是否可以枚舉,即可否被for-in遍歷,默認爲true

3)writable:是否可以修改值,默認爲true;

4)value:該屬性的具體值是多少,默認undifined;

5)get:當訪問person.name的時候get方法被調用,該方法能夠定義返回的具體值是多少,默認爲undifined;

6)set:當給person.name賦值時,set方法被調用,用於給name屬性賦值,默認undifined;

須要注意的是value,writable不能與get,set同時設置,不然乎報錯,且get\set應該成對設置,不然可能形成不能賦值或者不能設置值

可使用Object.difineProperty方法設置屬性類型

configurable

    var tom = { name: 'Tom' };
    //利用delete上傳name屬性,返回true表示成功
    console.log(delete tom.name); //true

    //使用Object.defineProperty設置name屬性
    Object.defineProperty(tom, 'name', { value: 'jack', configurable: false });

    console.log(delete tom.name);//false,已經不能刪除
    console.log(tom.name); //jack
    tom.name = "nik";//嘗試改變name的值也不能修改
    console.log(tom.name); //仍是jack

enumerable

    var tom = { name: 'Tom',age:11 };

    var keys = [];
    for (var k in tom) {
        keys.push(k);
    }

    console.log(keys);//["name","age"]

    //使用Object.defineProperty設置name屬性
    Object.defineProperty(tom, 'name', { enumerable: false });

    var key2 = [];
    for (var k in tom) {
        key2.push(k);
    }
    console.log(key2); //["age"]

    //注意enumerable:false是指不能經過for-in循環來判斷name屬性在不在tom對象中,並非不能經過循環輸出該屬性的值,下面的語句依次輸出tom,11
    ["name", "age"].forEach(function (item) { console.log(tom[item]) });

 

writable

var tom = { name: 'Tom',age:11 };


    console.log(tom.name); //Tom
    tom.name = "Jack";
    console.log(tom.name); //Jack,修改爲功
    //使用Object.defineProperty設置name屬性
    Object.defineProperty(tom, 'name', { writable: false });
    tom.name = "jone";
    console.log(tom.name); //Jack ,修改失敗

 

value

  var tom = {  };

    Object.defineProperty(tom, 'name', { value: 'tom' });
    console.log(tom.name); //tom

get/set

 var tom = {  };

    Object.defineProperty(tom, 'name', {
        get: function () { return 'Tom'; },
        set: function (value) { console.log(  value + ' in set'); }
    });
    tom.name = 'jack';//jack in set
    console.log(tom.name);  //Tom

上面是一次設置一個屬性的屬性類型,可使用Object.defineProperties同時設置多個屬性的屬性類型

 var tom = {  };

    Object.defineProperties(tom, {
        name: {
            value: 'tom',
            configurable:false
        },
        age: {
            value: 12
        }
    });

    var descriptor = Object.getOwnPropertyDescriptor(tom, 'name'); //使用getOwnPropertyDescriptor獲取某個屬性的屬性類型
    console.log(descriptor); //Object { value: "tom", writable: false, enumerable: false, configurable: false }
相關文章
相關標籤/搜索