JavaScript 對象繼承 OOP (三)

 

 對象繼承

A 對象經過繼承 B 對象,就能直接擁有 B 對象的全部屬性和方法。這 對於代碼的複用是很是有用的。
JavaScript 語言的繼承不經過 class (es6 中的class 不過是 prototype 的語法糖),而是經過「原型對象」`prototype`實現

#### 傳統原型鏈式繼承

- 過多的繼承屬性
- 好比一個函數用不到某個原型方法或屬性,那麼方法或屬性就過剩了
function Grand(){};
Grand.prototype.name="grand";

let grand = new Grand();

Father.prototype=grand;
function Father(){}
let father = new Father();

Son.prototype=father;
function Son(){}
let son = new Son();

 

#### 借用構造函數 使用call/appply

- 不是真正繼承,由於不能調用原型上的方法,並且每調用一次都會調用屢次函數,實際上步驟沒有變少
- 工業級推薦使用
- 缺點沒法添加私有原型
function Father() { }

function Son() {
Father.call(this); // 調用父類構造函數
}

Son.prototype.print = function() {
Father.prototype.print.call(this);//只使用單個方法
}
// 子類繼承父類的原型
Son.prototype = Object.create(Father.prototype);
Son.prototype.constructor = Son;

 

#### 聖盃模式

>隱式附加的東西就私有化,能夠公共定義的東西提取出來公有化
let inherit =(function(){
let Interim =function Interim() {};
return function (Target,Origin){//繼承源
Interim.prototype =Object.create(Origin);
Target.prototype = Interim.prototype;
 
//如今 能夠 制定本身的私有屬性,可是 constuctor 不是 原函數所一手動賦值回來,若是想要知道函數真正繼承那個原型須要保存它
Target.prototype.constuctor = Target;
Target.prototype.yliluokka =Origin;
}
}())

 

#### 多重繼承

JavaScript 不提供多重繼承功能,即不容許一個對象同時繼承多個對象。可是,能夠經過`Object.assign`,實現這個功能。這種模式稱之爲 Mixin (混入)
function Fn1(){ }
function Fn2(){ }
function Son(){
F1.call(this);
F2.call(this);
}
//繼承F1
Son.prototype =Object.create(Fn1.prototype);
//繼承F2
Object.assign(Son.prototype,Fn2.prototype);
Son.prototype.constructor =Son;
let a =new Son();

 

### call,apply and bind difference

-  均可改變函數內部this的指向(即函數執行時所在的做用域),而後在所指定的做用域中,調用該函數。
 
 

#### call and apply

function test() {}
//test() == test.call()


let obj ={};
Object.prototype.toString.call(obj) //"[object Object]"
//由於call 和 apply 會將函數中的this指向第一個參數
//至關於 obj.toString()
`call and apply` 兩者區別在於傳參:
- call 第二個參數開始單個單個參數傳
- apply 第二個參數爲數組或類數組
//返回數組中最大的數
let a = [1, 2, 4, 1, 15];
Math.max.apply(null, a) // 15

//將數組的空元素變爲undefined
Array.apply(null [1,,3,,4)//[1,undefined,3,undefined,4];

 

空元素與undefined的差異
- forEach方法會跳過空元素,可是不會跳過undefined。所以,遍歷內部元素的時候,會獲得不一樣的結果。
 
轉換相似數組的對象
let obj={0: 1, length: 2}
Array.protetype.slice.apply(obj);//[1,undefined]
被處理的對象必須有length屬性,以及相對應的數字鍵。

參數爲空、null和undefined,則默認傳入全局對象。

 #### bind

bind方法用於將函數體內的this綁定到某個對象,而後返回一個新函數。
let counter = {
count: 0,
inc: function () {
this.count++;
}
};


let func = counter.inc.bind(counter);
func();
counter.count // 1


let add = function (x, y) {
return x * this.m + y * this.n;
}


let obj = {
m: 2,
n: 2
};


let newAdd = add.bind(obj, 5); //將x 綁定爲 5
newAdd(5) // 20
newAdd(1,5)//12

 第一個參數是null或undefined,等於將this綁定到全局對象html

#### bind方法使用注意點

- bind方法每運行一次,就返回一個新函數 須要一個變量接收
- 結合回調函數使用
let counter = {
    count: 0,
    inc: function () {
    'use strict';
    this.count++;
    }
};

function callIt(callback) {
    callback();
}

callIt(counter.inc.bind(counter));
counter.count // 1

 

- 結合call方法使用
[1, 2, 3].slice(0, 1) // [1]
// 等同於
Array.prototype.slice.call([1, 2, 3], 0, 1) // [1]

//將Array.prototype.slice變成Function.prototype.call方法所在的對象
//調用時就變成了Array.prototype.slice.call。

let slice = Function.prototype.call.bind(Array.prototype.slice);
Function.prototype.slice.call([1, 2, 3], 0, 1) // [1]
//slice([1, 2, 3], 0, 1)

let push = Function.prototype.call.bind(Array.prototype.push);
let pop = Function.prototype.call.bind(Array.prototype.pop);

let a = [1 ,2 ,3];
push(a, 4)
a // [1, 2, 3, 4]

pop(a)
a // [1, 2, 3]

 

- 將Function.prototype.bind方法變成Function.prototype.call的方法,就意味着bind的調用形式也能夠被改寫
function f() {
    console.log(this.v);
}

let o = { v: 123 };
let bind = Function.prototype.call.bind(Function.prototype.bind);
bind(f, o)() // 123

 

### `Object 系統默認方法`

- `getPrototypeOf` 獲取對象原型,只有一個參數
function Foo (){}
let obj = new Foo ();
Object.getPrototypeOf(obj) // Foo.prototype


//空對象原型
Object.getPrototypeOf({}) // Object.prototype
// Object.prototype 原型
Object.getPrototypeOf(Object.prototype) //null
// Foo
Object.getPrototypeOf(Foo) // Function.prototype

 

- `setPrototypeOf` 設置對象原型
有兩個參數:
1. 現有對象
2. 繼承的原型對象
let now = {};
let pro = {name:"Owen"};


Object.setPrototypeOf(now,pro);
now.name //"Owen"

 

- `Object.create()`
> 生成實例對象的經常使用方法 參數必須爲對象 或 null
- 參數爲 `null` 會生成一個不會繼承任何屬性和方法的對象
let obj = Object.create(null);
obj.toString()// Error

//會繼承第二個參數的屬性和方法
let obj = Object.create({}, {
p1: {
    value: 123,
    enumerable: true,
    configurable: true,
    writable: true,
},
p2: {
    value: 'Owen',
    enumerable: true,
    configurable: true,
    writable: true,
}
});

// 等同於
let obj = Object.create({});
obj.p1 = 123;
obj.p2 = 'Owen';


//生成的對象會繼承它的原型對象的構造函數。
function Foo() {}
let f = new Foo();
let b = Object.create(f);

b.constructor === Foo // true
b instanceof Foo // true

 

- `object.isPrototypeOf`
> 判斷對象是否再參數對象的原型鏈上
function F(){}
let f = new F()
F.prototype.isPrototypeOf(f) //true

 

- 獲取原型的三種方法
1. `obj.__proto__`
2. `obj.constructor.prototype`
3. `Object.getPrototypeOf(obj)`
 
- 前兩種不可靠,都個一手動修改, 並且 `__proto__` 只有瀏覽器才須要部署

 

- `getOwnPropertyNames` 和 `keys`
> 以數組形式返回參數對象全部屬性名(不包含繼承屬性)
//無論可不可遍歷都會返回出來
Object.getOwnPropertyNames(Date);//["length", "name", "prototype", "now", "parse", "UTC"]
//返回可遍歷屬性
Object.keys(Date)// []

 

 

- `hasOwnProperty`
> 判斷參數是不是自身的屬性,惟一一個不會遍歷原型鏈的方法
Array.hasOwnProperty('length')//true

 

### 拷貝對象

拷貝對象須要確保兩件事情:
- 與原對象具備一樣的原型。
- 與原對象具備一樣的實例屬性。
function copyOwn (target,origin){
Object.getOwnPropertyNames(origin).forEach((key)=>{
let desc =Object.getOwnPropertyDescriptor(origin,key);
Object.defineProperty(target,origin,desc);
})
return target
}

function copy(origin){
let clone = Object.create (Object.getPrototypeOf(origin));
copyOwn(clone,origin)
return clone
}


//es8
const copyTwo = origin =>Object.create( Object.getPropertyOf(origin),Object.getOwnPropertyDescriptor(origin) );

 oop(一)es6

相關文章
相關標籤/搜索