1. 程式人生 > >珠峰js筆記1.2(原型鏈擴充套件)

珠峰js筆記1.2(原型鏈擴充套件)

{ 原型鏈模式

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

function CreatejsPerson(name,age){
	this.name = name;
	this.age = age;
}
CreatejsPerson.prototype.writeJS = function(){
	console.log('my name is '+ this.name);
}
var p1 =new CreatejsPerson('rose',23);
var p2 =new CreatejsPerson('jack',
22); console.log(p1.writeJS === p2.writeJS ); // true

基於建構函式模式的原型鏈模式解決了: 方法或者屬性公有的問題 -> 把例項之間相同的屬性和方法提取成公有的屬性和方法 -> 將其放在 CreatejsPerson.prototype 上即可

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

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

3 每一個物件資料型別 ( 普通的物件、例項、prototype…) 也天生自帶一個屬性__proto__

, 屬性值是當前例項所屬類的原型(prototype)

function Fn(){
	this.x = 100;
	this.sum = function(){ ... }
}
Fn.prototype.getX = function(){
	console.log(this.x);
}
Fn.prototype.sum = function(){ ... }
var f1 = new Fn;
var f2 = new Fn;
console.log('result:', Fn.prototype.constructor === Fn); // true


1Object 是js中所有物件資料型別的基類


在 Object.prototype 上沒有 proto 這個屬性

f1 instanceof Object -> true 是因為通過 proto 可以向上級查詢,不管有多少級,最後總能找到 Object

2 原型鏈模式
f1.hasOwnProperty('x'); // -> hasOwnProperty是 f1 的一個屬性
但是,我們發現在f1的私有屬性上並沒有這個方法,那是如何處理的呢?
1) 通過 物件名.屬性名的方法獲取屬性值的時候,首先在物件的私有屬性上進行查詢,如果私有中存在這個屬性,則獲取的是私有屬性的值;

2) 如果私有的沒有,則通過 proto 找到所屬類的原型(類的原型上定義的屬性和方法都是當前例項公有的屬性和方法),原型上存在的話,獲取的是公有屬性

3) 如果原型上也沒有,則繼續通過原型上的__proto__繼續向上查詢,一直找到 Object.prototype為止

f1.getX === f2.getX -> true 都是公有的
f1.__proto__.getX === f2.getX -> true 前者是省略查詢私有的
f1.getX === Fn.prototype.getX -> true
f1.sum === f1.__proto__.sum -> false 前者是私有的,後面是公有的
f1.sum === Fn.prototype.sum -> false

注意:
f1.hasOwnProperty -> f1.__proto__.__proto__.hasOwnProperty
在 IE 瀏覽器中,原型鏈模式也是同樣的原理,但是 IE 瀏覽器怕你通過__proto__把公有的修改,禁止使用__proto__

f1.sum = function(){ 修改私有的sum...}
f1.__proto__.sum = function(){ 修改公有的sum...}
Fn.prototype.sum = function(){ 修改公有的sum...}

{ 批量設定公有屬性

1 起別名方式

var pro = Fn.prototype;  // 設定了一個別名 pro, 兩者指向同一地址
pro.getY = function(){...}
pro.getZ = function(){...}

2 重構原型物件方式
自己新開闢一個堆記憶體,儲存公有的屬性和方法,把瀏覽器原來給 Fn.prototype 開闢的替換掉

只有瀏覽器天生給 Fn.prototype 開闢的堆記憶體裡面才有 constructor , 而我們自己開闢的這個堆記憶體沒有這個屬性,所以 f.constructor 指向的就不是 Fn,而是 Object
為了和原來儲存一致,需要手動增加 constructor 指向

Fn.prototype = {
	constructor: Fn,
	getX: function(){...},
	getY: function(){...}
}

{ 給內建 類增加公有的屬性

// 給 內建類 Array 增加陣列去重的方法
Array.prototype.unique = function(){...}
Array.prototype = {
	constructor: Array,
	unique:function(){ }
}		
console.dir(Array.prototype);

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

所以是用第一種方式修改內建類的屬性和方法,一個一個的新增,(在原型上增加方法,如果方法名和原來內建類的重複了,就會把內建類的方法修改掉)

在內建類的原型上增加方法時,命名需要加特殊的字首

{ 原型鏈 this

在原型鏈模式中,this 常用的有兩種情況
1 在類中 this.xxx = xxx; this 指當前類的例項
2 在某一個方法中(公有和私有)的 this , 看執行的時候 . (點)前面是誰 this 就是誰
a) 需要先確定 this 的指向(this 是誰)
b) 把 this 替換成對應的程式碼
c) 按照原型鏈查詢的機制,一步步的查詢

function Fn(){
	this.x = 100;
	this.y = 200;
	this.getY = function(){
		console.log(this.y);
	}
}
Fn.prototype = {
	constructor: Fn,
	y: 300,
	getX: function(){
		console.log(this.x);
	}
	getY: function(){
		console.log(this.y);
	}
}
var f = new Fn;
f.getX();  // 100
f.__proto__.getX(); // undefined

查詢步驟f.getX(); -> console.log(f.x); ->100
f.__proto__.getX(); -> this 是 f.__proto__ -> console.log(f.__proto__.x) -> undefined

陣列去重( this 的使用 )案例:

// 在內建類上擴充套件自己的方法  
Array.prototype.myUnique = function(){
	// this 指向當前類的例項 ary, 這個方法不用傳參
	var temp = [];
	for(var i=0; i<this.length; i++){
		var cur = this[i];
		if(temp[cur] == cur){
			this[i] = this[this.length -1]; //最後一項
			this.length--;
			i--;
			continue;
		}
		temp[cur] = cur; // 以值為下標 
	}
}

鏈式寫法:

ary.sort(function(a,b){
	return a - b;
}).reverse().pop();

sort 是 Array.prototype 上的公有方法,ary 是 Array 類的一個例項,ary 可以使用 sort 方法,其執行完成的返回值是一個排序後的“陣列”,可以繼續執行陣列類的公有方法

要讓自己寫的陣列去重的程式碼也能實現鏈式程式設計,在程式碼最後新增:return this;
思考:slice的實現,引數的大小,個數,負值,鏈式寫法

遍歷
for… in 遍歷 物件,遍歷出私有的和手動新增的公有方法
手動新增: Object.prototype.xxx = function(){…}
原型上方法都是不可列舉的
控制檯檢視:Object.prototype 回車 xxx 方法顏色不一樣
console.log(f.propertyIsEnumerable('age')); // true

考慮: 只遍歷私有的方法和屬性
加判斷:用 propertyIsEnumerable 或 hasOwnProperty

Object.create(proto, [propertiesObject]) ie6-8 不相容
proto 新建立物件的原型物件 ,上述方法返回值是一個新物件,帶著指定的原型物件和屬性

var obj = {
	getX: function() {}
}
function Fn(){ }
var f = new Fn;

預設 f.constructor 為 Fn( ) {…}
新增程式碼 Fn.prototype = obj; 後,f.constructor 為 Object( ){ …}
若要其指向原來的,在 obj 中新增 constructor: Fn
此時 obj 和 Fn.prototype 指向同一地址

要求是能使用 obj中的方法,不改變 obj, 怎麼克隆?
用一個新物件 for…in 遍歷賦值,只要私有的

var obj2 = {};
for(var key in obj){
	if(obj.hasOwnProperty(key)){
		obj2[key] = obj[key];
	}
}

另一種實現: var obj3 = Object.create(obj);
控制檯對比: dir obj2 vs dir obj3
模擬 Object.create()

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

{ 常用的六種繼承方式

1 原型繼承
以 按鈕標籤 button為例, 一級一級往上找
btn.__proto__ ->HTMLButtonElement -> HTMLElement -> Element -> Node -> EventTarget ->Object

模擬實現:
EventTarget.prototype = new MyObject();
EventTarget.prototype.addEventListener = function(){ }

原型繼承是js中 最常用的一種繼承方式
子類B想要繼承父類A中所有的屬性和方法(公有+私有),只要 B.prototype = new A; 即可,記得加上 B.prototype.constructor = B;

原型繼承的特點:它是把父類中私有的 + 公有的都繼承到子類原型上(子類公有)
核心:原型繼承並不是把父類中的屬性和方法克隆一份,而是讓B和A之間增加了原型鏈的連線,以後 B的例項 想要 A 中的方法,需要一級一級向上查詢來使用

2 call 繼承
把父類私有的屬性和方法克隆一份,作為子類私有的屬性

function A(){
	this.x = 100;
	this.showY = function(){
		return this.y;
	}
}
A.prototype.getX = function(){
	console.log('a:',this.x);
}
function B(){
	// this  -> n 
	A.call(this); // A.call(n)-> 讓 A 執行,把 A 中的this 指向 n 
	this.y = 23;
}
var n = new B;
console.log('n.x',n.x); // n.x 100
console.log('n.showY',n.showY());  // n.showX 23

3 冒充物件繼承
把父類私有的 +公有的克隆一份 給子類私有
在 B 中:

function B(){
	// this -> n
	var temp = new A;
	for(var key in A ){
		this[key] = temp[key];
	}
	temp = null;
}

4 混合模式繼承
原型繼承 + call 繼承

function B(){
	A.call(this);  // A 中私有的在B中有兩份
}
B.prototype = new A;
B.prototype.constructor = B;

5 寄生組合式繼承
用 Object.create ,有相容問題,自己模擬

function B(){
	A.call(B);  // 私有
}
B.prototype = objectCreate(A.prototype); // 公有
B.prototype.constructor = B;

function  objectCreate(o){  // 模擬
	function fn(){}
	fn.prototype = o;
	return new fn;
}

6

function avgFn(){       //求平均,先排序,去最低最高,取平均
	Array.prototype.sort.call(arguments,function(a,b){
		return a - b;
	});
	Array.prototype.pop.call(arguments);
	Array.prototype.shift.call(arguments);
	return (eval(Array.prototype.join.call(arguments,'+'))/arguments.length).toFixed(2);
}
console.log(avgFn(10,2,53,23));  // 保留了小數點2位

這種繼承不相容 (ie )

function avgFn(){
	console.log( typeof arguments); // object
	arguments.__proto__ = Array.prototype;  // 只公有
	arguments.sort(function(a,b){ return a-b; });
	arguments.pop();
	arguments.shift();
	return (eval(arguments.join('+')) / arguments.length).toFixed(2);
}

{ 原型鏈綜合練習

程式碼中有一個 id 為 ‘box’ 的 div (a , document, window)
控制檯: dir( box ) 檢視其所有私有屬性,一級一級往上找方法和屬性
ps: __proto__ 指向 類 Fn 的原型 不是 new 出來的, 又所有物件都是 object 的一個例項, 所以其指向 Object.prototype

增加一個名,在控制檯檢視的時候和其它原有的保持一致:


結果: true 20 NaN