珠峰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