一年經驗僥倖拿到字節跳動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)
}
複製代碼
解釋: return
中in
運算符斷定了key
是否在對象自己或者其原型上,然後面的!hasOwn
則斷定了key
不在對象自己上,則2者相結合就可以斷定出key
是否只在原型上,注意這裏用了call
而不是直接obj.hasOwnProperty(key)
,這是由於某些對象根本沒有hasOwnProperty
這個方法,好比Object.create(null)
,這個對象徹底純淨和空白,沒有任何原型上的方法和屬性,因此這樣寫健壯性高一些
node
靜態方法: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
因此能夠再對比下靜態方法和原型方法的區別加深理解
閉包
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){
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
function overload(){
var args = arguments
var strategy = {
'0':function methodA(){},
'1':function methodB(){},
'2':function methodC(){}
}
return strategy[args.length](...args)
}
複製代碼
其實就是一個策略模式的應用,經過判斷arguments這個對象的長度,也就是參數的個數,而後根據不一樣個數執行對應的方法而已
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++
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(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
複製代碼
像上面這種題目若是出在面試中應該大部分人都答不出來吧,畢竟比較細節