上一篇咱們講到了,在前端面試的時候常被問到的函數及函數做用域的問題。今天這篇咱們講js的一個比較重要的甚至在編程的世界都很重要的問題 面向對象 。javascript
「一切皆對象!」 你們都對此深信不疑。其實否則,這裏面帶有不少的語言陷阱,仍是不要處處給別人吹噓一切皆對象爲好。css
JavaScript 是一種弱類型或者說動態語言。這意味着你不用提早聲明變量的類型,在程序運行過程當中,類型會被自動肯定。這也意味着你可使用同一個變量保存不一樣類型的數據,最新的 ECMAScript 標準定義了 7 種數據類型:html
基本類型前端
對象類型java
對象類型涵蓋了不少引用類型,任何非基本類型的都是對象類型。如Function(函數Function 是一個附帶可被調用功能的常規對象),這裏就不在贅述。git
根據這個分類能夠看出「並不是一切接對象」。github
咱們能夠從兩方面來區別這兩種類型:面試
可變性編程
基本類型:不可變類型,沒法添加屬性;即便添加屬性,解析器沒法再下一步讀取它;數組
var cat = "cat";
cat.color = "black";
cat.color // undefined
複製代碼
對象類型:可變類型,支持添加和刪除屬性。
比較和傳遞
基本類型:按值比較,按值傳遞;
對象類型:按引用比較,按引用傳遞。
// 基本類型
var cat = "tom";
var dog = "tom";
cat === dog // true
//對象類型
var cat = {name:"tom"};
var dog = {name:"tom"};
cat === dog //false
複製代碼
咱們說的經過引用進行對象比較是:兩個對象的值是否相同取決於它們是否指向相同的底層對象 __David Flanagan
因此咱們改爲這樣:
var cat = {name:"tom"}
var dog = cat;
b.name = "Haba"
dog === cat // true
複製代碼
檢測一個對象的類型,強烈推薦使用 Object.prototype.toString 方法; 由於這是惟一一個可依賴的方式。 咱們使用Object.prototype.toString方法:
Object.prototype.toString.call([]) // "[object Array]"
Object.prototype.toString.call({}) // "[object Object]"
Object.prototype.toString.call(2) // "[object Number]"
複製代碼
爲何不能用typeOf ?
typeof只有在基本類型的檢測上面纔好使,在引用類型(Function除外)裏面他返回的都是object,另 typeof null === "object".
「面向對象編程(OOP)」 是目前主流的編程範式,其核心思想是將真實世界中各類複雜的關係,抽象爲一個個對象,而後由對象之間的分工與合做,完成對真實世界的模擬。
對象是單個實物的抽象,一本書,一隻貓,一我的均可以是對象。從語言的角度對象就是一個容器封裝了屬性和方法。
典型面向對象的兩大概念:類和 實例
很遺憾JavaScript沒有類的概念,但它使用構造函數(constructor)做爲對象的模板。
//構造函數
var Pet = function (name, language) {
this.name = name;
this.say = function () {
console.log(language);
}
}
// new 關鍵字生成對象 有關new操做符咱們在後面會講到。
var cat = new Pet('tom', 'meow');
cat.name // tom
cat.say() // meow
複製代碼
new用於新建一個對象,例如:
function Pet () {}
var tom = new Pet();
複製代碼
new進行了以下操做:
function newObj(Fun,arguments) {
var o = {};
if (Fun && typeof Fun === "function") {
o.__proto__ = Fun.prototype;
Fun.apply(o, arguments);
return o;
}
}
複製代碼
這裏須要注意的是,構造函數內部有return語句的狀況。若是return 後面跟着一個對象,new命令返回return指定的對象;不然無論return語句直接返回this.
var Pet = function (name) {
this.name = name;
return {notInstance:"blabla"}
}
var cat = new Pet('tom');
cat.name // undefined
cat.notInstance // blabla
複製代碼
上面的講到的構造函數,實例對象的屬性和方法都在構造函數內部實現。這樣的 構造函數有一個缺點:
var cat1 = new Pet('tom', 'meow');
var cat2 = new pet('jery', 'meow');
cat1.say === cat2.say // false
複製代碼
生成兩隻貓 叫聲同樣,可是貓的say方法是不同的,就是說每新建一個對象就生成一個新的say方法。全部的say方法都是一樣的行爲,徹底能夠共享。
JavaScript的原型(prototype)可讓咱們實現共享。
JavaScrip能夠採用構造器(constructor)生成一個新的對象,每一個構造器都擁有一個prototype屬性,而每一個經過此構造器生成的對象都有一個指向該構造器原型(prototype)的內部私有的連接(proto),而這個prototype由於是個對象,它也擁有本身的原型,這麼一級一級直到原型爲null,這就構成了原型鏈.
原型鏈的工做原理:
function getProperty(obj, prop) {
if (obj.hasOwnProperty(prop)) //首先查找自身屬性,若是有則直接返回
return obj[prop]
else if (obj.__proto__ !== null)
return getProperty(obj.__proto__, prop) //如何不是私有屬性,就在原型鏈上一步步向上查找,直到找到,若是找不到就返回undefind
else
return undefined
}
複製代碼
若是跟着原型鏈一層層的尋找,全部對象均可以尋找到最頂層,Object.prototype, 即Object的構造函數的prototype屬性,而Object.prototype對象指向的就是沒有任何屬性和方法的null對象。
Object.getPrototypeOf(Object.prototype)
// null
複製代碼
原型鏈代表了一個對象查找他的屬性的過程:首先在對象自己上面找 -> 沒找到再到對象的原型上找 ->仍是找不到就到原型的原型上找 —>直到Object.prototype找不到 -> 返回undefined。(在這種查找中找到就馬上返回)。
prototype對象有一個constructor屬性,默認指向prototype對象所在的構造函數。
因爲constructor屬性是一種原型對象與構造函數的關聯關係,因此修改原型對象的時候,務必要當心。
var Pet = function (name) {
this.name = name;
}
// 儘可能避免這麼寫,由於會把construct屬性覆蓋掉。
Pet.prototype = {
say: function () {
console.log('meow');
}
}
// 若是咱們覆蓋了constructor屬性要記得將他指回來。
Pet.prototype.constructor = Pet;
複製代碼
prototype是function對象中專有的屬性。
__proto__是普通對象的隱式屬性,在new的時候,會指向prototype所指的對象;
__proto__其實是某個實體對象的屬性,而prototype則是屬於構造函數的屬性。
__proto__只能在學習或調試的環境下使用。
這裏抓住兩點:
Object 爲構造函數時,是Function的實例對象;Function爲構造函數時,Function.prototype 是對象,那麼他就是Object的實例對象。
來看一個題目:
var F = function(){};
Object.prototype.a = function(){};
Function.prototype.b = function(){};
var f = new F();
// f 能取到a,b嗎?原理是什麼?
複製代碼
根據原型鏈的關係:
f是F的實例對象,其原型鏈:
f.__proto__ -> [F prototype].__proto__ -> [Object prototype].__proto__ -> null
複製代碼
F是構造函數,是Function的實例,他的原型鏈:
F.__proto__ -> [Function prototype].__proto__ -> [Object prototype].__proto__ -> null
複製代碼
由此,只有F可以訪問到Function的prototype,答案就是:「f只能a,可是F能夠訪問a,b」
原型繼承是藉助已有的對象建立新的對象,將子類的原型指向父類,就至關於加入了父類這條原型鏈。
function Animal(){
this.super = 'animal';
}
function Cat(name) {
this.name = name;
this.food = 'fish';
}
Cat.prototype = new Animal(); // Cat 繼承了Animal
Cat.prototype.getFood = function () {
return this.food;
}
複製代碼
上面的方法中constructor指向有點問題:
var cat = new Cat('tom');
cat.name // tom
cat.super // animal
cat.getFood() // fish
//but
cat.constructor === Cat //false
cat.constructor === Animal //true
複製代碼
cat 的constructor 並無指向Cat而是指向了父類Animal。咱們須要對它進行修正:
function Cat(name){
this.name = name;
this.food = 'fish';
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
Cat.prototype.getFood = function () {
return this.food;
}
複製代碼
好了上面就實現了一個簡單的原型繼承。
關於面向對象,我想說js 「幾乎一切皆對象」,由於有原型鏈的存在咱們能實現相似其餘語言的繼承。
加上前面一篇,兩篇文章已經涵蓋了js大半部分面試問題了,接下來的文章可能會講解一下單線程模型和計時器相關。這塊是個難點我也是看了好多資料後才搞明白了大概。此次的面試系列主要仍是針對於「中高級前端」,也是一個進階的層次,各位看官不要灰心一切都會有撥雲見日的一天。
女排奪冠了,今年好像好多我關注的時間都有了完美的結局,非常爲他們高興,也但願個人將來也是一個完美的結局,晚安。
請關注個人專欄 《前端雜貨鋪》