目錄:
1、原型&原型鏈注意
2、js中繼承的六種方式
3、new關鍵字
4、instance of
5、Object.create
6、複製實現繼承:淺拷貝、深拷貝
7、繼承理解DOM
8、創造對象的四種方式
1、原型&原型鏈注意
提到原型說對象(目錄8),每一個對象都有一個__proto__隱式原型屬性,指向構造改對象的構造函數的原型。
而函數比較特殊,它具備prototype這樣一個指針屬性,指向包含所有實例共享的屬性和方法的對象,稱爲原型對象;原型對象有一個constructor屬性,指向該函數。
因此具備:Object.\_\_proto\_\_ === Function.prototype;Function.\_\_proto\_\_ === Function.prototype。Object是對象的構造函數,對象的構造函數均指向“Function.prototype”,因爲Function本身是一個構造函數,爲不產生混亂,就將兩個聯繫到一起。
幾乎所有 JavaScript 中的對象都是位於原型鏈頂端的 Object 的實例。
function Person(){}
let p1 === new Person()
let obj === {}
p1.__proto__ === Person.prototype
Person.__proto__ === Function.prototype
Person.prototype
Person.prototype.__proto__ === Object.prototype
Person.prototype.constructor === Person
Function.__proto__ === Function.prototype
Function.prototype
Function.prototype.__proto__ === Object.prototype
Function.prototype.constructor === Function
obj.__proto__ === Object.prototype
Object.__proto__ === Function.prototype
Object.prototype
Object.prototype.__proto__ === null
Object.prototype.constructor === Object
JavaScript 並沒有其他基於類的語言所定義的“方法”。在 JavaScript 裏,任何函數都可以添加到對象上作爲對象的屬性。函數的繼承與其他的屬性繼承沒有差別,包括上面的“屬性遮蔽”(這種情況相當於其他語言的方法重寫)。
當繼承的函數被調用時,this 指向的是當前繼承的對象,而不是繼承的函數所在的原型對象。
2、js中繼承的六種方式
1、原型鏈繼承
特點:通過原型鏈繼承,引用類型會因爲引用的特點變成公用屬性;且無法在繼承的時候傳遞參數;可以使用instanceof、isPropertyOf檢測出來
function Company(){
this.address = '深圳總部';
this.projects = ['games']
}
Company.prototype.showAddress = function(){
console.log("address爲" + this.address);
}
function SonCompany(address){
this.address = address;
}
SonCompany.prototype = new Company();
// 將SonCompany.prototype.__proto__由指向Object.prototype轉爲指向Company.prototype,繼而SonCompany.prototype.__proto__.__proto__ === Object.prototype
var sonC1 = new SonCompany('廣州分部');
var sonC2 = new SonCompany('上海分部');
sonC1.projects.push('movies');
console.log(sonC2.projects); // ["games", "movies"]
console.log(sonC1);
/*
SonCompany
address: "廣州分部"
__proto__: Company
address: "深圳總部"
projects: (2) ["games", "movies"]
__proto__:
showAddress: ƒ ()
constructor: ƒ Company()
__proto__: Object
而沒有使用prototype繼承的時候,sonC1爲:
SonCompany {address: "廣州分部"}
address: "廣州分部"
__proto__:
constructor: ƒ SonCompany(address)
__proto__: Object
*/
/*
此時一般還是需要將sonCompany的constructor換回來的,即應該在SonCompany.prototype = new Company();之後操作SonCompany.prototype.constructor = SonCompany;
這樣操作之後,sonC1打印出來如下:
SonCompany {address: "廣州分部"}
address: "廣州分部"
__proto__: Company
address: "深圳總部"
constructor: ƒ SonCompany(address)
projects: ["games"]
__proto__:
showAddress: ƒ ()
constructor: ƒ Company()
__proto__: Object
*/
2、構造函數繼承
思想:在子類型構造函數的內部call、apply調用父類構造函數
特點:解決了原型鏈“引用類型私有屬性變公有”、“不可傳遞參數”兩個問題;
但是沒有使用new生成一個父類實例的時候,方法寫在prototype上無法使用,所有方法需要在函數中定義。這樣的話就導致無法複用,每次構建實例的時候都會在每一個實例中保留方法函數,造成內容浪費,無法同步更新。寫在prototype上,就只有一份,更新可以同步更新。
function Company (address) {
this.address = address;
this.projects = ['games'];
this.getAddress = function () {
return this.address;
}
}
Company.prototype.showAddress = function(){ console.log(this.address) }
// 子類
function SonCompany (address) {
// 繼承了Company,同時還傳遞了參數
Company.call(this, address);
// 實例屬性
this.staff = 20;
}
var com1 = new Company('深圳總部');
com1.getAddress(); // 深圳總部
com1.showAddress(); // 深圳總部
var sonC1 = new SonCompany('廣州分部');
sonC1.getAddress(); // 廣州分部
sonC1.projects.push('movies');
var sonC2 = new SonCompany('上海分部');
console.log(sonC2.projects) // ["games"]
sonC2.getAddress(); // 上海分部
sonC2.showAddress(); // Uncaught TypeError: sonC2.showAddress is not a function
/*
sonC1的打印: getAddress成爲sonC1的一個函數
SonCompany
address: "sonC1Address"
staff: 20
getAddress: ƒ ()
projects: (2) ["games", "movies"]
__proto__:
constructor: ƒ SonCompany(address)
__proto__: Object
com1的打印:可以查到showName()
Company
address: "SuperAddress"
getAddress: ƒ ()
projects: ["games"]
__proto__:
showAddress: ƒ ()
constructor: ƒ Company(address)
__proto__: Object
*/
3、組合繼承(1、2組合)
思想:將上述兩種結合,發揮各自優勢;是目前廣泛使用的繼承方式;但是對父類構造函數調用了兩次;
function Company (address, staff) {
this.address = address;
this.staffs = staff;
this.projects = ['games'];
this.getAddress = function () {
return this.address;
}
}
Company.prototype.showAddress = function(){ console.log(this.address) }
// 子類
function SonCompany (address, staff) {
// 繼承了Company,同時還傳遞了參數
Company.call(this, address);
// 實例屬性
this.staffs = staff;
}
SonCompany.prototype = new Company();
// 將SonCompany.prototype.__proto__由指向Object.prototype轉爲指向Company.prototype,繼而SonCompany.prototype.__proto__.__proto__ === Object.prototype
SonCompany.prototype.constructor = SonCompany;
// 將構造函數轉回來,因爲上一步操作之後:SonCompany.prototype.constructor === Company;轉之後即可在SonCompany的prototype上正常操作
SonCompany.prototype.getStaff = function(){
return this.staffs
}
var com1 = new Company('深圳總部', 100);
com1.getAddress(); // 深圳總部
com1.showAddress(); // 深圳總部
var sonC1 = new SonCompany('廣州分部', 20);
sonC1.getAddress(); // 廣州分部
sonC1.projects.push('movies'); // ['games', 'movies']
var sonC2 = new SonCompany('上海分部', 40);
console.log(sonC2.projects) // ['games']
sonC2.getAddress(); // 上海分部
sonC2.showAddress(); // 上海分部
sonC2.getStaff(); // 40
/*
SonCompany
address: "廣州分部"
getAddress: ƒ ()
projects: ["games"]
staffs: 20
__proto__: Company
address: undefined
constructor: ƒ SonCompany(address, staff)
getAddress: ƒ ()
getStaff: ƒ ()
projects: ["games"]
staffs: undefined
__proto__:
showAddress: ƒ ()
constructor: ƒ Company(address, staff)
__proto__: Object
*/
4、原型式繼承
思想:不適用嚴格意義上的構造函數,藉助原型基於已有對象創建新對象,同時不必創建自定義類型;
一般是僅僅模擬一個對象的時候使用; es5新增了一個方法Object.create(prototype, descripter)接收兩個參數:
prototype(必選),用作新對象的原型對象
descripter(可選),爲新對象定義額外屬性的對象
在傳入一個參數的情況下,Object.create()與前面寫的object()方法的行爲相同。
使用一個函數對傳入其中的對象執行一次淺複製,複製到的副本還需要進一步改造;引用類型屬性還是會被共享
function objectCopy(o){
function F(){} // 創建一個臨時構造函數
F.prototype = o; // 將傳入的對象作爲構造函數的原型
return new F(); // 返回臨時類型的新實例
}
var company = {
address: "深圳分部",
projects: ['games'],
getAddress: function(){
console.log(this.address);
}
}
var c1 = objectCopy(company);
var c2 = objectCopy(company);
c1.projects.push('movies');
console.log(c2.projects); // ["games", "movies"]
c1.getAddress(); // "深圳分部"
5、寄生式繼承
思想:創建一個僅用於封裝繼承過程的函數,改函數內部以某種形式來做增強對象,最後返回對象。
function objectCopy(o){
function F(){}
F.prototype = o;
return new F();
}
function createAnother (original) {
var clone = objectCopy(original) // 通過調用函數創建一個新對象
clone.sayHi = function () { // 以某種方式來增強這個對象
alert("hi")
}
return clone // 返回這個對象
}
var company = {
address: "深圳分部",
projects: ['games'],
getAddress: function(){
console.log(this.address);
}
}
var c1 = createAnother(company);
var c2 = createAnother(company);
c1.projects.push('movies');
console.log(c2.projects); // ["games", "movies"]
c1.sayHi(); // hi
/*
c1打印
F {sayHi: ƒ}
sayHi: ƒ ()
__proto__:
address: "深圳分部"
getAddress: ƒ ()
projects: (2) ["games", "movies"]
__proto__: Object
*/
6、組合寄生式繼承(3、5)
思想:使用構造函數繼承屬性,原型鏈混成形式繼承方法;只調用一次構造函數
被認爲最理想的繼承方式;
function objectCopy(o){
function F(){}
F.prototype = o;
return new F();
}
function inheritPrototype(s, f){
var prototype = objectCopy(f.prototype); // 創建對象
prototype.constructor = s; // 增強對象
s.prototype = prototype; // 指定對象
}
function Company (address, staff) {
this.address = address;
this.staffs = staff;
this.projects = ['games'];
this.getAddress = function () {
return this.address;
}
}
Company.prototype.showAddress = function(){ console.log(this.address) }
// 子類
function SonCompany (address, staff) {
// 繼承了Company,同時還傳遞了參數
Company.call(this, address);
// 實例屬性
this.staffs = staff;
}
inheritPrototype(SonCompany, Company); // 繼承
SonCompany.prototype.getStaff = function(){
return this.staffs
}
var com1 = new Company('深圳總部', 100);
com1.getAddress();
com1.showAddress();
var sonC1 = new SonCompany('廣州分部', 20);
sonC1.getAddress();
sonC1.projects.push('movies');
var sonC2 = new SonCompany('上海分部', 40);
console.log(sonC2.projects)
sonC2.getAddress();
sonC2.showAddress();
sonC2.getStaff();
/*
sonC1打印如下
SonCompany
address: "廣州分部"
getAddress: ƒ ()
projects: (2) ["games", "movies"]
staffs: 20
__proto__: Company
constructor: ƒ SonCompany(address, staff)
getStaff: ƒ ()
__proto__:
showAddress: ƒ ()
constructor: ƒ Company(address, staff)
__proto__: Object
*/
7、es6的class實現繼承
底層使用的是組合寄生式繼承;寫法簡單易懂,如下:
class Company{
constructor(address){
this.address = address;
this.projects = ['games']
}
getAddress(){
console.log(this.address)
}
}
class SonCompany extends Company{
constructor(address, staffs){
super(address) // 繼承父類實例屬性和prototype上的方法
this.staffs = staffs
}
getStaffs(){
console.log(this.staffs)
}
}
var sonC1 = new SonCompany('廣州分部', 40);
var sonC2 = new SonCompany('上海分部', 20);
sonC1.getAddress(); // 廣州分部
sonC1.getStaffs(); // 40
sonC1.projects.push('movies');
sonC2.getAddress(); // 上海分部
sonC2.getStaffs(); // 20
sonC2.projects; // ['games']
3、new關鍵字
new做了四件事er:
1、創建一個新對象
2、將__proto__屬性指向構造器函數的prototype
3、將this指向新創建對象,使用新創建的對象執行構造函數
4、返回新建對象
function _new(fn){
return function(){
let obj = { // 創建新obj
__proto__: fn.prototype // 原型鏈
}
// 執行構造函數,傳遞參數
fn.call(obj, ...arguments); // apply也是OK的,不過參數問題這樣方便;所以根據這個,fn中的this指的是new的對象
// 返回obj
return obj
}
}
function A(a){
this.a = a
}
var obj = _new(A)('aa'); // {a: 'aa}
函數科裏化: fun裏return一個fun
4、instance of
會根據原型鏈一直找下去
function instance_of(L, R) {//L 表示左表達式,R 表示右表達式
var O = R.prototype;// 取 R 的顯示原型
L = L.__proto__;// 取 L 的隱式原型
while (true) {
if (L === null) //L是Object.prototype
return false;
if (O === L)// 這裏重點:當 O 嚴格等於 L 時,返回 true
return true;
L = L.__proto__;
}
}
5、Object.create
判斷第一個參數是否符合繼承的特點,即是否爲具有__proto__屬性,是否爲對象,函數也是對象;
判斷第二個參數是否符合條件,即不爲undefined;將對象賦值給第一個參數
var _create = function (proto, propertyObject = undefined) {
// 先判斷proto是否符合條件
if (typeof proto !== 'object' && typeof proto !== 'function') {
throw new TypeError('Object prototype may only be an Object: ' + proto);
}
/*
else if (proto === null) {
throw new Error("This browser's implementation of Object.create is a shim and doesn't support 'null' as the first argument.");
}
*/
if (propertyObject === null) {
throw 'TypeError'
} else {
function Fn() { }
Fn.prototype = proto
const obj = new Fn()
if (propertyObject !== undefined) {
Object.defineProperties(obj, propertyObject)
}
if (proto === null) {
// 創建一個沒有原型對象的對象,Object.create(null)
obj.__proto__ = null
}
return obj
}
}
var proto = {name:'allen'};
var p = _create(proto,{age:{value:21}});
p.hasOwnProperty('age');//true
p.hasOwnProperty('name');//false
Object.create(null) vs {}
前者就是一個單純的{},不具備properties的屬性;{} 相當於 Object.create(Object.ptototype),存在n.toString(),且可以用for…in循環
6、複製實現繼承:淺拷貝、深拷貝
7、繼承理解DOM
DOM上是具備原型鏈的;
文檔對象模型 (DOM) 將 web 頁面與到腳本或編程語言連接起來。通常是指 JavaScript,但將 HTML、SVG 或 XML 文檔建模爲對象並不是 JavaScript 語言的一部分。DOM模型用一個邏輯樹來表示一個文檔,樹的每個分支的終點都是一個節點(node),每個節點都包含着對象(objects)。DOM的方法(methods)讓你可以用特定方式操作這個樹,用這些方法你可以改變文檔的結構、樣式或者內容。節點可以關聯上事件處理器,一旦某一事件被觸發了,那些事件處理器就會被執行。
8、創建對象的4種方式
來看看使用不同方法來創建對象:
1.語法結構
var o = {a: 1};
// o ---> Object.prototype ---> null ; o 這個對象繼承了 Object.prototype 上面的所有屬性
var a = [1, 2, 3]
// a ---> Array.prototype ---> Object.prototype ---> null;數組都繼承於 Array.prototype
function f() { return true}
// f ---> Function.prototype ---> Object.prototype ---> null;函數都繼承於 Function.prototype;(Function.prototype 中包含 call, bind等方法)
2.構造器(即普通函數),即使用new操作符
function testF(a){ this.a = a }
testF.prototype = {
funA: function(){
console.log(this.a)
}
}
var t = new testF('aaa');
3.使用Object.create創建對象(ES5方法),新對象的原型就是調用create時傳入的第一個參數
var a = {a: 1};
// a ---> Object.prototype ---> null
var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (繼承而來)
var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null
var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined, 因爲d沒有繼承Object.prototype
4.使用class關鍵字(ES6引入的語法糖,雖然有class,extends的概念,單js還是基於原型)
class A { static geta(){ console.log( 'A' ); } }
class B extends A { static getb(){ console.log( 'B' ); } }
B.geta(); // A
B.getb(); // B
要檢查對象是否具有自己定義的屬性,而不是其原型鏈上的某個屬性,則必須使用所有對象從 Object.prototype 繼承的 hasOwnProperty 方法。(在原型鏈上查找屬性比較耗時,對性能有副作用,這在性能要求苛刻的情況下很重要。另外,試圖訪問不存在的屬性時會遍歷整個原型鏈。)遍歷對象的屬性時,原型鏈上的每個可枚舉屬性都會被枚舉出來。Object.keys()也是跟 hasOwnProperty 一樣的效果;(注意:檢查屬性是否爲 undefined 是不能夠檢查其是否存在的。該屬性可能已存在,但其值恰好被設置成了 undefined。)
原型鏈是可擴展的,但是不建議隨意擴展【原生對象】的原型,這樣會影響到很多繼承原生對象的使用;
擴展原型鏈大概有一下4種:new、Object.create、Object.setPrototypeOf、proto 越靠後越要求版本等,還是建議new