js繼承是用來干啥的:
首先說js沒有真正的跟其他面向?qū)ο蟮恼Z言一樣概念的繼承,js里邊所說的繼承是指模擬繼承。
具體js繼承是干啥呢,剛開始做前端的時候我是用來面試的(最早寫些小效果的時候基本用不到,為啥要看呢,因為面試官很愛問這個問題啊),所以就看看大概的,面試時候能說個大概,在這個問題上算是面試黨了。后來跟著慢慢的實際上雖然概念不是很明確也用到一些。
真正是用來干啥的呢,主要是用來復(fù)用我們之前寫過的代碼。比如寫過一個功能,一個對象,或者用別人寫的東西,我們要自己加點兒什么,總不能改人家的東西吧,直接繼承過來用一下就好了,這才是繼承的真正用途。
js繼承怎么實現(xiàn):
先不上代碼,先說說想法。其實繼承呢就是想辦法把其他對象(js里邊一切皆對象哈)的屬性或者方法搞到我們自己的對象上,讓我們自己的這個對象可以用。這也就達到復(fù)用的目的了。
目的搞明白了,下面就是實現(xiàn)手段了。
根據(jù)js的特性,實現(xiàn)無非就是以下幾種方法的其中一種或者組合使用。
1、構(gòu)造函數(shù),js好像沒有嚴格的構(gòu)造函數(shù)的定義,但是可以用new來創(chuàng)建新的對象。構(gòu)造函數(shù)據(jù)說也是嚴格的面向?qū)ο蟮恼Z言實現(xiàn)繼承的方法,那么js當然可以模擬一下了,所以學(xué)過oop語言的人們會最先想到這個。
2、利用函數(shù)原型,利用原型鏈把兩個對象鏈接起來,因為js原型鏈是比較獨特所以想到這個也是很容易的。
原型也分幾種,就是把什么作為繼承對象的原型,被繼承對象的原型或者被繼承對象的實例,或者直接被繼承者。這幾種作為繼承對象的原型得到的繼承效果是不一樣的。
3、復(fù)制屬性和方法,把被繼承對象的屬性或者方法統(tǒng)統(tǒng)復(fù)制克隆過來變成我們自己對象的屬性和方法就好了啊,我們就可以無比順暢的用了。當然這個還分淺復(fù)制和深度復(fù)制兩種情況。
4、利用call和apply這兩個方法,這兩個方法比較神奇,可以改變函數(shù)執(zhí)行的上下文(this),所以利用這兩個方法也可以實現(xiàn)對被繼承對象的方法的繼承復(fù)用。
總的來js實現(xiàn)繼承的途徑大概就是這些,千變?nèi)f化的實現(xiàn)方法都是從這幾種方法的基礎(chǔ)上組合升級完善出來的,為毛大多數(shù)要組合使用呢,當然是因為使用單個方法實現(xiàn)的效果不太理想啊。當然可以根據(jù)自己項目中實際的需求選擇使用哪種方式,只要滿足自己的需求并沒有說必須使用哪種方法去實現(xiàn)。就像說從北京去石家莊,最快當然是飛機啦。但是如果離機場遠,算上到機場,從機場去市里,整體算下來還不如高鐵快,那就可以做高鐵。又比如自己有車可以開車,想要挑戰(zhàn)一下還可以騎自行車,這個根據(jù)自己的實際情況來選就可以。
代碼實現(xiàn),下面結(jié)合代碼說說上面的幾種實現(xiàn)方法,有些是從其他地方摘來的,加點兒注釋啥的。
看了不少js繼承的東西也該總結(jié)總結(jié)了。
先說一下大概的理解,有不對的還望指正,也好更正一下三觀。另外說明下,下面的例子并非原創(chuàng)基本就是改了個變量名啥的,有的甚至直接拿過來用的。
js繼承是用來干啥的:
首先說js沒有真正的跟其他面向?qū)ο蟮恼Z言一樣概念的繼承,js里邊所說的繼承是指模擬繼承。
具體js繼承是干啥呢,剛開始做前端的時候我是用來面試的(最早寫些小效果的時候基本用不到,為啥要看呢,因為面試官很愛問這個問題?。?,所以就看看大概的,面試時候能說個大概,在這個問題上算是面試黨了。后來跟著慢慢的實際上雖然概念不是很明確也用到一些。
真正是用來干啥的呢,主要是用來復(fù)用我們之前寫過的代碼。比如寫過一個功能,一個對象,或者用別人寫的東西,我們要自己加點兒什么,總不能改人家的東西吧,直接繼承過來用一下就好了,這才是繼承的真正用途。
js繼承怎么實現(xiàn):
先不上代碼,先說說想法。其實繼承呢就是想辦法把其他對象(js里邊一切皆對象哈)的屬性或者方法搞到我們自己的對象上,讓我們自己的這個對象可以用。這也就達到復(fù)用的目的了。
目的搞明白了,下面就是實現(xiàn)手段了。
根據(jù)js的特性,實現(xiàn)無非就是以下幾種方法的其中一種或者組合使用。
1、構(gòu)造函數(shù),js好像沒有嚴格的構(gòu)造函數(shù)的定義,但是可以用new來創(chuàng)建新的對象。構(gòu)造函數(shù)據(jù)說也是嚴格的面向?qū)ο蟮恼Z言實現(xiàn)繼承的方法,那么js當然可以模擬一下了,所以學(xué)過oop語言的人們會最先想到這個。
2、利用函數(shù)原型,利用原型鏈把兩個對象鏈接起來,因為js原型鏈是比較獨特所以想到這個也是很容易的。
原型也分幾種,就是把什么作為繼承對象的原型,被繼承對象的原型或者被繼承對象的實例,或者直接被繼承者。這幾種作為繼承對象的原型得到的繼承效果是不一樣的。
3、復(fù)制屬性和方法,把被繼承對象的屬性或者方法統(tǒng)統(tǒng)復(fù)制克隆過來變成我們自己對象的屬性和方法就好了啊,我們就可以無比順暢的用了。當然這個還分淺復(fù)制和深度復(fù)制兩種情況。
4、利用call和apply這兩個方法,這兩個方法比較神奇,可以改變函數(shù)執(zhí)行的上下文(this),所以利用這兩個方法也可以實現(xiàn)對被繼承對象的方法的繼承復(fù)用。
總的來js實現(xiàn)繼承的途徑大概就是這些,千變?nèi)f化的實現(xiàn)方法都是從這幾種方法的基礎(chǔ)上組合升級完善出來的,為毛大多數(shù)要組合使用呢,當然是因為使用單個方法實現(xiàn)的效果不太理想啊。當然可以根據(jù)自己項目中實際的需求選擇使用哪種方式,只要滿足自己的需求并沒有說必須使用哪種方法去實現(xiàn)。就像說從北京去石家莊,最快當然是飛機啦。但是如果離機場遠,算上到機場,從機場去市里,整體算下來還不如高鐵快,那就可以做高鐵。又比如自己有車可以開車,想要挑戰(zhàn)一下還可以騎自行車,這個根據(jù)自己的實際情況來選就可以。
代碼實現(xiàn),下面結(jié)合代碼說說上面的幾種實現(xiàn)方法,有些是從其他地方摘來的,加點兒注釋啥的。
一、構(gòu)造函數(shù)實現(xiàn)(借用構(gòu)造函數(shù)):
function Super(arg){
this.arr1 = "I'm super "+arg;
this.show = function(){
alert(this.arr1);
}
}
Super.prototype.say = function(){
alert(this.arr1);
}
function suber(arg){
Super.apply(this, arguments); //在suber的上下文中運行super
}
var sub =new suber("suber");
var sub2 = new suber("suber1");
console.log(sub.arr1); //I'm super suber
console.log(sub.show); //function (){ alert(this.arr1);}
console.log(sub.say); //undefined
console.log(sub.show === sub2.show); //false
發(fā)現(xiàn)sub.say是undefined,這說明它沒有被繼承過來啊,下邊兩個對象sub,sub2的show不相等,說明兩個函數(shù)指向了兩個不同的對象,也就是說被復(fù)制了兩份出來。
所以這個方法實現(xiàn)繼承的話原型對象上的屬性和方法沒有被繼承過來,Super上的屬性和方法為每個new出來的對象分別復(fù)制一份。
所以單單使用這個方法來實現(xiàn)繼承還是不妥啊,因為原型上的方法都沒有被繼承過來啊。于是大神們就想到了原型繼承
二、原型繼承:
function Super(arg){
this.arr1 = "I'm super "+arg;
this.show = function(){
alert(this.arr1);
}
}
Super.prototype.say = function(){
alert(this.arr1);
}
function suber(arg){}
suber.prototype = new Super();
var sub = new suber("suber1");
var sub2 = new suber("suber2");
console.log(sub.arr1); //I'm super undefined
console.log(sub.show); //function (){ alert(this.arr1);}
console.log(sub.say); //function (){ alert(this.arr1);}
console.log(sub.show === sub2.show); //true;
console.log(sub.say === sub2.say); //true;
這次是arr1繼承過來了,但是參數(shù)沒有添加進來,是undefined,所以這個方法子類聲明時候這個參數(shù)傳進來付類繼承過來的這個屬性沒法收到。其他的都還算正常。show和say都繼承過來了。但是有一點兒需要注意,say是通過super的原型對象繼承過來的,而show是新建super對象實例時實例的屬性。
那么怎么實現(xiàn)參數(shù)傳輸又能把原型里邊的東東繼承過來呢,當然上邊兩種方法組合一下就好了啊,于是前輩們又發(fā)明了下面這種方法
三、組合繼承(借用構(gòu)造函數(shù)并設(shè)置原型):
function Super(arg){
this.arr1 = "I'm super "+arg;
this.show = function(){
alert(this.arr1);
}
}
Super.prototype.say = function(){
alert(this.arr1);
}
function suber(arg){
Super.apply(this, arguments);
}
suber.prototype = new Super();
var sub = new suber("suber1");
var sub2 = new suber("suber2");
console.log(sub.arr1); //I'm super suber1
console.log(sub.show); //function (){ alert(this.arr1);}
console.log(sub.say); //function (){ alert(this.arr1);}
console.log(sub.show === sub2.show); //false;
console.log(sub.say === sub2.say); //true;
這次幾乎完美了,但是可以發(fā)現(xiàn)sub.show 和sub2.show并不相等啊,這是為啥呢,因為apply這個地方使得show成為了suber的自己的屬性,那么就吧suber原型里的show(Super的當做suber原型對象的實例對象的show)給覆蓋了,所以又變成每次復(fù)制一個,當然這個地方?jīng)]有辦法避免啊。為了不產(chǎn)生這種多余的開消這種可以共用的函數(shù)可以多放到原型對象里邊。
因為suber的構(gòu)造里邊的調(diào)用,和給suber原型對象賦值時候的調(diào)用導(dǎo)致Super被調(diào)用了兩次,那么每次new suber對象時候就調(diào)用了兩次Super,調(diào)用兩次就會產(chǎn)生兩個實例對象,需要消耗多余的資源了。
于是前輩們?yōu)榱私鉀Q這個問題又開了開腦洞,開發(fā)出來下面這種方法。
四、寄生組合繼承:
該方法跟方法三最主要的不同就是把父類原型賦給了子類原型而不是父類示例,看例子
function Super(arg){
this.arr1 = "I'm super "+arg;
}
Super.prototype.show = function(){ //這個方法放到了原型對象上。
alert(this.arr1);
}
Super.prototype.say = function(){
alert(this.arr1);
}
function suber(arg){
Super.apply(this, arguments);
}
/*inherit函數(shù)的作用,使用一個新的空函數(shù),來切斷父類對象的原型對象與子類原型對象的直接聯(lián)系,而是通過這個空構(gòu)造的實例對象實現(xiàn)繼承,這樣可以避免更改子類原型的屬性或者方法而影響了父類原型對象的屬性或者方法。*/
function inherit(obj){
function F(){}
F.prototype = obj;
return new F();
}
suber.prototype = inherit(Super.prototype);
var sub = new suber("suber1");
var sub2 = new suber("suber2");
console.log(sub.arr1); //I'm super suber1
console.log(sub.show); //function (){ alert(this.arr1);}
console.log(sub.say); //function (){ alert(this.arr1);}
console.log(sub.show === sub2.show); //true;
console.log(sub.say === sub2.say); //true;
好了,這樣就把三方法的弊端干掉了,這個可以完美的使用了吧。
五、復(fù)制屬性實現(xiàn)
拷貝我們可以寫一個拷貝函數(shù)來實現(xiàn)。
function extend(Super,suber){
suber = suber || {};
for(var i in Super){
if(Super.hasOwnProperty(i)){
suber[i] = Super[i];
}
}
return suber;
}
var parent = {
name:"dad",
num:[1,2,3],
say:function(){alert("dad");}
}
var child = {
age:5,
sex:"boy"
};
child = extend(parent, child);
//以下測試
console.log(child); /*{
age:5,
sex:"boy",
name:"dad",
num:[1,2,3],
say:function(){alert("dad");}
}*/
console.log(child.say === parent.say); //true
console.log(child.num === parent.num); //true
復(fù)制成功,那么child成功繼承了parent的一些屬性,但是后面兩個測試發(fā)現(xiàn)他們是相等的,就表明了他們在公用同一個數(shù)組,同一個函數(shù),函數(shù)這個可以,但是數(shù)組這個就有問題了,如果一個chiild改變了數(shù)組,幾個被繼承對象的數(shù)組也跟著變了,這就不給力了啊。
為什么會發(fā)生這種情況呢,js里邊對象存儲的是指針,然后這個指針指向這個值,我們在這復(fù)制的實際是指向該對象的指針的值,所以繼承對象和被繼承對象都指向了同一個對象,接下來看看如何使用深度復(fù)制來解決這個問題。
深度復(fù)制對象屬性:
function extenddeep(Super, suber){
var tostr = Object.prototype.toString, astr = "[object Array]";
suber = suber || {};
for(var i in Super){
if(Super.hasOwnProperty(i)){
if(typeof Super[i] === "object"){
suber[i] = (tostr.call(Super[i]) == astr) ? [] : {};
extenddeep(Super[i],suber[i]);
}else {
suber[i] = Super[i];
}
}
}
return suber;
}
var parent = {
name:"papa",
num:[1,2,3],
say:function(){alert("I'm father of my son!");}
}
var child = {
name:"jone",
sex:"boy",
}
var kid = extenddeep(parent, child);
console.log(kid); // {name: "papa"
num: Array[3]
say: ()
sex: "boy"
// }
console.log(kid.say === parent.say); //true
console.log(kid.num === parent.num); //false
console.log(kid.name); //papa
好了,深度復(fù)制完畢,但似有木有發(fā)現(xiàn)問題,name是parent的,也就是說如果繼承對象有和被繼承對象一樣的屬性名的屬性如果不做處理就會被替換掉。那么我們可以做一下處理,先聲明一個屬性,保存parent里的東西,剩下的的當然就是child自己的東西了,最后再把屬性給child對象就可以了。
六、利用call和apply這兩個方法(借用方法)。
這個就是通過call和apply來復(fù)用其他對象的方法,達到復(fù)用的目的。
var one = {
name:"object",
say: function(greet){
return greet + ', ' + this.name;
}
}
var tow = {
name:"two"
}
one.say.call(tow, "hi"); //hi, two
這個就是借用了,好了,下課。
好吧,好吧,其實這里邊還有其他東西要看??梢越栌貌⒉弧皫П怼笨梢噪S便把某個方法賦值給誰然后跟沒發(fā)生什么似的繼續(xù)用。所以我們平時使用借用時要注意一下上下文,下面看下那些容易出錯的地方。
//賦值給一個變量時候上下文會變化
var say = one.say;
console.log(say('hoho')); // "hoho, undefined"
//作為回調(diào)函數(shù)時也會發(fā)生變化
var yetother = {
name:"yetother obj",
method:function(callback){
return callback("Hola");
}
}
console.log(yetother.method(one.say)); //"Hola, "
神馬意思呢,就是this.name是undefined,當one.say賦值給say是,實際上是say存儲了指向函數(shù)對象的指針,say這個變量明顯又是全局變量的一個屬性,那么它運行的時候?qū)嶋H的上下文就變成了windows了,當然這個時候name就變成undefined了?;卣{(diào)這個也是一樣,return 的是函數(shù)運行的結(jié)果。如果我們事先設(shè)置 windows.name="windows" 那么得到的結(jié)果就變成了 "hoho, windows" 和"Hola, windows" 了。
function bind(o, m){
return function(){
return m.apply(o, [].slice.call(arguments));
}
}
var othersay = bind(yetother, one.say);
othersay("Hola"); //"Hola, yetother obj"
通過apply可以改變方法執(zhí)行的上下文,那么我們構(gòu)建一個函數(shù)來實現(xiàn)這樣一個功能,通過使用方法調(diào)用實現(xiàn)上下文的改變,這樣就不會出現(xiàn)上下文不是我們期望的上下文的情況了。
//這段是直接復(fù)制過來的。
// ECMAScript 5給Function.prototype添加了一個bind()方法,以便很容易使用apply()和call()。
if (typeof Function.prototype.bind === 'undefined') {
Function.prototype.bind = function (thisArg) {
var fn = this,
slice = Array.prototype.slice,
args = slice.call(arguments, 1);
return function () {
return fn.apply(thisArg, args.concat(slice.call(arguments)));
};
};
}
var twosay2 = one.say.bind(two);
console.log(twosay2('Bonjour')); // "Bonjour, another object"
var twosay3 = one.say.bind(two, 'Enchanté');
console.log(twosay3()); // "Enchanté, another object"
介紹完了,該說說自己的疑惑了,當復(fù)制屬性方法遇到的被繼承對象里邊存在方法,如何單獨復(fù)制出來呢,現(xiàn)在的是直接共用了,因為畢竟方法一般不會經(jīng)常改動。求解答?
下面是轉(zhuǎn)載過來的jQuery的extend方法,好像也沒有特殊處理函數(shù)這塊,繼承完了兩個函數(shù)也是共用的。
$.extend源碼
jQuery.extend = jQuery.fn.extend = function() {
var options, name, src, copy, copyIsArray, clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false ;
// Handle a deep copy situation
//如果第一個參數(shù)是boolean類型
//修正參數(shù),將第二個參數(shù)作為target
if ( typeof target === "boolean" ) {
deep = target;
// skip the boolean and the target
target = arguments[ i ] || {};
//i++是為了后續(xù) i === length的判斷
i++;
}
// Handle case when target is a string or something (possible in deep copy)
//如果目標既不是對象也不是方法(例如給基本類型擴展屬性方法和屬性不會報錯但是是無用的),修正target為 js對象
if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
target = {};
}
// extend jQuery itself if only one argument is passed
//如果只有一個參數(shù),修正對象為JQuery函數(shù)或JQuery對象
if ( i === length ) {
target = this ;
//修正target所在位置,后面的都是要添加給target的對象
i--;
}
for ( ; i < length; i++ ) {
// Only deal with non-null/undefined values
if ( (options = arguments[ i ]) != null ) {
// Extend the base object
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
// Prevent never-ending loop
//如果target和copy是同一個對象,略過,防止自己的屬性引用了本身對象導(dǎo)致的循環(huán)引用,以致GC無法回收
if ( target === copy ) {
continue ;
}
// Recurse if we're merging plain objects or arrays
//如果是deep為true,并且要添加給target的copy為對象獲數(shù)組
if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
if ( copyIsArray ) {
copyIsArray = false ;
clone = src && jQuery.isArray(src) ? src : [];
} else {
clone = src && jQuery.isPlainObject(src) ? src : {};
}
// Never move original objects, clone them
//很巧妙 ,用一個遞歸,實現(xiàn)引用對象的深克隆,遞歸的返回條件是屬性石基本類型,基本類型都是深克隆
target[ name ] = jQuery.extend( deep, clone, copy );
// Don't bring in undefined values
} else if ( copy !== undefined ) {
//淺克隆
target[ name ] = copy;
}
}
}
}
// Return the modified object
return target;
};
以上這篇javascript 繼承學(xué)習(xí)心得總結(jié)就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考