前端面試之js相關問題(二)

上一篇咱們講到了,在前端面試的時候常被問到的函數及函數做用域的問題。今天這篇咱們講js的一個比較重要的甚至在編程的世界都很重要的問題 面向對象 。javascript

在JavaScript中一切都是對象嗎?

「一切皆對象!」 你們都對此深信不疑。其實否則,這裏面帶有不少的語言陷阱,仍是不要處處給別人吹噓一切皆對象爲好。css

數據類型

JavaScript 是一種弱類型或者說動態語言。這意味着你不用提早聲明變量的類型,在程序運行過程當中,類型會被自動肯定。這也意味着你可使用同一個變量保存不一樣類型的數據,最新的 ECMAScript 標準定義了 7 種數據類型:html

基本類型前端

  • Boolean
  • Null
  • Undefined
  • Number
  • String
  • Symbol (ECMAScript 6 新定義)

對象類型java

  • Object

對象類型涵蓋了不少引用類型,任何非基本類型的都是對象類型。如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建立一個對象都進行了哪些操做?

new用於新建一個對象,例如:

function Pet () {}
var tom = new Pet();
複製代碼

new進行了以下操做:

  • 建立一個空對象,用this 變量引用該對象並繼承該函數的原型
  • 屬性和方法加入到this的引用對象中
  • 新建立的對象由this所引用,而且最後隱式的返回this
    模擬過程:
    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
複製代碼

闡述原型鏈?js如何實現繼承?

上面的講到的構造函數,實例對象的屬性和方法都在構造函數內部實現。這樣的 構造函數有一個缺點:

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。(在這種查找中找到就馬上返回)。

constructor 屬性

prototype對象有一個constructor屬性,默認指向prototype對象所在的構造函數。
因爲constructor屬性是一種原型對象與構造函數的關聯關係,因此修改原型對象的時候,務必要當心。

var Pet = function (name) {
    this.name = name;
}
// 儘可能避免這麼寫,由於會把construct屬性覆蓋掉。
Pet.prototype = {
    say: function () {
        console.log('meow');
    }
}

// 若是咱們覆蓋了constructor屬性要記得將他指回來。
Pet.prototype.constructor = Pet;
複製代碼

__proto__ 屬性和prototype屬性的區別

prototype是function對象中專有的屬性。
__proto__是普通對象的隱式屬性,在new的時候,會指向prototype所指的對象;
__proto__其實是某個實體對象的屬性,而prototype則是屬於構造函數的屬性。
__proto__只能在學習或調試的環境下使用。

這裏抓住兩點:

  • 構造函數經過 prototype 屬性訪問原型對象
  • 實例對象經過 [[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特點的一直實現方式,也是使用最多的方式。

關於面向對象,我想說js 「幾乎一切皆對象」,由於有原型鏈的存在咱們能實現相似其餘語言的繼承。

加上前面一篇,兩篇文章已經涵蓋了js大半部分面試問題了,接下來的文章可能會講解一下單線程模型和計時器相關。這塊是個難點我也是看了好多資料後才搞明白了大概。此次的面試系列主要仍是針對於「中高級前端」,也是一個進階的層次,各位看官不要灰心一切都會有撥雲見日的一天。

女排奪冠了,今年好像好多我關注的時間都有了完美的結局,非常爲他們高興,也但願個人將來也是一個完美的結局,晚安。

請關注個人專欄 《前端雜貨鋪》

參考文章

相關文章
相關標籤/搜索