JS面試題—原型和原型鏈
一、 題目
- 如何準確判斷一個變數是陣列型別
- 寫一個原型鏈繼承的例子
- 描述new一個物件的過程
二、知識點
1.建構函式
function Foo(name, age){
this.name = name
this.age = age
this.class = 'class-1'
// return this //預設有這一行
}
var f = new Foo('zhangsan', '20')
//var f1 = new Foo('lisi', 22) //可建立多個物件
特點:預設函式首字母大寫,建構函式並沒有顯示返回任何東西。new 操作符會自動建立給定的型別並返回他們,當呼叫建構函式時,new會自動建立this物件,且型別就是建構函式型別。
2.建構函式—擴充套件
var a = {} 其實是 var a = new Object()的語法糖
var a = [] 其實是 var a = new Array()的語法糖
function Foo(){…} 其實是 var Foo = new Function(){…}
使用instanceof判斷一個函式是否是建構函式
3.原型規則和示例
原型規則是原型鏈的基礎
- 所有引用型別(陣列、物件、函式),都具有物件特性,即可自由擴充套件屬性(除‘null’以外)
- 所有引用型別(陣列、物件、函式),都具一個_proto_
- 所有函式,都有一個prototype(顯式原型)屬性,屬性值也是一個普通物件
- 所有引用型別(陣列、物件、函式),_proto_ (隱式原型)屬性值指向它的建構函式的prototype(顯式原型)屬性值
- 當試圖得到一個物件的某個屬性時,如果這個物件本身沒有這個屬性,那麼會去它的_proto_ (即它的建構函式的prototype)中尋找
var obj = {}; obj.a = 100;
var arr = []; arr.a = 100;
function fn(){}
fn.a = 100; // 所有引用型別都能拓展屬性
console .log(obj.__proto__)
console.log(arr.__proto__)
console.log(fn.__proto__) //所有引用型別都有_proto_(隱式原型)屬性
console.log(fn.prototype) //所有函式,都有一個prototype(顯式原型)屬性
console.log(obj.__proto__ === Object.prototype)// true 引用型別_proto_(隱式原型)屬性值指向它的建構函式的prototype(顯式原型)屬性值
//當試圖得到一個物件的某個屬性時,如果這個物件本身沒有這個屬性,那麼會去它的*_proto_* (即它的建構函式的prototype)中尋找
//建構函式
function Foo(name, age){
this.name = name;
}
Foo.prototype.alertName = function(){
alert(this.name)
}
//建立例項
var f = new Foo('zhangsan');
f.printName = function(){
console.log(this.name);
}
//測試
f.printName(); //f自身有printName這個屬性,所以直接可以呼叫
f.alertName(); //f自身沒有alertName這個屬性,所以會去它的_proto_(即它的建構函式的prototype)中尋找
tips:迴圈物件自身的屬性時,建議加上hasOwnProperty來保證程式的健壯性
var item;
for(item in f){
//高階瀏覽器已經在for in 中遮蔽了來自原型的屬性
//但是這裡建議大家還是加上這個判斷,保證程式的健壯性
if(f.hasOwnProperty(item)){
console.log(item)
}
}
4.原型鏈
還是採用上面建構函式的例子來解釋原型鏈
//建構函式
function Foo(name, age){
this.name = name;
}
Foo.prototype.alertName = function(){
alert(this.name)
}
//建立例項
var f = new Foo('zhangsan');
f.printName = function(){
console.log(this.name);
}
//測試
f.toString(); //要去f._proto_._proto_中查詢
/*
f自身沒有toString這個屬性, 那麼f會去它的隱式原型_proto_(即它的建構函式的prototype)中尋找,而f的構 造函式也沒有toString這個屬性,那該往哪裡找?
原型規則中第二、三條提到:所有引用型別(陣列、物件、函式),都具一個_proto_(隱式原型)屬性,屬性值是一個普通物件,意思f._proto_(f.prototype)是個物件
f._proto_是個物件,那麼該往f._proto_隱式原型中尋找,即f._proto_._proto_ (f._proto_尋找其物件的建構函式也就是Object)
* */
5.instanceof
用於判斷引用型別屬於哪個建構函式的方法
f instanceof Foo的判斷邏輯:
f的proto一層一層往上,能否找到Foo.prototype
再試著判斷f instanceof Object
三、解答
1.如何準確判斷一個變數是陣列型別
var arr = []
arr instanceof Array //true
typeof arr // Object typeof是無法判斷是否是陣列的
2.寫一個原型鏈繼承的例子
//寫一個封裝DOM的例子
function Elem(id){
this.elem = document.getElementById(id);
}
Elem.prototype.html = function (val) {
var elem = this.elem;
if(val){
elem.innerHTML = val;
return this; //鏈式操作, 可有可無
}else{
return elem.innerHTML;
}
}
Elem.prototype.on = function(type, fn){
var elem = this.elem;
elem.addEventListener(type, fn);
return this;
}
var div1 = new Elem('div1');
//console.log(div1.html())
div1.html('<p>hello world<p>').on('click', function(){
alert('clicked');
}).html('<p>javascript<p>')
3.描述new一個物件的過程
- 建立空物件;
var obj = {}; 設定新物件的constructor屬性為建構函式的名稱,設定新物件的_proto_屬性指向建構函式的prototype物件;
obj._proto_ = ClassA.prototype;使用新物件呼叫函式,函式中的this被指向新例項物件:
ClassA.call(obj); //{}.建構函式();將初始化完畢的新物件地址,儲存到等號左邊的變數中
tips:若建構函式中返回this或返回值是基本型別(number、string、boolean、null、undefined)的值,則返回新例項物件;若返回值是引用型別的值,則實際返回值為這個引用型別。