原生js的三座大山之原型和原型鏈(1)

shuxiaotai發表於2018-09-14

一.原型規則

1.所有的引用型別陣列物件函式),都具有物件特性,即可自由擴充套件屬性,即xxx.axxx.b等(null除外)

2.所有的引用型別,都有__proto__屬性(隱式原型),屬性值是一個普通的物件

3.所有的函式,都有一個prototype屬性(顯式原型),屬性值也是一個普通的物件

4.所有引用型別__proto__屬性指向它的建構函式prototype的屬性值

5.當試圖得到一個引用型別屬性時,如果這個物件本身沒有這個屬性,那麼會去它的__proto__(即它的建構函式的prototype中)尋找

二.js中建立物件的方法

1.Object建構函式:

let o1 = new Object({name: 'sxt'});
複製程式碼

2.物件字面量

let o2 = {name: 'sxt'};
複製程式碼

上面兩種方式的原型都是Object,建構函式也是Object,即

console.log(o1.__proto__ === Object.prototype);   //true
console.log(o1.constructor === Object);   //true
console.log(o2.__proto__ === Object.prototype);  //true
console.log(o2.constructor === Object);  //true
複製程式碼

缺點:建立很多物件時,會有很多重複的程式碼

3.建構函式

let F = function (name) {
    this.name = name;
};
let o3 = new F('sxt');
複製程式碼

這種方式建立的物件的原型是F,建構函式也是F,即

console.log(o3.__proto__ === F.prototype);  //true
console.log(o3.constructor === F);  //true
複製程式碼

4.Object.create建立物件

let P = {name: 'sxt'};
let o4 = Object.create(P);
複製程式碼

這個物件的原型是P,建構函式是Object,即

console.log(o4.__proto__ === P);  //true
console.log(o4.constructor === Object);  //true
複製程式碼

而Object.create的內部實現類似於如下

Object.prototype.create2 = function (proto) {
    function F() {
    }
    F.prototype = proto;
    return new F();
};
複製程式碼

5.工廠模式

function createPerson(name, age) {
    let o = new Object();
    o.name = name;
    o.age = age;
    o.sayName = function () {
        return this.name;
    };
    return o;
}

let person1 = createPerson('sxt1', 1);
let person2 = createPerson('sxt2', 2);
console.log(person1.sayName());  //sxt1
console.log(person2.sayName());  //sxt2
console.log(person1.constructor === Object);  //true
console.log(person2.constructor === Object);  //true
複製程式碼

優點:可以批量建立物件

缺點:物件無法識別,不能知道物件的型別

6.建構函式模式

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

let person1 = new Person('sxt1', 1);
console.log(person1.__proto__ === Person.prototype);  //true
console.log(person1.constructor === Person);   //true
複製程式碼

缺點:每個方法都要在每個例項上建立,等同下面

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.sayName = new Function("return this.name")
}
複製程式碼

所以每個例項的方法都不是相同的,如下

console.log(person1.sayName === person2.sayName);  //false
複製程式碼

補充:

  • 建構函式的特點:
    • 沒有顯式建立物件
    • 直接將屬性和方法賦給了this物件
    • 沒有return語句
    • 函式名大寫
  • 建構函式其實也可以有返回值,如果是五種基本資料型別(NullUndefinedStringBooleanNumber)的話,返回值還是這個例項,但是如果是複雜資料型別的話(Object),則返回這個複雜資料型別,同時適用於其他引用型別(陣列,函式等)

返回String型別時:

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.sayName = function () {
        return this.name;
    };
    return '1';
}

let person = new Person('sxt', 1);
console.log(person);   // Person {name: "sxt", sayName: ƒ}
複製程式碼

返回Object型別時:

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

let person = new Person('sxt', 1);
console.log(person);   // {}
複製程式碼
  • new操作符的原理:
    • 建立一個空物件,繼承自建構函式的原型物件,即空物件.__proto__ = 建構函式.prototype
    • 將this和這個物件關聯,即this指向該物件
    • 執行建構函式中的程式碼,即為這個物件新增屬性
    • 如果建構函式中返回一個物件,將會取代前面那個空物件,否則結果為那個空物件 否則結果為那個空物件
let new2 = function (func) {
    let o = Object.create(func.prototype);
    let k = func.call(o);
    if (typeof k === 'object') {
        return k;
    }else {
        return o;
    }
};

function Person() {
    this.name = 'sxt';
    this.sayName = function () {
        return this.name;
    }
}
let person = new2(Person);
console.log(person.__proto__ === Person.prototype);  //true
console.log(person.constructor === Person);  //true
複製程式碼

7.原型模式

function Person() {
}
Person.prototype.name = 'sxt';
Person.prototype.age = 11;
Person.prototype.sayName = function () {
    return this.name;
};
let person1 = new Person();
let person2 = new Person();
console.log(person1.sayName());  //sxt
console.log(person1.sayName());  //sxt
console.log(person1.sayName === person2.sayName);  //true
複製程式碼

缺點: Person.prototype.nums = [1, 2, 3];即在原型物件建立引用型別時,所有的例項會共享,即當person1修改了這個屬性的值後,person2的屬性值也會改變

補充:

  • 函式都有一個prototype指向函式的原型物件

  • 原型物件只會取得constructor屬性,其他方法都從Object繼承而來

  • isPrototypeOf:用來判斷一個原型物件是不是某例項物件的原型

console.log(Person.prototype.isPrototypeOf(person1));   //true
複製程式碼
  • getPrototypeOf:用來獲取一個例項物件的原型
console.log(Object.getPrototypeOf(person1) === Person.prototype);  //true
複製程式碼
  • hasOwnProperty可以檢測這個屬性存在例項中(true)還是存在原型物件中(false)

  • 可以用person.name = 'sxt2'; 來遮蔽原型中的值,但是不能修改原型中的值,可以用delete person1.name;來刪除例項中的值,然後訪問到原型中的值

  • in操作符,不管在原型中還是例項中都會返回true,如"name" in person1

  • for in:返回例項中,原型可列舉的屬性,如果例項屬性toString覆寫了原型中的方法,那麼會返回例項中的toString,即使原型中的Enumerable是false

function Person() {
    this.age = 1;
}
Person.prototype.name = 'sxt';
Person.prototype.sayName = function () {
    return this.name;
};
let person1 = new Person();
for (let item in person1) {
    console.log(item)   //age name sayName
}
複製程式碼
function Person() {
    this.age = 1;
    this.toString = function () {
        console.log('例項中的toString');
    }
}
Person.prototype.name = 'sxt';
Person.prototype.sayName = function () {
    return this.name;
};
let person1 = new Person();
person1.toString()  //例項中的toString
for (let item in person1) {
    console.log(item)   //age toString name sayName
}
複製程式碼

不應該把for in用來迭代陣列,因為索引順序不一定有序

  • Object.keys(),可以列舉出所有物件中的可列舉的例項屬性
function Person() {
    this.age = 1;
    this.toString = function () {
        console.log('例項中的toString');
    }
}
Person.prototype.name = 'sxt';
Person.prototype.sayName = function () {
    return this.name;
};
let person1 = new Person();
console.log(Object.keys(person1));   //["age", "toString"]
複製程式碼
  • Object.getOwnPropertyNames()可以列舉出該物件的所有例項屬性
console.log(Object.getOwnPropertyNames(Person.prototype))  // ["constructor", "name", "sayName"]
複製程式碼
  • 原型的動態性:就算建立例項在修改原型之前,原型上的修改也能立即反映在例項中,如下
function Person() {
    this.age = 1;
    this.toString = function () {
        console.log('例項中的toString');
    }
}
let person1 = new Person();
Person.prototype.name = 'sxt';
Person.prototype.sayName = function () {
    return this.name;
};
console.log(person1.sayName())  //sxt
複製程式碼

但是如果重寫了整個原型物件,會找不到這個屬性,因為切斷了原型物件和建構函式之間的聯絡

function Person() {
    this.age = 1;
    this.toString = function () {
        console.log('例項中的toString');
    }
}
let person1 = new Person();
Person.prototype = {
    name: 'sxt',
    sayName: function () {
        return this.name;
    }
};
console.log(person1.sayName())  //sxt
複製程式碼

8.組合建構函式模式和原型模式:

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.nums = [1, 2, 3];
}
Person.prototype.sayName = function () {
    return this.name;
};
let person1 = new Person('sxt1', 1);
let person2 = new Person('sxt2', 2);
console.log(person1.sayName === person2.sayName);
person1.nums.push(4);  //true
console.log(person1.nums);  //[1, 2, 3, 4]
console.log(person2.nums);  //[1, 2, 3]
複製程式碼

優點:

1.解決了每次都要在例項上建立出一個新的方法的問題

2.引用型別建立在例項上,不會共享,某個例項修改了它的值不會影響到其他例項

github地址:github.com/shuxiaotai/…
gitbook地址:shuxiaotai.gitbooks.io/front_summa…

相關文章