日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

js原型继承

發(fā)布時間:2024/3/26 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 js原型继承 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

這里寫自定義目錄標題

  • 前言
  • 一、原型的特性
  • 二、原型繼承到底有幾種
    • 1、原型鏈繼承
    • 2、構造函數(shù)繼承
    • 3、組合式繼承
    • 4、原型式繼承
    • 5、寄生式繼承
    • 6、寄生組合繼承
    • 7、對象冒充
    • 8、class繼承
  • 三、引出的幾道經(jīng)典題目
    • 1、new的時候做了什么
    • 2、es6箭頭函數(shù)的特性
    • 4、var、let、const的區(qū)別
    • 5、call、apply、bind的區(qū)別
    • 6、編程的幾種范式
  • 使用原型封裝一個插件

前言

這是一道非常經(jīng)典的前端面試題,尤其是對于初中級前端來說,基本上是必考題。當我還是一個前端萌新的時候,也被問到過很多次。當時我對原型、原型鏈、繼承的理解,也就是面試題看懂了,面試的時候能忽悠幾句的程度,面試官也不一定真的搞懂了,所以也總能蒙混過關。
但是隨著新一代前端的成長,大家用的都是class,相信這道題很快很成為歷史,到時候只有類,沒有原型這個詞。
為什么原型這么難懂?我覺得有下面三個主要原因:
1、它確實比較難,你找一個寫java的人來解釋js的原型繼承他也不一定能講清楚
2、你可能干了好幾年都不一定會深入使用它,即使react玩家天天在寫,但是應該有很多人只是在套模板,更何況vue那種更舒服的模板寫法(vue最終也是原型繼承,比如一開始的 new vue())。
3、網(wǎng)上的博客五花八門,有說繼承有五種,有說有六種,有的說有八種,而且名字還可能不一樣
本文試圖通過一個實際應用的例子來幫助初學者理解原型繼承 (但是沒成功,有興趣看最后一節(jié))
本文對原型繼承的幾種方式做了整理,算是一篇筆記

一、原型的特性

雖然很多教程跟書本上都有,這里還是簡單介紹一下,避免有些同學忘記
原型有三大特性:封裝 繼承 多態(tài)
封裝:指的是把很多屬性、方法全都集中到原型里,要訪問的時候都是統(tǒng)一入口,不會像過程式編程(后面有介紹)一樣到處都是作用域比較廣的變量。
繼承:指的是兩個原型之間,子類可以繼承父類的屬性和方法。
多態(tài):父類有一個屬性叫做“name”,子類也可以有一個叫“name”的屬性,子類只會生效自己的“name”。

這里是按我自己的理解簡單介紹的,書面化的介紹請看《javascript權威指南》,畢竟作者也不一定是對的,不管是博客還是視頻,都不是權威的,都可能會講錯,對于自己不理解的東西,一定要從源頭找答案對照。

二、原型繼承到底有幾種

講了這么多廢話,先上個總結吧,畢竟刷到這篇博客的人基本上都是為了面試:
分細一點應該有八種,看了好多其它的博客,最多的也是介紹到八種。
1、原型鏈繼承(Child.prototype=new Parent())
2、構造函數(shù)繼承(在構造函數(shù)里面使用call)
3、組合繼承(原型鏈繼承+構造繼承)
4、原型式繼承(用了object.create,也有把他歸類到寄生式)
5、寄生式繼承(原型式繼承的進階版)(也有叫拷貝繼承)
6、寄生組合式繼承(寄生式+組合式,是class出現(xiàn)前的終極繼承方案)
7、對象冒充(不知道誰先想出來的怪招)
8、class繼承(大家都嫌寄生組合太麻煩了,所以出現(xiàn)了它,屠龍術)
因果關系記憶法:
因為原型鏈繼承不能傳參,所以有了構造繼承,但是構造繼承不能繼承父級的原型,所以出現(xiàn)了結合兩種方式的組合繼承。
因為組合繼承實例化了兩次父類,性能有缺陷,強迫癥的前端們?nèi)滩涣?#xff0c;所以想出了原型式繼承,再增強成寄生繼承,把寄生繼承跟組合繼承一結合,變成了寄生組合繼承,用來解決組合繼承的小缺陷
有一位腦洞清奇的人才,發(fā)現(xiàn)了對象冒充,讓大家的面試題又多了一個答案。
最后大家都覺得寫寄生組合繼承太費勁了,所以出現(xiàn)了class

1、原型鏈繼承

優(yōu)點:繼承了父類的所有,包括原型
缺點:
1、不能給父類傳參
2、引用類型的屬性會被子類修改(比如一個子類改了父類的Array類型的屬性,另一個子類的原型上Array也會變)

var Parent = function(type){this.parentType = type; } var Child = function(type){this.childType = type } // 直接實例化之后賦值到Child.prototype Child.prototype = new Parent();

2、構造函數(shù)繼承

優(yōu)點:
1、可以傳參數(shù)
2、引用類型的屬性不會被子類修改
缺點:
1、不能繼承原型,因為只是把屬性用call綁定到了this上
2、每次構造函數(shù)都要多走一個函數(shù)(call)

var Child = function(childType, parentType){// call、apply都行,入?yún)⒉煌?#xff0c;后面有簡單介紹,通常面試也會順帶問問他們Parent.call(this, parentType)this.childType = childType }

3、組合式繼承

優(yōu)點:
1、可以傳參又繼承了原型
2、引用類型的屬性不會被子類修改
缺點:
1、實例和原型上存在兩份相同的屬性,一份在this,一份在prototype(this不等于prototype,雖然簡單用起來差不多),也就是說實例化了兩次Parent,性能上欠佳,所以才有了下面的原型式到寄生組合的進化
2、每次構造函數(shù)都要多走一個函數(shù)(call)

var Child = function(childType, parentType){// 說了用apply也行,只不過入?yún)⑹莻€數(shù)組Parent.apply(this, [parentType])this.childType = childType } Child.prototype = new Parent(); Child.prototype.constructor = Child;

4、原型式繼承

優(yōu)點:不用實例化父類,只是用Object.create創(chuàng)建了一個副本
缺點:不能改動自己的原型(因為返回new已經(jīng)實例化了),所以也不能復用

// 自己簡單點寫是這樣,作用約等于Object.create() function create(options){var Temp = function(){};Temp.prototype = options; return new Temp(); } // 這里命名為parentPrototype跟寄生組合有關,往下看寄生組合 var parentPrototype = {name:'me' } var ChildPrototype = create(parentPrototype); // 下面寫的也是原型式 var ChildPrototype = Object.create(parentPrototype);

5、寄生式繼承

也有把原型式歸到寄生式里面的,因為就包了個殼
優(yōu)點:在原型式繼承的基礎上,增強了對象
缺點:
1、多包了一層,當然是多走了一次函數(shù)啦
2、也是不能復用,跟原型式一樣

function clone(options){ let clone = Object.create(options);clone.personName = "you";return clone } var parentPrototype = { name:"me" } var ChildPrototype = clone(parentPrototype);

6、寄生組合繼承

優(yōu)點:
以上所有的優(yōu)點
缺點:
1、寫起來復雜
2、多執(zhí)行了call、create

function Parent(name) {this.name = name; }// 定義原型上的一個方法 Parent.prototype.method = function () {console.log(this.name); }; // 函數(shù)式 function Child(name, age) {Person.call(this, name); } // 如果沒有Object.create方法,就自己簡單寫一下 if (!Object.create) {Object.create = function (proto) {function Temp() {}Temp.prototype = proto;return new Temp();}; } // 寄生 function clone(options){ let clone = Object.create(options);clone.personName = "you";return clone } // 原型鏈上寄生 Child.prototype = clone(Parent.prototype); // 修改constructor指向 Child.prototype.constructor = Child;

7、對象冒充

優(yōu)點: 讓你的面試答案多了一個
缺點:
1、當父類的屬性相同時,后面定義的會覆蓋前面定義的屬性(看實現(xiàn)就知道為什么)
2、每次構造函數(shù)都要多走一個函數(shù)

function Parent1(name) { this.name1 = name; } function Parent2(name) { this.name2 = name; } function Child(name1, name2) {// 就是把父類先掛到子類的一個方法上,this就指向了子類上this.Method = Parent1;// 一執(zhí)行,父類上this的東西就掛到子類上了this.Method(name1);// 對象到手就甩,渣男delete this.Method; this.Method = Parent2;this.Method(name2);delete this.Method; } var child = new Child("me", "you");

8、class繼承

優(yōu)點:優(yōu)點就是沒有缺點

class Parent { static staticMethod() { return true; } constructor(name) { this.name = name; } ParentMethod() { console.log('method2'); } }; class Child extends Parent{ constructor(name) { super(name); this.childName = 'child'; } childMethod() { console.log('childMethod'); } }

三、引出的幾道經(jīng)典題目

1、new的時候做了什么

也就是 var Child = new Parent(); 干了啥
通過對原型的理解,我們很容易解答
1、創(chuàng)建一個空對象(不創(chuàng)建一個空的怎么往里面塞東西)
2、讓Prarent中的this指向Child,并執(zhí)行Parent的函數(shù)體(classconstructor,Parent本身)
3、設置原型鏈,將Child的__proto__的成員指向了Prarent的prototype的成員
4、給Child賦值,Parent的返回值類型是個值child就是個值,是個對象,child就是這個對象
也有回答說:將初始化完畢的新對象地址,保存到等號左邊的變量中
就是賦值,沒啥好解釋的,面試官聽不懂公司就沒必要去了
名詞解釋:
函數(shù)體:用class就是constructor,用構造函數(shù)就是Parent本身
prototype是原型才有的屬性,__proto__對象跟原型都有,__proto__里面存的是Parent的constructor
__proto__跟prototype可以額外找別的文章看

2、es6箭頭函數(shù)的特性

1、簡潔,直接返回的時候可以省略花括號跟return,一個參數(shù)的時候可以不寫入?yún)⒗ㄌ?br /> 2、this指向上層,上一層是箭頭函數(shù)繼續(xù)向上
3、不可以使用arguments對象,該對象在函數(shù)體內(nèi)不存在。如果要用,可以用 rest 參數(shù)代替。
4、不可以使用yield命令,因此箭頭函數(shù)不能用作 Generator 函數(shù)。
5、不可以作為構造函數(shù),因為this不是指向自己(這里只是為了說明這一點)

4、var、let、const的區(qū)別

這里主要是想說一下var的聲明提前,為什么我的例子里面都是用的var
因為在我們練手的時候經(jīng)常會反復聲明同一個變量,只有var可以反復賦值
而且聲明提前可以讓我們把想放一起的代碼集中到一塊
但是在實際使用中我們并不希望聲明提前,并且反復聲明,所以用let、const
同作用域的情況下:
1、var可以反復賦值,var是聲明提前
2、let、const只能賦值一次
3、const是常量,數(shù)值不能改變
4、const如果是聲明的對象,只是引用被固定

const a = {} // 下面的可以 a.name = 1; 下面不可以 a = {}; // 如果想要對象不可變 const a = Object.freeze({name:1});

5、call、apply、bind的區(qū)別

首先他們的作用都是改變this的指向(就是給this賦值)
區(qū)別:
1、入?yún)⒎矫?call、bind都是接收一個個逗號隔開的參數(shù),apply接收的是數(shù)組
2、使用入?yún)⒌臅r候都一樣,apply入?yún)⑹菙?shù)組,取的時候還是跟call,bind一樣一個個逗號隔開
3、call、apply是立即執(zhí)行this賦值,bind返回了一個函數(shù),需要手動執(zhí)行了才會給this賦值
通過下面的代碼來理解

var a = function(name, name1){this.name = name;this.name1 = name1; } var b = function(name){this.name = name } var c = function(name){this.name1 = name } var d = {}; // 順序執(zhí)行 a.call(d,'a', 'a'); // d = { name: 'a', name1: 'a' } b.call(d, 'b'); // d = { name: 'b', name1: 'a' } c.call(d, 'c'); // d = { name: 'b', name1: 'c' } // 使用appply實現(xiàn)一樣的效果 a.apply(d, ['a', 'a']) b.apply(d, ['b']) c.apply(d, ['c']) // 使用bind var e = a.bind(d, 'a', 'a'); // bind返回了一個新函數(shù),沒有執(zhí)行不會賦值 e(); // 一般寫成立即執(zhí)行 a.bind(d, 'a', 'a')()

6、編程的幾種范式

1、聲明式編程----html、css,看到他們你應該懂的
2、過程式編程----一步步變量聲明,執(zhí)行函數(shù)下來,這個是基礎,只要是開發(fā)都在用,即使是面向?qū)ο罄锩嬉矔嬖谶^程式
3、面向?qū)ο缶幊?---也叫oop,就是原型和對象
4、函數(shù)式編程----比較復雜
個人感覺跟過程式也差不多,各種說法都有,我也不敢寫太絕對,這里就講我知道的
函數(shù)式要先知道純函數(shù)的概念
像是數(shù)組方法 slice是截取生成新函數(shù),不改變輸入的值,就是純函數(shù)
像是 splice會改變原數(shù)組,就不是純函數(shù)
函數(shù)式的典型,輸入一個函數(shù),返回一個函數(shù)
代碼全都是(也有人認為大多數(shù)是)純函數(shù)來寫,使用了大量的可復用的函數(shù)的編程,就差不多算是函數(shù)式編程。
咱級別不夠,接觸不到函數(shù)式寫得很正宗的大佬,只能用自己的微薄經(jīng)驗總結一下:代碼里多點純函數(shù),復用性會強一點

使用原型封裝一個插件

本來是想要通過一個實際例子來講解一下原型繼承,但是寫著寫著發(fā)現(xiàn)舉個例子說明起來更不好理解了。本著寫了就不要浪費的原則,這里把還沒寫一半的代碼貼出來湊湊字數(shù),感興趣可以瞅瞅。
在vue跟react統(tǒng)治國內(nèi)前端的情況下,很多人都是在寫組件、套模板,只有以前jquery時代大家會經(jīng)常用原型封裝插件。現(xiàn)在需要從頭寫原型的場景很少,個人認為canvas是最可能自己寫代碼會深入使用原型繼承的。

假如我們想用canvas畫一個圓弧進度條

1、先貼出html部分

<head><style>.canvas{width: 400px;height: 400px;color: rgb(252, 44, 44);}</style> </head> <body><canvas id="canvas" width="300" height="300" class="canvas"></canvas><script>// js寫在dom之后才拿得到</script> </body>

2、如果習慣過程式編程的人(比如作者,因為寫起來確實順手),最開始會寫成下面的風格

// 角度轉(zhuǎn)弧度 function radians(degrees){return (Math.PI / 180) * degrees } // canvas畫圓弧,可以不用懂 function drawArc(strokeStyle, endAngle, ctx, lineWidth, centerXY, startAngle, round){// 開始一段繪制ctx.beginPath();// 顏色ctx.strokeStyle = strokeStyle;// 線寬ctx.lineWidth = lineWidth;// 線的兩端以圓角結束ctx.lineCap = 'round';// 畫圓弧參數(shù)依次為: 中心點x,y坐標,半徑,起始弧度,結束弧度ctx.arc(centerXY, centerXY, round, radians(startAngle), radians(endAngle));// 結束繪制ctx.stroke(); } // 畫圓弧 function draw(opitons) {let {lineWidth = 10, bgColor = 'rgb(198, 219, 223)', startAngle = 150, endAngle = 30, round, margin = 20, color = '#3399ff', percent, centerXY,id} = opitons;// 中心點等于半徑+左(上)邊距let centerXY = centerXY || (round + margin);// 獲取canvas畫筆var canvasDom = document.getElementById(id);var ctx = canvasDom.getContext('2d');// 畫背景圓弧drawArc(bgColor, endAngle, ctx, lineWidth, centerXY, startAngle, round);// 根據(jù)百分比算出弧度let end = percent * 240 / 100 + 150;end= end > 360 ? (end - 360) : end;drawArc(color, end, ctx, lineWidth, centerXY, startAngle, round) } // 執(zhí)行 draw({percent: 80, id: 'canvas', round: 80})

3、當作者想要做成插件的時候,就會改成面向?qū)ο缶幊?#xff08;用class)

// 因為上面注釋很充足,這邊就不寫注釋了 class Echarts {centerXY = 0;startAngle = 150;endAngle = 30;round = 0;margin = 0;color = '#3399ff';bgColor = 'rgb(198, 219, 223)';name = '進度';percent = 100;lineWidth = 10;constructor(opitons){this.name = opitons.name || this.name;this.round = opitons.round || this.round;this.margin = opitons.margin || this.margin;this.color = opitons.color || this.color;this.percent = opitons.percent || this.percent;this.centerXY = this.centerXY || (this.round + this.margin);var canvasDom = document.getElementById(opitons.id);this.ctx = canvasDom.getContext('2d');}// 畫進度條drawGauge(){this.drawArc(this.bgColor, this.endAngle);let endAngle = this.percent * 240 / 100 + 150;endAngle = endAngle > 360 ? (endAngle - 360) : endAngle;this.drawArc(this.color, endAngle);}// 畫圓弧drawArc(strokeStyle, endAngle){let ctx = this.ctxctx.beginPath();ctx.strokeStyle = strokeStyle;ctx.lineWidth = this.lineWidth;ctx.lineCap = 'round';ctx.arc(this.centerXY, this.centerXY, this.round, this.radians(this.startAngle), this.radians(endAngle));ctx.stroke();}// 角度轉(zhuǎn)弧度radians(degrees) {return (Math.PI / 180) * degrees} } var echarts = new Echarts({percent: 80, id: 'canvas', round: 80}); echarts.drawGauge();

可以很直觀的看到原型的封裝的優(yōu)點:
1)內(nèi)部屬性可以儲存一些配置,內(nèi)部方法可用,不用通過參數(shù)傳進去
2)暴露到外部的只有原型本身,不會出現(xiàn)很多全局變量

4、當想要在原本的圓弧的基礎上加一個圓弧,變成這樣

實際項目中用到繼承需要比較復雜的場景,作者也只是在剛學習前端的時候?qū)懥酥袊笃濉⒍砹_斯方塊練了練手就再也沒在項目中遇到過了,這里刻意用一下繼承(用得很生硬)

class Echarts {... } class EchartsChild extends Echarts {constructor(options){super(options);// 父類特地不給centerXY入?yún)?#xff0c;由子類自己定義centerXY,這就是原型多態(tài)this.centerXY = options.centerXY;}// 加多一個畫雙圓的方法drawDoubleGauge(){this.drawGauge();// 下面對round的處理是不對的,不應該對入?yún)⒆鲎兏?/span>// 但是由于作者偷懶,給舉一個反面教材,硬實現(xiàn)this.round = this.round/2;this.drawGauge();} } var echarts = new EchartsChild({percent: 80, id: 'canvas', round: 80, centerXY: 160}); echarts.drawDoubleGauge();

總結

以上是生活随笔為你收集整理的js原型继承的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。