據說你精通原生JavaScript,來試試這份題目吧

前言

一年經驗僥倖拿到字節跳動Offer,最近面了那麼4,5家,發現不管大公司仍是小公司,都比較注重原生Javascript,而大點的公司框架源碼更是必問。因此我從新看了遍MDN以及其餘的書籍,本身總結出了以下的題目,目的是提高對底層原生知識的瞭解,其實我本身基礎也不是特別紮實,反正寫出來你們能夠一塊兒討論~html

題目

  • 請寫一個函數JudgePropsOnlyInPrototype(obj, key)判斷key這個屬性是否僅僅存在於obj對象的原型上
function JudgePropsOnlyInPrototype(obj, key){
    var hasOwn = Object.hasOwnProperty;
    return (key in obj) && !hasOwn.call(obj,key)
}
複製代碼

解釋: returnin運算符斷定了key是否在對象自己或者其原型上,然後面的!hasOwn則斷定了key不在對象自己上,則2者相結合就可以斷定出key是否只在原型上,注意這裏用了call而不是直接obj.hasOwnProperty(key),這是由於某些對象根本沒有hasOwnProperty這個方法,好比Object.create(null),這個對象徹底純淨和空白,沒有任何原型上的方法和屬性,因此這樣寫健壯性高一些


node

  • 請解釋Js中靜態方法,實例方法,原型方法的區別和使用場景

靜態方法:es6

function Animal(type){
    this.type = type
}
Animal.staticMethod = function(){
    console.log("I'm a static method!")
}
Animal.staticMethod()
複製代碼

簡而言之,靜態方法(static method)就是定義在構造函數上或者類自己上的方法,這類方法沒法經過類的實例來訪問,在方法內部沒法經過this來訪問類內部的變量,那麼你可能會問什麼樣的方法要被定義爲靜態方法,那就是不須要訪問類屬性的方法均可以寫成靜態的,這類方法通常都是 Helper 方法,即對輸入進行處理再獲得一個輸出,與對象的成員無關,好比Math.abs這種就是靜態方法,繼續深刻思考爲何要有靜態方法,緣由我以爲就是節約內存使用量,由於靜態方法能夠全部實例公用,不用再放到每個實例或者其原型上

實例方法:面試

function Animal(type){
    this.type = type
    this.run = function(){
        console.log('run')
    }
}
var animal = new Animal()
animal.run()
複製代碼

上面的run就是實例方法,實例方法就是一個類new出來的實例上的方法,這個方法存在於實例對象自己上,在構造函數中經過this定義,通常來講定義一個方法爲實例方法是要求這個方法是該實例獨有的,不和其餘實例共享,不然就應該定義爲原型方法

原型方法:數組

function Animal(type){
    this.type = type
}
Animal.prototype.run = function(){
    console.log('run')
}
var animal = new Animal()
animal.run()
複製代碼

這個run就是原型方法,經過prototype屬性定義,這個方法不存在於new出來的實例上,只存在於其原型上,實例能夠經過原型鏈訪問原型方法,原型方法的好處在於節約內存,把多個實例公用的方法提取出來放到prototype中去,這也就是爲何要定義原型這個概念bash

因此能夠再對比下靜態方法和原型方法的區別加深理解

閉包

  • 請寫一個函數實現instanceof的功能
function instanceOf(obj,Cons){
    var protoType = Cons.prototype
    var proto = obj.__proto__
    while(proto !== null){
        if(proto === protoType){
            return true
        }
        proto = proto.__proto__
    }
    return false
}
複製代碼

這道題好幾個公司都問了,提問方式是你是否知道instanceof的原理,這道題很重要,由於它包含了原型和原型鏈的知識,面試官不會主動說明這是原型或原型鏈。首先instanceof是一個關鍵詞,用法a instanceof A,左操做數是一個對象,右操做數是一個構造函數,意思就是判斷a是不是A的實例,那麼怎麼判斷呢,就是經過在左操做數a的原型鏈上一步一步往上早,而後依次比對a的__proto__和右操做數的prototype是否相等,若是相等則a是A的實例,直到搜索原型鏈末端null,此時返回false。因此這道題就是考察原型和原型鏈的知識點,另外最好用Object.getPrototypeOf獲取原型,而不是用__proto__

框架

  • 請寫一個函數function getLongestPath(root),該函數的參數是html節點,返回值是dom樹從根節點開始到葉節點的最長路徑,用數組表示,數組中每個元素是dom節點的標籤名
function getLongestPath(root){
    if(!root) return [];
    var tempList = [];
    for(var i=0;i<root.children.length;i++){
    	var ret = getLongestPath(root.children[i]);
    	if(ret.length>tempList.length){
    	    tempList = ret.slice();
    	}
    }
    tempList.push(root.nodeName.toLowerCase());
    return temoList
}
複製代碼

這就是一個遞歸求多叉樹深度的問題,leetcode上有求二叉樹深度的問題,這個就是一個變種,且要求出最長路徑,原理是同樣的,用一個數組保存每一個節點到其葉節點的最長路徑,而後for循環遍歷其全部子節點,不斷更新最大長度,注意不要用childeNodes而是要用children,由於前者包含文本和註釋節點,不在考慮範圍內,另外最好用nodeName代替tagName,由於tagName只適用於元素節點

dom

  • 請解釋什麼是類數組,並舉例說明
var arrayLikeObj = {
    '1':'a',
    '2':'b',
    '3':'c',
    length:3
}
複製代碼

這就是類數組,首先它是一個對象而不是數組,而後擁有length屬性,且其餘的key都是非負整數的字符串,熟知的arguments對象就是類數組,而後document.getElementsByTagName返回值也是類數組函數

類數組轉化爲數組能夠用Array.from或者Array.prototype.slice.call

  • 請在js中實現函數重載
function overload(){
    var args = arguments
    var strategy = {
        '0':function methodA(){},
        '1':function methodB(){},
        '2':function methodC(){}
    }
    return strategy[args.length](...args)
}
複製代碼

其實就是一個策略模式的應用,經過判斷arguments這個對象的長度,也就是參數的個數,而後根據不一樣個數執行對應的方法而已

  • 請在數組的原型上實現push方法
Array.prototype.push = function(){
    for(var i=0;i<arguments.length;i++){
        this[this.length] = arguments[i]
    }
}
複製代碼

這裏注意push能夠有多個參數,因此要for循環遍歷arguments對象,而後this就是調用push時的數組,直接給length那個位置賦值便可擴充數組,注意length會自增,不用再寫this.length++

  • 請用2種方式實現一個函數Log,該函數在執行奇數次時打印1,偶數次時打印2,不能用全局變量
    也就是要達到以下目的
function Log(){...}
Log() //1
Log() //2
Log() //1
Log() //2
複製代碼

答案就是使用閉包和靜態屬性而已,先說閉包,首先題目中說不能使用全局變量,第一反應就是閉包,以下

var Log = (function(){
    var cnt = 1
    return function(){
        if(cnt === 1){
            console.log(1)
            cnt++
        }else{
            console.log(2)
            cnt--
        }
    }
})()
複製代碼

這就是閉包加自執行函數,Log函數就是裏面return的那個function,這個返回的函數引用這外部函數的cnt變量,所以構成閉包結構,cnt沒有被回收且cnt是局部變量,下面說下靜態方法實現

function Log(){
    if(Log.cnt === 1){
        console.log(1)
        cnt++
    }else{
        console.log2)
        cnt--
    }
}
Log.cnt = 1;
複製代碼

這就是static屬性,這個cnt屬性是加在Log自己上的,不是全局變量,且能夠在Log內部訪問到,es6的class要使用static關鍵字定義靜態方法

  • 請解釋Object.create的做用並模擬實現一個該方法 這個方法就是用來建立對象的,函數簽名以下
Object.create(proto, [propertiesObject])
複製代碼

第一個參數是原型對象,第二個參數是一個對象,對象的key是要添加到返回值對象的屬性,value是descriptor對象,用法以下

var proto = {a:1}
var o = Object.create(proto,{p: {
  value: 42,
  writable: true,
  enumerable: true,
  configurable: true
} })
console.log(o)
複製代碼

第一個參數proto是生成的對象的原型對象,即o.__proto__ === proto,而後第二個參數的屬性都加在o對象自己上而不是原型上,這個方法能夠實現繼承,以下

// Shape - 父類(superclass)
function Shape() {
  this.x = 0;
  this.y = 0;
}

// 父類的方法
Shape.prototype.move = function(x, y) {
  this.x += x;
  this.y += y;
  console.info('Shape moved.');
};

// Rectangle - 子類(subclass)
function Rectangle() {
  Shape.call(this); // call super constructor.
}

// 子類續承父類
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

var rect = new Rectangle();
複製代碼

下面模擬實現Object.create

function ObjCreate(proto){
    var f = function(){}
    f.prototype = proto
    return new f()
}
複製代碼

其實很簡單,就是顯示指明f的原型爲proto並返回f的實例對象

  • 最後一題,也是很容易忽略的一點,請說明下面的繼承方法有什麼問題
function Parent() {};
function Son() {}
Son.prototype = Object.create(Parent.prototype);
複製代碼

有2個問題,第一個是這僅僅是基於原型鏈的繼承,Parent構造函數內的東西沒有繼承下來,須要在Son的構造函數內添加Parent.call(this),第二點在於沒有寫以下的代碼

Son.prototype.constructor = Son
複製代碼

這有可能致使問題出現,考慮以下代碼

function Parent() {};
function CreatedConstructor() {}

CreatedConstructor.prototype = Object.create(Parent.prototype);

CreatedConstructor.prototype.create = function create() {
  return new this.constructor();
}

new CreatedConstructor().create().create(); // error undefined is not a function since constructor === Parent
複製代碼

這裏的CreatedConstructor的原型上有一個create方法,先看return new this.construtor()這句話,這種用法很是少見,首先要了解constructor是啥,constructor就是一個屬性,在構造函數的原型上存在,且指向構造函數自己,constructor就是一個構造函數,因此能夠像這麼來調用constructor(),而後它必需要經過this來訪問,最後new操做符說明生成了一個對象,這個對象就是CreatedConstructor的實例,也就是說create方法建立了實例自己,就和new CreateConstrutor()的返回值是同樣的

好,上面的問題在於經過CreatedConstructor.prototype = Object.create(Parent.prototype)這句話進行原型繼承後,CreateConstructor這個構造函數的原型的construtor已經被改寫爲Parent的constructor,那麼咱們來看最後一句話

new CreatedConstructor().create().create()
複製代碼

先看第一個create,這就是new了一個對象並調用其create方法,因爲create方法返回實例自己,所以它上面理應還有create方法能夠調用,可是此時報錯,爲何,就是由於CreateConstructor這個構造函數的原型的construtor已經被改寫爲Parent的constructor,因此返回的是Parent的實例,Parent原型上根本沒有create方法,因此報錯。所以咱們須要加上以下代碼就可以保證CreateConstructor的原型上的構造函數是CreateConstructor自己

CreateConstructor.prototype.constructor = CreateConstructor
複製代碼

像上面這種題目若是出在面試中應該大部分人都答不出來吧,畢竟比較細節

相關文章
相關標籤/搜索