以下題目是根據(jù)網(wǎng)上多份面經(jīng)收集而來的知残,題目相同意味著被問的頻率比較高廓俭,有問題歡迎留言討論云石,喜歡可以點(diǎn)贊關(guān)注。
1研乒、你是怎么理解面向?qū)ο蟮男谥遥裁词敲嫦驅(qū)ο螅妹嫦驅(qū)ο笞鲞^什么
面向?qū)ο缶幊蹋∣OP雹熬,Object Oriented Programming)
用一個對象去描述一些屬性和行為
好處:易維護(hù)宽菜、易復(fù)用、易擴(kuò)展竿报,由于面向?qū)ο笥蟹庋b铅乡、繼承、多態(tài)性的特性烈菌,可以設(shè)計出低耦合的系統(tǒng)阵幸,使系統(tǒng) 更加靈活、更加易于維護(hù)
2芽世、繼承(ES5/ES6)侨嘀,es5的繼承實現(xiàn)一下,手寫一個類的繼承
http://www.reibang.com/p/dd7eb3464759
es5 構(gòu)造函數(shù)繼承 原型鏈繼承 組合繼承 原子繼承 原型方法繼承 寄生式繼承 寄生組合式繼承
原型鏈繼承繼承
Cat.prototype = new Animal();
//上面的代碼把cat的prototype指向了Animal 現(xiàn)在要還原回來
Cat.prototype.constructor = Cat;
// 問題
// 1捂襟、子類的構(gòu)造函數(shù)的參數(shù)咬腕,沒法傳給父級的構(gòu)造函數(shù)
// 2、子類的原型的constructor會被改變葬荷,需要自己變回來
// 3涨共、父類使用this聲明的屬性被所有實例共享纽帖。 原因是實例化是父類一次性賦值到子類實例的原型上,它會將父類通過this聲明的屬性也賦值到子類原型上举反。例如在父類中一個數(shù)組值懊直,在子類的多個實例中,無論哪一個實例去修改這個數(shù)組的值火鼻,都會影響到其他子類實例室囊。
構(gòu)造函數(shù)繼承
function Cat(name, age) {
// Animal(age,name)//this===window;
// Animal.call(this) 無參數(shù)
Animal.call(this, name, age)//借用父類的構(gòu)造函數(shù) 給子類創(chuàng)建實例屬性
}
//優(yōu)點(diǎn):
//1、可以向父類傳遞參數(shù)魁索。
//2融撞、解決父類this聲明的屬性會被實例共享的問題。即實例修改屬性不會影響到父級屬性
//缺點(diǎn)
//1粗蔚、不能繼承父類prototype上的屬性/方法尝偎,只能繼承父類通過this聲明的屬性/方法。
//2鹏控、每次實例化子類致扯,都要執(zhí)行父類函數(shù)。重新聲明父類所定義的方法当辐,無法復(fù)用抖僵。
組合繼承
規(guī)避了原型繼承法(解決父類this聲明的屬性會被實例共享的問題)和構(gòu)造函數(shù)繼承法的缺點(diǎn)
// 父類
function Animal(name, age) {
this.name = name;
this.age = age;
this.fruit = ['water', 'apple']
}
// 在父類的原型上 創(chuàng)建run方法
Animal.prototype.run = function () {
console.log(this.name + ' running')
}
function Cat(name, age) {
// Animal(age,name)//this===window;
Animal.call(this, name, age)
}
Cat.prototype = new Animal();//組合原型繼承模式
Cat.prototype.constructor = Cat;
var c = new Cat('Tom', 12)
console.dir(Cat.prototype.constructor)
console.log(c)// Tom
console.log(c.name)// Tom
console.log(c.age)// Tom
console.log(c.fruit)// ['water', 'apple']
優(yōu)點(diǎn)
1、解決原型鏈繼承父類this聲明的屬性或者方法被共享的問題缘揪。
2裆针、解決借用構(gòu)造函數(shù)解決不能繼承父類prototype對象上的屬性/方法問題。
缺點(diǎn)
1寺晌、調(diào)用了父類函數(shù)兩次,造成一定的性能問題澡刹。
2呻征、因調(diào)用兩次父類,導(dǎo)出父類通過this聲明的屬性和方法被生成兩份的問題罢浇。
3陆赋、原型鏈上下文丟失,子類和父類通過prototype聲明的屬性和方法都存在與子類prototype上
原子繼承
var animal = { name: "qw", age: 8 };
var a = Object.create(animal);//'qw'
console.log(a.name);//{}
console.log(a);
// 注:此時a.name a.age可訪問成功嚷闭,但a本身并無此類屬性攒岛,而a原型上有這些屬性
// 優(yōu)點(diǎn):
// 不需要使用new構(gòu)造函數(shù)就可以直接 構(gòu)造另外其他對象
// 缺點(diǎn):
// 所有構(gòu)造函數(shù)出來的實例會共享 原型對象上的引用類型的屬性
原型式繼承
http://www.reibang.com/p/ab6b8821e80f
function Object(o) {
function F(){};
// 將被繼承的對象作為空函數(shù)的prototype
F.prototype = o;
// 返回new期間創(chuàng)建的新對象,此對象的原型為被繼承的對象,
// 通過原型鏈查找可以拿到被繼承對象的屬性
return new F();
}
var o = { name: 'liu', age: 23 }
var m1 = Object(o)
console.log(m1.name)
優(yōu)點(diǎn)
1、兼容性好胞锰,最簡單的對象繼承灾锯。
缺點(diǎn)
1、多少實例共享被繼承的屬性嗅榕,存在被篡改的情況顺饮,不能傳遞參數(shù)吵聪。
寄生式繼承
function Person(name, age, job) {
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function() {
alert(this.name);
}
return o;
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
/* 重點(diǎn):就是給原型式繼承外面套了個殼子。
優(yōu)點(diǎn):沒有創(chuàng)建自定義類型兼雄,因為只是套了個殼子返回對象吟逝,這個函數(shù)順理成章就成了創(chuàng)建的新對象。
缺點(diǎn):沒用到原型赦肋,無法復(fù)用块攒。 */
優(yōu)點(diǎn)
1、兼容性好佃乘,最簡單的對象繼承囱井。
缺點(diǎn)
1、多少實例共享被繼承的屬性恕稠,存在被篡改的情況琅绅,不能傳遞參數(shù)。
寄生組合繼承
JS的繼承方式有很多種鹅巍,最理想的繼承方式是寄生組合式繼承千扶。
組合繼承(構(gòu)造函數(shù)和原型的組合)會調(diào)用兩次父類構(gòu)造函數(shù)的代碼,
function Person(name){
this.name=name;
}
Person.prototype.sayName=function(){
console.log(this.name+' '+this.gender+' '+this.age);
}
function Female(name,gender,age){
Person.call(this,name);//第一次調(diào)用父類構(gòu)造函數(shù)
this.age=age;
this.gender=gender;
}
Female.prototype=new Person();//第一次調(diào)用父類構(gòu)造函數(shù)
Female.prototype.constrcutor=Female;//因重寫原型而失去constructor屬性骆捧,所以要對constrcutor重新賦值
因此引入寄生組合式繼承澎羞,即通過借用構(gòu)造函數(shù)來繼承屬性,通過原型鏈的方式來繼承方法敛苇,而不需要為子類指定原型而調(diào)用父類的構(gòu)造函數(shù)妆绞,我們需要拿到的僅僅是父類原型的一個副本。因此可以通過傳入子類和父類的構(gòu)造函數(shù)作為參數(shù)枫攀,首先創(chuàng)建父類原型的一個復(fù)本括饶,并為其添加constrcutor,最后賦給子類的原型来涨。這樣避免了調(diào)用兩次父類的構(gòu)造函數(shù)图焰,為其創(chuàng)建多余的屬性。
function Parent(name) {
this.name = name
}
Parent.sayHello = function (){
console.log('hello')
}
Parent.prototype.sayName = function() {
console.log('my name is ' + this.name)
return this.name
}
function Child(name, age) {
Parent.call(this, name)
this.age = age
}
function _inherits(Child, Parent) {
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
Child.__proto__ = Parent
}
_inherits(Child, Parent)
Child.prototype.sayAge = function () {
console.log('my age is ' + this.age)
return this.age
}
var parent = new Parent('Parent')
var child = new Child('Child', 18)
console.log(parent)
Parent.sayHello()
parent.sayName()
console.log(child)
Child.sayHello()
child.sayAge()
child.sayName()
1蹦掐、寄生組合式繼承是當(dāng)前最成熟的繼承方法技羔,也是先也常用的繼承方法,在大多數(shù)Js框架中都是用這個作為繼承方案卧抗。
寄生組合式繼承相對組合繼承的優(yōu)點(diǎn):
1藤滥、只調(diào)用了父類構(gòu)造函數(shù)一次,節(jié)約了性能社裆。
2拙绊、避免生成了不必要的屬性。
3、使用原型式繼承保證了原型鏈上下文不變时呀,子類的prototype只有子類通過prototype聲明的屬性和方法张漂,父類的prototype只有父類通過prototype聲明的屬性和方法。
es6通過extend
1谨娜、class 可以理解為function,由于class本質(zhì)還是一個function,因此它也會擁有一個的prototype屬性航攒,當(dāng)new一個class時,會把class的porototype屬性賦值給這個新對象的 __proto屬性趴梢。
2漠畜、constructor 方法是默認(rèn)添加的方法,在new一個對象時坞靶,自動調(diào)用該方法憔狞,constructor里面定義自己的屬性。
3彰阴、繼承extends和super瘾敢,class 子類名 extends 父類名實現(xiàn)繼承,當(dāng)然還得在constructor里面寫上super(父類的參數(shù))尿这,意思就是在子類中獲得父類的this指針簇抵,相當(dāng)于Animal.call(this)
// es6繼承
class Animal {
//構(gòu)造函數(shù),里面寫上對象的屬性
constructor(props) {
this.name = props.name || 'Unknown';
}
//方法寫在后面
eat() {//父類共有的方法
console.log(this.name + " will eat pests.");
}
}
//class繼承
class Bird extends Animal {
//構(gòu)造函數(shù)
constructor(props,myAttribute) {//props是繼承過來的屬性射众,myAttribute是自己的屬性
//調(diào)用實現(xiàn)父類的構(gòu)造函數(shù)
super(props)//相當(dāng)于獲得父類的this指向
this.type = props.type || "Unknown";//父類的屬性碟摆,也可寫在父類中
this.attr = myAttribute;//自己的私有屬性
}
fly() {//自己私有的方法
console.log(this.name + " are friendly to people.");
}
myattr() {//自己私有的方法
console.log(this.type+'---'+this.attr);
}
}
//通過new實例化
var myBird = new Bird({
name: '小燕子',
type: 'Egg animal'//卵生動物
},'Bird class')
myBird.eat()
myBird.fly()
myBird.myattr()
3、JS的繼承方法x2叨橱,用原型實現(xiàn)繼承有什么缺點(diǎn)典蜕,怎么解決
同2題,繼承最大的優(yōu)點(diǎn)就是能繼承父類構(gòu)造函數(shù)和原型的屬性和方法
缺點(diǎn):
1罗洗、子類的構(gòu)造函數(shù)的參數(shù)愉舔,沒法傳給父級的構(gòu)造函數(shù)
2、子類的原型的constructor會被改變伙菜,需要自己變回來
3轩缤、父類使用this聲明的屬性被所有實例共享。 原因是實例化是父類一次性賦值到子類實例的原型上仇让,它會將父類通過this聲明的屬性也賦值到子類原型上。例如在父類中一個數(shù)組值躺翻,在子類的多個實例中丧叽,無論哪一個實例去修改這個數(shù)組的值,都會影響到其他子類實例公你。
方法:實用組合繼承
4踊淳、解釋一下原型和原型鏈有什么特點(diǎn)x2
原型
每個函數(shù)都有一個prototype屬性,這個屬性指向的就是原型對象. 實例有一個proto指向它構(gòu)造函數(shù)的原型對象.
原型鏈
當(dāng)我們訪問一個對象的屬性時,如果這個對象內(nèi)部不存在這個屬性,會去對象的proto屬性(隱式原型對象)里去找 , (這里的隱式原型對象指向的就是它構(gòu)造函數(shù)的prototype(顯示原型對象))然后原型本身也是一個對象 , 擁有proto 屬性 , 所以會繼續(xù)向上查找 ,一直找到Object.prototype.proto===null 這樣的鏈條稱之為原型鏈
特點(diǎn)
原型對象上的方法是被不同實例共有的 迂尝,當(dāng)我們修改原型時脱茉,與之相關(guān)的對象也會繼承這一改變。原型鏈就是為了實現(xiàn)繼承
5垄开、介紹this和原型
一:到底什么是this呢
概念:執(zhí)行上下文琴许,this一般存在于函數(shù)中,表示當(dāng)前函數(shù)的執(zhí)行上下文
溉躲, 如果函數(shù)沒有執(zhí)行榜田,那么this沒有內(nèi)容,只有函數(shù)在執(zhí)行后this才有綁定锻梳。注意:this的指向只能是對象箭券,當(dāng)然別忘記了數(shù)組也是一個特殊的對象。
二:this到底指向的是誰呢
this的指向其實是跟this的執(zhí)行位置是有關(guān)的疑枯,不同的位置執(zhí)行位置辩块,this的指向就可能發(fā)生改變。this被誰執(zhí)行了荆永,this就是執(zhí)行誰的废亭,這時候一定要看清楚this是被誰直接執(zhí)行的! 默認(rèn)執(zhí)行:this指向了window屁魏,在嚴(yán)格模式下滔以,this指向了undefined
6、使用原型最大的好處
原型的作用一:數(shù)據(jù)共享氓拼,節(jié)省空間你画,繼承
利用原型可以使得每一個實例對象都可以調(diào)用原型上的say方法,可以讓每個實例對象只是得到函數(shù)say的一個指針桃漾,指向同一個say函數(shù)坏匪,節(jié)省了空間
原型的作用二:繼承
在子類構(gòu)造函數(shù)中借用父類構(gòu)造函數(shù),再通過原型繼承父類的原型屬性和方法撬统,模擬繼承的效果
7适滓、prototype和_proto_區(qū)別x2
對象并不具有prototype屬性,只有函數(shù)才有prototype屬性恋追。
總結(jié):
js里所有的對象都有proto屬性(對象凭迹,函數(shù)),指向構(gòu)造該對象的構(gòu)造函數(shù)的原型苦囱。
只有函數(shù)function才具有prototype屬性嗅绸。這個屬性是一個指針,指向一個對象撕彤,這個對象的用途就是包含所有實例共享的屬性和方法(我們把這個對象叫做原型對象)鱼鸠。原型對象也有一個屬性,叫做constructor,這個屬性包含了一個指針蚀狰,指回原構(gòu)造函數(shù)愉昆。
8、construct是什么
construct構(gòu)造屬性構(gòu)造屬性存儲著這個對象所對應(yīng)的函數(shù)的引用麻蹋。由于JS對象是通過call調(diào)用并執(zhí)行堆的聲明函數(shù)實現(xiàn)的JS對象跛溉,所以JS對象的construct存儲著這個JS對象所調(diào)用的函數(shù)的函數(shù)引用
<script type="text/javascript">
function employee(name,job,born)
{
this.name=name;
this.job=job;
this.born=born;
}
var bill=new employee("Bill Gates","Engineer",1985);
document.write(bill.constructor);
console.log(bill.__proto__.constructor);//結(jié)果同上
</script>
輸出:
function employee(name, job, born)
{this.name = name; this.job = job; this.born = born;}
9、new 的過程哥蔚,new的原理是什么倒谷?通過new的方式創(chuàng)建對象和通過字面量創(chuàng)建有什么區(qū)別,實現(xiàn)new
10糙箍、ES5對象 vs ES6對象(es6 class 的new實例和es5的new實例有什么區(qū)別渤愁?)
在ES6
中(和ES5
相比),class
的new
實例有以下特點(diǎn):
-
class
的構(gòu)造參數(shù)必須是new
來調(diào)用深夯,不可以將其作為普通函數(shù)執(zhí)行 -
es6
的class
不存在變量提升 -
最重要的是:
es6
內(nèi)部方法不可以枚舉抖格。es5的prototype
上的方法可以枚舉。
為此我做了以下測試代碼進(jìn)行驗證:
console.log(ES5Class()) // es5:可以直接作為函數(shù)運(yùn)行
// console.log(new ES6Class()) // 會報錯:不存在變量提升
function ES5Class(){
console.log("hello")
}
ES5Class.prototype.func = function(){ console.log("Hello world") }
class ES6Class{
constructor(){}
func(){
console.log("Hello world")
}
}
let es5 = new ES5Class()
let es6 = new ES6Class()
console.log("ES5 :")
for(let _ in es5){
console.log(_)
}
// es6:不可枚舉
console.log("ES6 :")
for(let _ in es6){
console.log(_)
}
復(fù)制代碼
這篇《JavaScript創(chuàng)建對象—從es5到es6》對這個問題的深入解釋很好咕晋,推薦觀看雹拄!
11、介紹class和ES5的類以及區(qū)別
ES5的繼承實質(zhì)是先創(chuàng)建子類的實例對象this掌呜,然后將父類的方法添加到this上滓玖。
ES6的繼承實質(zhì)是先將父類實例對象的方法和屬性加到this上面,然后在用子類的構(gòu)造函數(shù)修改this质蕉。
ES5的繼承是通過prototype或構(gòu)造函數(shù)機(jī)制來實現(xiàn)势篡。ES5的繼承實質(zhì)上是先創(chuàng)建子類的實例對象,然后再將父類的方法添加到this上(Parent.apply(this))模暗。ES6的繼承機(jī)制實質(zhì)上是先創(chuàng)建父類的實例對象this(所以必須先調(diào)用父類的super()方法)禁悠,然后再用子類的構(gòu)造函數(shù)修改this。具體為ES6通過class關(guān)鍵字定義類兑宇,里面有構(gòu)造方法碍侦,類之間通過extends關(guān)鍵字實現(xiàn)繼承。子類必須在constructor方法中調(diào)用super方法隶糕,否則新建實例報錯瓷产。因為子類沒有自己的this對象,而是繼承了父類的this對象枚驻,然后對其調(diào)用濒旦。如果不調(diào)用super方法,子類得不到this對象测秸。注意:super關(guān)鍵字指代父類的實例疤估,即父類的this對象。在子類構(gòu)造函數(shù)中霎冯,調(diào)用super后铃拇,才可使用this關(guān)鍵字,否則報錯沈撞。
12慷荔、call,apply缠俺,bind 三者用法和區(qū)別:角度可為參數(shù)显晶、綁定規(guī)則(顯示綁定和強(qiáng)綁定),運(yùn)行效率壹士、運(yùn)行情況磷雇,原生實現(xiàn)bind
https://juejin.im/post/59bfe84351882531b730bac2
fun.apply(thisArg, [argsArray])
thisArg:在 fun 函數(shù)運(yùn)行時指定的 this 值。需要注意的是躏救,指定的 this 值并不一定是該函數(shù)執(zhí)行時真正的 this 值唯笙,如果這個函數(shù)處于非嚴(yán)格模式下,則指定為 null 或 undefined 時會自動指向全局對象(瀏覽器中就是window對象)盒使,同時值為原始值(數(shù)字崩掘,字符串,布爾值)的 this 會指向該原始值的自動包裝對象少办。
argsArray:一個數(shù)組或者類數(shù)組對象,其中的數(shù)組元素將作為單獨(dú)的參數(shù)傳給 fun 函數(shù)。如果該參數(shù)的值為null 或 undefined活喊,則表示不需要傳入任何參數(shù)酒请。從ECMAScript 5 開始可以使用類數(shù)組對象。瀏覽器兼容性請參閱本文底部內(nèi)容鞋拟。
首先說明call骂维,apply是ES5中的語法,bind是ES6新引入的贺纲,它們?nèi)叩南嗨浦帪椋?/p>
- 都是用來改變函數(shù)的this對象的指向(即函數(shù)運(yùn)行時的上下文(context))
- 第一個參數(shù)都是this要指向的對象
- 都可以利用后續(xù)參數(shù)進(jìn)行傳參
apply方法實際上是與call方法用法相同航闺,只不過apply方法傳進(jìn)去的參數(shù)是以數(shù)組形式(或者類數(shù)組)bind()的作用其實與call()以及apply()都是一樣的,都是為了改變函數(shù)運(yùn)行時的上下文猴誊,bind()與后面兩者的區(qū)別是潦刃,call()和apply()在調(diào)用函數(shù)之后會立即執(zhí)行,而bind()方法調(diào)用并改變函數(shù)運(yùn)行時的上下文的之后懈叹,返回一個新的函數(shù)乖杠,在我們需要調(diào)用的地方去調(diào)用他。
call澄成,apply胧洒,bind 三者用法和區(qū)別:參數(shù)畏吓、綁定規(guī)則(顯示綁定和強(qiáng)綁定)、運(yùn)行效率(最終都會轉(zhuǎn)換成一個一個的參數(shù)去運(yùn)行)卫漫、運(yùn)行情況(call菲饼,apply 立即執(zhí)行,bind 是return 出一個 this “固定”的函數(shù)列赎,這也是為什么 bind 是強(qiáng)綁定的一個原因)宏悦。
注:“固定”這個詞的含義,它指的固定是指只要傳進(jìn)去了 context包吝,則 bind 中 return 出來的函數(shù) this 便一直指向 context饼煞,除非 context 是個變量。
實現(xiàn)bind
Function.prototype.myBind = function (obj) {
const object = obj || window; //如果第一個參數(shù)為空則默認(rèn)指向window對象
let self = this;
let args = [...arguments].slice(1); //存放參數(shù)的數(shù)組
return function () {
let newArgs = [...arguments]
return self.apply(object, args.concat(newArgs))
}
}
personOne.say.myBind(personTwo, "女", 24)();
前面的知識不重復(fù)說诗越,return function是因為bind返回的是一個函數(shù)砖瞧,并且這個函數(shù)不會執(zhí)行,需要我們再次調(diào)用嚷狞,那么當(dāng)我們調(diào)用的時候芭届,我們依舊可以對這個函數(shù)進(jìn)行傳遞參數(shù),即為支持柯里化形式傳參感耙,所以需要在返回的函數(shù)中聲明一個空的數(shù)組接收調(diào)用bind函數(shù)返回的函數(shù)時傳遞的參數(shù)褂乍,之后對兩次的參數(shù)使用concat()方法進(jìn)行連接,調(diào)用ES5中的apply方法即硼。
介紹下原型鏈(解決的是繼承問題嗎)
請簡述原型鏈
https://juejin.im/post/5d282541e51d4577523f2422
function employee(name, job, born) {
this.name = name;
this.job = job;
this.born = born;
}
var b = new employee("Bill Gates", "Engineer", 1985);
employee.prototype.age = 200
console.log('age' in b);//true
console.log(b.hasOwnProperty('name'));//true
console.log(b.hasOwnProperty('age'));//false 原型中的屬性不能檢測到
console.log(b.age !== undefined);//true