1. 程式人生 > 實用技巧 >JS原型鏈模式和繼承模式

JS原型鏈模式和繼承模式

原型鏈模式

例項識別:
建構函式模式中擁有了類和例項的概念,並且例項和例項之間是相互獨立開的

function CreateJsPerson(name, age){
    this.name = name;
    this.age = age;
}
CreateJsPerson.prototype.writeJs = function(){
        console.log("my name is " + this.name + ", i can                write js ~~")
    }

基於建構函式模式的原型模式解決了,方法或者屬性共有的問題,想讓誰共有就把他放在CreateJsPerson.prototype上即可

js中規定的

prototype

  1. 每一個函式資料型別(普通函式,類)都有一個天生自帶的屬性:prototype(原型),並且這個屬性是一個物件資料型別的值

constructor

  1. 並且在prototype上瀏覽器天生給它加了一個屬性constructor(建構函式),屬性值是當前函式(類)本身

_ proto _

  1. 每一個物件資料型別(普通的物件,例項,prototype..)也天生自帶一個屬性: _ _proto _ _,屬性值是當前例項所屬類的原型(prototype)
function Fn(){
    this.x = 100;
}
Fn.prototype.getX = function(){
    console.log(this.x);
}
var f1 = new Fn;
var f2 = new Fn;

console.log(Fn.prototype.constructor === Fn); // true
console.log(f1 instanceof Object); // true
  • f1 instanceof Object -> true 因為f1通過_ _ protp _ _ 可以向上級查詢,不管查詢多少級都可以查詢到Object
  • 在Object.prototype上沒有_ _protp _ _ 這個屬性

原型鏈查詢機制

  1. 通過 物件名.屬性名 的方式獲取屬性值的時候, 首先在物件的私有的屬性上進行查詢, 如果私有中存在這個屬性,則獲取的是私有的屬性值;
  2. 如果私有的沒有,則通過__proto__找到所屬類的原型, 類的原型上定義的屬性和方法都是當前例項公有的屬性和方法, 原型上存在的話, 獲取的是共有的屬性值;
  3. 如果原型上也沒有,則繼續通過原型上的__proto__繼續向上查詢, 一直找到Obejct.prototype為止
console.log(f1.getX === f2.getX); // true
console.log(f1.__proto__.getX === f2.getX);// true
console.log(f1.getX === Fn.prototype.getX); // true

在IE瀏覽器中,原型模式也是同樣的原理,但是IE瀏覽器怕你通過__proto__把公有的修改,禁止我們修改__proto__

批量設定共有屬性

  1. 起別名
function Fn(){
    this.x = 100;
}
var pro = Fn.prototype;
pro.getX = function(){}
pro.getY = function(){}
  1. 重構原型物件的方式
function Fn(){
    this.x = 100;
}
Fn.prototype = {
    getX: function(){},
    getY: function(){}
}
var f = new Fn;
console.log(f.constructor)' // Object

只有瀏覽器天生給Fn.prototype開闢的堆記憶體裡面才有constructor,而我們自己開闢的這個堆記憶體沒有這個屬性,
這樣constructor指向就不再是Fn而是Object

為了和原來的保持一致,我們需要手動的增加constructor的指向

Fn.prototype = {
   constructor: Fn
}

會把之前已經存在於原型上的屬性和方法給替換掉, 用這種方式修改內建類的話, 瀏覽器會給遮蔽掉

Array.prototype = {
    constructor: Array,
    unique: function(){}
}
console.dir(Array.prototype);

但是可以使用prototype屬性,一個個修改內建的方法,如果方法名和原來內建的重複了, 會把內建的修改掉, 在內建類的原型上增加方法, 名命都需要加特殊的字首

Array.prototype.sort = function(){
    console.log("lemon");
}
var ary = [1, 2, 2, 1, 2, 3, 4];
ary.sort();
console.log(ary);

常用的六種繼承模式

for in 循壞在遍歷的時候, 可以把自己私有的和在它所屬原型上擴充套件的屬性和方法都遍歷到.

Object.prototype.aaa = function(){};
var obj = {name: 'lemon', age: 22};
for(let key in obj){
    console.log(key)
}

可以使用以下方法,進行判斷處理

for(let key in obj){
    if(obj.propertyIsEnumerable(key)){
        console.log(key);
    }
}
for(let key in obj){
    if(obj.hasOwnProperty(key)){
        console.log(key);
    }
}

Object.create(proObj)

建立一個新的物件, 但是要把proObj作為這個物件的原型, 在IE6-IE8不相容(ECMAScript5)

var obj = {
    getX: function(){}
}
var obj2 = Object.create(obj)
console.dir(obj2)
obj.getY = function(){}
console.dir(obj2)

自己實現一個create

var obj = {
    getX: function(){}
}
function object(o){
    function Fn(){}
    Fn.prototype = o;
    return new Fn();
}
var newObj = object(obj);

原型鏈繼承

原型繼承是JS中最常用的一種繼承方式, 子類B想要繼承父類A中的所有的屬性和方法(私有+公有), 只需要讓B.prototype = new A;
特點: 它是把父類中的私有+公有的都繼承到了子類原型上(共有的)
核心: 原型繼承並不是把父類中的屬性和方法克隆一份一模一樣的給B,而是讓B和A之間增加了原型鏈的連線, 以後B的例項n想要A中的getX方法,需要一級一級的向上查詢來使用

function A(){
    this.x = 100;
}
A.prototype.getX = function(){
    console.log(this.x)
}
function B(){
    this.y = 200;
}
B.prototype = new A

缺點: 不安全, 可以通過子類或子類的例項,更改父類原型鏈上的屬性和方法, 對A的例項和子類造成影響

var b = new B
var a = new A
b.__proto__.__proto__.getX = 3000;
console.log(a.getX)
B.prototype.__proto__.getX = 2000;
console.log(a.getX)

call繼承

借用建構函式, 偽造物件繼承和經典繼承
call:呼叫一個物件的一個方法,用另一個物件替換當前物件。例如:B.call(A, args1,args2);即A物件呼叫B物件的方法。

function A(){
    this.x = 100;
}
A.prototype.getX = function(){
    console.log(this.x);
}
function B(){
    A.call(this)
}
var b = new B;
console.log(b.x)

缺點:
父類在原型鏈中定義的函式不能被子類訪問,也就是說所有的函式都必須寫在建構函式內部,無法複用

冒充物件

把父類私有和公有的屬性和方法,克隆一份一模一樣的給子類私有的

function A(){
    this.x = 100;
}
A.prototype.getX = function(){
    console.log(this.x);
}
function B(){
    var temp = new A;
    for(var key in temp){
        this[key] = temp[key];
    }
}
var b = new B;
console.log(b.x)

混合模式繼承

子私有 = 父私有, 子公有 = 父私有 + 父公有

function A(){
    this.x = 100;
}
A.prototype.getX = function(){
    console.log(this.x);
}
function B(){
    A.call(this)
}
B.prototype = new A;
B.prototype.constructor = B;
var b = new B;
console.log(b.x)

寄生組合式繼承

子私有 = 父私有, 子公有 = 父公有

function A(){
    this.x = 100;
}
A.prototype.getX = function(){
    console.log(this.x);
}
function B(){
    A.call(this)
}
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
var b = new B;
console.log(b.x)

中間類繼承法,不相容

function avgFn(){
    arguments.__proto__ = Array.prototype;
    arguments.sort(function (a, b) {
        return a - b;
    })
    arguments.pop()
    arguments.shift()
    return eval(arguments.join("+")) / arguments.length;
}
avgFn(10, 20 ,30 ,10 ,30, 40, 40); // 26