前端工程師面試題(js)

image.png

正值金九銀十的招聘旺季凑阶,我把我珍藏整理多年的前端面試題分享給大家青团,分三部分奶躯。這是第二部分,js相關(guān)的很有用的基礎(chǔ)知識(shí)倡鲸。

1. 如何實(shí)現(xiàn)一個(gè)LazyMan?

1.1 題目

實(shí)現(xiàn)一個(gè)LazyMan树灶,可以按照以下方式調(diào)用:
LazyMan(“Hank”)輸出:
Hi! This is Hank!

LazyMan(“Hank”).sleep(10).eat(“dinner”)輸出
Hi! This is Hank!
//等待10秒..
Wake up after 10
Eat dinner~

LazyMan(“Hank”).eat(“dinner”).eat(“supper”)輸出
Hi This is Hank!
Eat dinner~
Eat supper~

LazyMan(“Hank”).sleepFirst(5).eat(“supper”)輸出
//等待5秒
Wake up after 5
Hi This is Hank!
Eat supper

以此類推纤怒。

這是典型的JavaScript流程控制,問(wèn)題的關(guān)鍵是如何實(shí)現(xiàn)任務(wù)的順序執(zhí)行破托。在Express有一個(gè)類似的東西叫中間件肪跋,這個(gè)中間件和我們這里的吃飯、睡覺(jué)等任務(wù)很類似土砂,每一個(gè)中間件執(zhí)行完成后會(huì)調(diào)用next()函數(shù)州既,這個(gè)函數(shù)用來(lái)調(diào)用下一個(gè)中間件。

對(duì)于這個(gè)問(wèn)題萝映,我們也可以利用相似的思路來(lái)解決吴叶,首先創(chuàng)建一個(gè)任務(wù)隊(duì)列,然后利用next()函數(shù)來(lái)控制任務(wù)的順序執(zhí)行:

1.2 隊(duì)列實(shí)現(xiàn)

function _LazyMan(name){
  this.tasks=[];
  var self=this;
  var fn=(function(n){
    var name=n;
    return function(){
      console.log("Hi! this is "+name+"!");
      self.next();
    }
  })(name);
  this.tasks.push(fn);
  setTimeout(function(){
    self.next();
  },0);  // 在下一個(gè)事件循環(huán)啟動(dòng)任務(wù)
}
/* 事件調(diào)度函數(shù) */
_LazyMan.prototype.next=function(){
  var fn=this.tasks.shift();
  fn && fn();
}
_LazyMan.prototype.eat=function(name){
  var self=this;
  var fn=(function(name){
    return function(){
      console.log("Eat "+name+" ~");
      self.next()
    }
  })(name);
  this.tasks.push(fn);
  return this; // 實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用
}
_LazyMan.prototype.sleep=function(time){
  var self=this;
  var fn=(function(time){
    return function(){
      setTimeout(function(){
        console.log("Wake up after "+time+" s!");
        self.next();
      },time*1000);
    }
  })(time);
  this.tasks.push(fn);
  return this;
}
_LazyMan.prototype.sleepFirst=function(time){
  var self=this;
  var fn=(function(time){
    return function(){
      setTimeout(function(){
        console.log("Wake up after "+time+" s!");
      },time*1000);
    }
  })(time);
  this.tasks.unshift(fn);
  return this;
}
/* 封裝 */
function LazyMan(name){
  return new _LazyMan(name);
}

1.3 promise實(shí)現(xiàn)

lazyman里邊含有鏈?zhǔn)秸{(diào)用序臂,那么每一個(gè)子任務(wù) return this;這個(gè)程序支持任務(wù)優(yōu)先順序蚌卤,那么就需要兩個(gè)貫穿全場(chǎng)的Promise對(duì)象:第一,普通順序promise奥秆;第二逊彭,插入順序promise,同時(shí)插入順序是阻塞普通順序的,代碼如下:

function _LazyMan(name){
  this.orderPromise=this.newPromise(); // 定義順序promise對(duì)象
  this.insertPromise=this.newPromise(); // 定義插入promise對(duì)象
  this.order(function(resolve){
    console.log(name);
    resolve();
  })
}

_LazyMan.prototype={
  /*實(shí)例化promise對(duì)象工廠*/
  newPromise:function(){
    return new Promise(function(resolve,reject){
      resolve();
    })
  },
  order:function(fn){
    var self=this;
    this.orderPromise=this.orderPromise.then(function(){
      return new Promise(function(resolve,reject){
        //如果有insertPromise,阻塞orderPromise.
        self.fir?self.insertPromise.then(function(){
          fn(resolve)
        }):fn(resolve)
      })
    })
  },
  insert:function(fn){
    var self=this;
    this.fir=true;
    this.insertPromise=this.insertPromise.then(function(){
      return new Promise(function(resolve,reject){
        fn(resolve);
        self.fir=false;
      })
    })
  },
  sleepFirst:function(time){
    this.insert(function(resolve){
      setTimeout(function(){
        console.log('wait '+time+' s,other logic');
        resolve();
      },time*1000)
    })
    return this;
  },
  eat:function(something){
    this.order(function(resolve){
      console.log(something+' ~~');
      resolve();
    })
    return this;
  },
  sleep:function(time){
    this.order(function(resolve){
      setTimeout(function(){
        console.log('sleep '+time+' s');
      },time*1000);
    })
    return this;
  }
}

//接口封裝构订。
function LazyMan(name) {
    return new _LazyMan(name);
}
//調(diào)用測(cè)試
LazyMan(‘RoryWu‘).firstTime(1).sleep(2).firstTime(3).eat(‘dinner‘).eat(‘breakfast‘);
// 彈出:
// wait 1 s, other logic
// wait 3 s, other logic
// RoryWu
// sleep 2 s
// dinner~~
// breakfast~~

2. 用JS代碼求出頁(yè)面上一個(gè)元素的最終的background-color侮叮,不考慮IE瀏覽器,不考慮元素float情況悼瘾。

2.1代碼實(shí)例

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style>
    .button {
    height: 2em;
    border: 0;
    border-radius: .2em;
    background-color: #34538b;
    color: #fff;
    font-size: 12px;
    font-weight: bold;
}
  </style>
</head>
<body>
  <input type="button" id="button" class="button" value="點(diǎn)擊我囊榜,顯示背景色" />
  <script>
    document.getElementById("button").onclick = function() {
    var oStyle =window.getComputedStyle(this, null); // null不是必須
    // 如果考慮IE var oStyle = this.currentStyle? this.currentStyle : window.getComputedStyle(this, null);
    alert(oStyle.getPropertyValue("background-color")); //這里也可以用鍵值獲取,建議用getPropertyValue("background-color")
};
  </script>
</body>
</html>

3. setTimeout(fn,0)

3.1 題目

for (var i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i);
    }, 0);
    console.log(i);
}

3.2 題解

結(jié)果是:0 1 2 3 3 3
很多公司面試都愛(ài)出這道題,此題考察的知識(shí)點(diǎn)還是蠻多的亥宿。
為了防止初學(xué)者栽在此問(wèn)題上,此文稍微分析一下曙求。
都考察了那些知識(shí)點(diǎn)呢?
異步怎抛、作用域芽淡、閉包,你沒(méi)聽(tīng)錯(cuò)挣菲,是閉包。
我們來(lái)簡(jiǎn)化此題:

setTimeout(function() {
        console.log(1);
}, 0);
console.log(2);

先打印2椭赋,后打印1。
因?yàn)槭莝etTimeout是異步的或杠。
正確的理解setTimeout的方式(注冊(cè)事件):
有兩個(gè)參數(shù)哪怔,第一個(gè)參數(shù)是函數(shù),第二參數(shù)是時(shí)間值向抢。
調(diào)用setTimeout時(shí)认境,把函數(shù)參數(shù),放到事件隊(duì)列中挟鸠。等主程序運(yùn)行完叉信,再調(diào)用。
沒(méi)啥不好理解的艘希。就像我們給按鈕綁定事件一樣:

btn.onclick = function() {
        alert(1);
};

這么寫(xiě)完硼身,會(huì)彈出1嗎。不會(huì)8蚕怼佳遂!只是綁定事件而已!
必須等我們?nèi)ビ|發(fā)事件撒顿,比如去點(diǎn)擊這個(gè)按鈕丑罪,才會(huì)彈出1。
setTimeout也是這樣的核蘸!只是綁定事件巍糯,等主程序運(yùn)行完畢后啸驯,再去調(diào)用客扎。
setTimeout的時(shí)間值是怎么回事呢?
比如:

setTimeout(fn, 2000)

我們可以理解為2000之后罚斗,再放入事件隊(duì)列中袱吆,如果此時(shí)隊(duì)列為空婶希,那么就直接調(diào)用fn喻杈。如果前面還有其他的事件,那就等待瓷们。
因此setTimeout是一個(gè)約會(huì)從來(lái)都不準(zhǔn)時(shí)的童鞋谬晕。
繼續(xù)看:

setTimeout(function() {
        console.log(i);
}, 0);
var i = 1;

程序會(huì)不會(huì)報(bào)錯(cuò)?
不會(huì)夕玩!而且還會(huì)準(zhǔn)確得打印1燎孟。
為什么揩页?
因?yàn)檎嬲?zhí)行console.log(i)這句代碼時(shí),var i = 1已經(jīng)執(zhí)行完畢了兔仰!
所以我們進(jìn)行dom操作乎赴《鲂颍可以先綁定事件原探,然后再去寫(xiě)其他邏輯。

window.onload = function() {
        fn();
}
var fn = function() {
        alert('hello')
};

這么寫(xiě)离唬,完全是可以的。因?yàn)楫惒剑?/p>

es5中是沒(méi)有塊級(jí)作用域的

for (var i = 0; i < 3; i++) {}
console.log(i);

也就說(shuō)i可以在for循環(huán)體外訪問(wèn)到嫂用。所以是沒(méi)有塊級(jí)作用域嘱函。
但此問(wèn)題在es6里終結(jié)了,因?yàn)閑s6函似,發(fā)明了let撇寞。
這回我們?cè)賮?lái)看看原題。
原題使用了for循環(huán)啤握。循環(huán)的本質(zhì)是干嘛的恨统?
是為了方便我們程序員,少寫(xiě)重復(fù)代碼悠鞍。
讓我們倒退50年咖祭,原題等價(jià)于:

var i = 0;
setTimeout(function() {
    console.log(i);
}, 0);
console.log(i);
i++;
setTimeout(function() {
    console.log(i);
}, 0);
console.log(i);
i++;
setTimeout(function() {
    console.log(i);
}, 0);
console.log(i);
i++;

因?yàn)閟etTimeout是注冊(cè)事件。根據(jù)前面的討論浩嫌,可以都放在后面码耐。
原題又等價(jià)于如下的寫(xiě)法:

var i = 0;
console.log(i);
i++;
console.log(i);
i++;
console.log(i);
i++;
setTimeout(function() {
    console.log(i);
}, 0);
setTimeout(function() {
    console.log(i);
}, 0);
setTimeout(function() {
    console.log(i);
}, 0);

這回你明白了為啥結(jié)果是0 1 2 3 3 3了吧。

那個(gè)束铭,說(shuō)它是閉包纯露,又是怎么回事?
為了很好的說(shuō)明白這個(gè)事情钞速,我們把它放到一個(gè)函數(shù)中:

var fn = function() {
        for (var i = 0; i < 3; i++) {
                setTimeout(function() {
                        console.log(i);
                }, 0);
                console.log(i);
        }
};
fn();

上面的函數(shù)跟我們常見(jiàn)另一個(gè)例子(div綁定事件)有什么區(qū)別:

var fn = function() {
        var divs = document.querySelectorAll('div');
        for (var i = 0; i < 3; i++) {
                divs[i].onclick = function() {
                        alert(i);
                };
        }
};
fn();

點(diǎn)擊每個(gè)div都會(huì)彈出3。道理是一樣的驾凶。因?yàn)閍lert(i)中的i是fn作用越中的调违,因而這是閉包且轨。
《javascript忍者秘籍》書(shū)里把一個(gè)函數(shù)能調(diào)用全局變量旋奢,也稱閉包。
因?yàn)樽髡哒J(rèn)為全局環(huán)境也可以想象成一個(gè)大的頂級(jí)函數(shù)爽丹。
怎么保證能彈出0粤蝎,1, 2呢碑宴。
解決之道:以毒攻毒延柠!
再創(chuàng)建個(gè)閉包!增热!

var fn = function() {
        var divs = document.querySelectorAll('div');
        for (var i = 0; i < 3; i++) {
                divs[i].onclick = (function(i) {
                        return function() {
                                alert(i);
                        };
                })(i);
        }
};
fn();

或者如下的寫(xiě)法:

var fn = function() {
        var divs = document.querySelectorAll('div');
        for (var i = 0; i < 3; i++) {
                (function(i) {
                        divs[i].onclick = function() {
                                alert(i);
                        };
                })(i);
        }
};
fn();

因此原題如果也想setTimeout也彈出0,1摄咆,2的話吭从,改成如下:

for (var i = 0; i < 3; i++) {
    setTimeout((function(i) {
        return function() {
            console.log(i);
        };
    })(i), 0);
    console.log(i);
}

想了解更多關(guān)于setTimeout(fn,0),可以參考setTimeout(fn,0)

4. 原型與繼承及new過(guò)程

4.1 原型和繼承

先看題目:
請(qǐng)用js實(shí)現(xiàn)一個(gè)類P,包含成員變量a鸭廷,成員變量b辆床,成員函數(shù)sum,sum輸出a與b的和咨堤,a一喘,b默認(rèn)值都為0。實(shí)現(xiàn)一個(gè)類M萎战,M繼承自P蚂维,在P的基礎(chǔ)上增加成員變量c,成員函數(shù)sum變成輸出a,b,c的和孝鹊。
題目分析
Js所有的函數(shù)都有一個(gè)prototype屬性又活,這個(gè)屬性引用了一個(gè)對(duì)象团赏,即原型對(duì)象舔清,也簡(jiǎn)稱原型。這個(gè)函數(shù)包括構(gòu)造函數(shù)和普通函數(shù)抒痒,我們講的更多是構(gòu)造函數(shù)的原型故响,但是也不能否定普通函數(shù)也有原型,實(shí)現(xiàn)繼承的方法很多,這里使用原型鏈和構(gòu)造繼承,即組合繼承的方式。

function P(a,b){
  this.a=a||0;
  this.b=b||0;
  this.sum=function(){
    return this.a+this.b;
  }
}
function M(a,b,c){
  P.call(this,a,b)
  this.c=c;
  this.sum=function(){
    return this.a+this.b+this.c;
  }
}
M.prototype=new P();
var m=new M(2,2,2);
m.sum()  //輸出6

接下來(lái),我們深入js繼承,看看js實(shí)現(xiàn)繼承幾種方式的特點(diǎn)惨缆。

4.2 js繼承的實(shí)現(xiàn)方式

既然要實(shí)現(xiàn)繼承,那么首先我們得有一個(gè)父類捣染,代碼如下:

// 定義一個(gè)動(dòng)物類
function Animal (name,eye,skin) {
  // 屬性
  this.name = name || 'Animal';
  this.eye=eye;
  this.skin=skin;
  // 實(shí)例方法
  this.sleep = function(){
    console.log(this.name + '正在睡覺(jué)耍攘!');
  }
}
// 原型方法
Animal.prototype.eat = function(food) {
  console.log(this.name + '正在吃:' + food);
};

下面給大家列出幾種繼承方式的實(shí)現(xiàn)

4.3 原型鏈繼承

實(shí)現(xiàn)父類代碼在(4.2 js繼承的實(shí)現(xiàn)方式中)
核心: 將父類的實(shí)例作為子類的原型

function Cat(){ 
}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.eat('fish'));
console.log(cat.sleep());
console.log(cat instanceof Animal); //true 
console.log(cat instanceof Cat); //true

特點(diǎn):

  1. 非常純粹的繼承關(guān)系,實(shí)例是子類的實(shí)例式曲,也是父類的實(shí)例
  2. 父類新增原型方法/原型屬性兰伤,子類都能訪問(wèn)到
  3. 簡(jiǎn)單敦腔,易于實(shí)現(xiàn)

缺點(diǎn):

  1. 要想為子類新增屬性和方法符衔,必須要在new Animal()這樣的語(yǔ)句之后執(zhí)行判族,不能放到構(gòu)造器中
  2. 無(wú)法實(shí)現(xiàn)多繼承
  3. 來(lái)自原型對(duì)象的引用屬性是所有實(shí)例共享的(詳細(xì)請(qǐng)看附錄代碼)
  4. 創(chuàng)建子類實(shí)例時(shí)颗品,無(wú)法向父類構(gòu)造函數(shù)傳參(即無(wú)法像這樣var cat=new Cat(hair,eye,skin)傳參給父類)

推薦指數(shù):★★(3则吟、4兩大致命缺陷)

4.4 構(gòu)造繼承

核心:使用父類的構(gòu)造函數(shù)來(lái)增強(qiáng)子類實(shí)例氓仲,等于是復(fù)制父類的實(shí)例屬性給子類(沒(méi)用到原型)

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true

特點(diǎn):

  1. 解決了1中,子類實(shí)例共享父類引用屬性的問(wèn)題
  2. 創(chuàng)建子類實(shí)例時(shí)朝抖,可以向父類傳遞參數(shù)(可通過(guò)Animal.call(this,name,eye,skin)或者Animal.apply(this,[name,eye,skin])實(shí)現(xiàn))
  3. 可以實(shí)現(xiàn)多繼承(call多個(gè)父類對(duì)象)

缺點(diǎn):

  1. 實(shí)例并不是父類的實(shí)例急侥,只是子類的實(shí)例
  2. 只能繼承父類的實(shí)例屬性和方法,不能繼承原型屬性/方法
  3. 無(wú)法實(shí)現(xiàn)函數(shù)復(fù)用绊茧,每個(gè)子類都有父類實(shí)例函數(shù)的副本捉超,影響性能

推薦指數(shù):★★(缺點(diǎn)3)

4.5 實(shí)例繼承

核心:為父類實(shí)例添加新特性拼岳,作為子類實(shí)例返回

function Cat(name){
  var instance = new Animal();
  instance.name = name || 'Tom';
  return instance;
}

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // false

特點(diǎn):

  1. 不限制調(diào)用方式叶撒,不管是new 子類()還是子類(),返回的對(duì)象具有相同的效果

缺點(diǎn):

  1. 實(shí)例是父類的實(shí)例,不是子類的實(shí)例
  2. 不支持多繼承

推薦指數(shù):★★

4.6 拷貝繼承

function Cat(name){
  var animal = new Animal();
  for(var p in animal){
    Cat.prototype[p] = animal[p];
  }
  Cat.prototype.name = name || 'Tom';
}

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true

特點(diǎn):

  1. 支持多繼承

缺點(diǎn):

  1. 效率較低古瓤,內(nèi)存占用高(因?yàn)橐截惛割惖膶傩裕?/li>
  2. 無(wú)法獲取父類不可枚舉的方法(不可枚舉方法,不能使用for in 訪問(wèn)到)

推薦指數(shù):★(缺點(diǎn)1)

4.7 組合繼承

核心:通過(guò)調(diào)用父類構(gòu)造绎速,繼承父類的屬性并保留傳參的優(yōu)點(diǎn)纹冤,然后通過(guò)將父類實(shí)例作為子類原型萌京,實(shí)現(xiàn)函數(shù)復(fù)用

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
Cat.prototype = new Animal();

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true

特點(diǎn):

  1. 彌補(bǔ)了方式2的缺陷知残,可以繼承實(shí)例屬性/方法,也可以繼承原型屬性/方法
  2. 既是子類的實(shí)例扒最,也是父類的實(shí)例
  3. 不存在引用屬性共享問(wèn)題
  4. 可傳參
  5. 函數(shù)可復(fù)用

缺點(diǎn):

  1. 調(diào)用了兩次父類構(gòu)造函數(shù),生成了兩份實(shí)例(子類實(shí)例將子類原型上的那份屏蔽了)

推薦指數(shù):★★★★(僅僅多消耗了一點(diǎn)內(nèi)存)

4.8 寄生組合繼承

核心:通過(guò)寄生方式,砍掉父類的實(shí)例屬性,這樣呆细,在調(diào)用兩次父類的構(gòu)造的時(shí)候絮爷,就不會(huì)初始化兩次實(shí)例方法/屬性,避免的組合繼承的缺點(diǎn)

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
(function(){
  // 創(chuàng)建一個(gè)沒(méi)有實(shí)例方法的類
  var Super = function(){};
  Super.prototype = Animal.prototype;
  //將實(shí)例作為子類的原型
  Cat.prototype = new Super();
})();

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true

特點(diǎn):

  1. 堪稱完美

缺點(diǎn):

  1. 實(shí)現(xiàn)較為復(fù)雜

推薦指數(shù):★★★★(實(shí)現(xiàn)復(fù)雜,扣掉一顆星)

4.9 附錄代碼

示例:

function Animal (name) {
  // 屬性
  this.name = name || 'Animal';
  // 實(shí)例方法
  this.sleep = function(){
    console.log(this.name + '正在睡覺(jué)淑履!');
  }
  //實(shí)例引用屬性
  this.features = [];
}
function Cat(name){
}
Cat.prototype = new Animal();

var tom = new Cat('Tom');
var kissy = new Cat('Kissy');

console.log(tom.name); // "Animal"
console.log(kissy.name); // "Animal"
console.log(tom.features); // []
console.log(kissy.features); // []

tom.name = 'Tom-New Name';
tom.features.push('eat');

//針對(duì)父類實(shí)例值類型成員的更改岁疼,不影響
console.log(tom.name); // "Tom-New Name"
console.log(kissy.name); // "Animal"
//針對(duì)父類實(shí)例引用類型成員的更改瑰排,會(huì)通過(guò)影響其他子類實(shí)例
console.log(tom.features); // ['eat']
console.log(kissy.features); // ['eat']

原因分析:

關(guān)鍵點(diǎn):屬性查找過(guò)程

執(zhí)行tom.features.push椭住,首先找tom對(duì)象的實(shí)例屬性(找不到)京郑,
那么去原型對(duì)象中找,也就是Animal的實(shí)例户魏。發(fā)現(xiàn)有关翎,那么就直接在這個(gè)對(duì)象的
features屬性中插入值纵寝。
在console.log(kissy.features); 的時(shí)候。同上闹啦,kissy實(shí)例上沒(méi)有,那么去原型上找琳袄。
剛好原型上有,就直接返回碎紊,但是注意,這個(gè)原型對(duì)象中features屬性值已經(jīng)變化了词爬。

其實(shí)這里主要是理解js值類型和引用類型

4.10 new關(guān)鍵字

假設(shè)已經(jīng)定義了父類Base對(duì)象
我們執(zhí)行如下代碼

var obj = new Base();

這樣代碼的結(jié)果是什么秃嗜,我們?cè)贘avascript引擎中看到的對(duì)象模型是:


interview01.png

new操作符具體干了什么呢?其實(shí)很簡(jiǎn)單,就干了三件事情顿膨。

var obj  = {};
obj.__proto__ = Base.prototype; 
Base.call(obj);  
  • 第一行锅锨,我們創(chuàng)建了一個(gè)空對(duì)象obj
  • 第二行,我們將這個(gè)空對(duì)象的__proto__成員指向了Base函數(shù)對(duì)象prototype成員對(duì)象
  • 第三行恋沃,我們將Base函數(shù)對(duì)象的this指針替換成obj必搞,然后再調(diào)用Base函數(shù)
    注意:new的過(guò)程會(huì)執(zhí)行構(gòu)造函數(shù)Base() 再對(duì)空對(duì)象進(jìn)行構(gòu)造

5. js題(2)

5.1 如何實(shí)現(xiàn)懶加載(跟預(yù)加載的區(qū)別)

5.2 同源,跨域

推薦閱讀 瀏覽器同源政策及其規(guī)避方法

5.3 js有幾種類型值,畫(huà)內(nèi)存圖

棧:原始數(shù)據(jù)類型(Undefined顾画,Null庶诡,Boolean迅栅,Number让簿、String) 
堆:引用數(shù)據(jù)類型(對(duì)象、數(shù)組和函數(shù))

兩種類型的區(qū)別是:存儲(chǔ)位置不同抹估;
原始數(shù)據(jù)類型直接存儲(chǔ)在棧(stack)中的簡(jiǎn)單數(shù)據(jù)段贸典,占據(jù)空間小绳锅、大小固定,屬于被頻繁使用數(shù)據(jù)茂蚓,所以放入棧中存儲(chǔ)怠肋;
引用數(shù)據(jù)類型存儲(chǔ)在堆(heap)中的對(duì)象,占據(jù)空間大、大小不固定,如果存儲(chǔ)在棧中,將會(huì)影響程序運(yùn)行的性能豹储;引用數(shù)據(jù)類型在棧中存儲(chǔ)了指針球及,該指針指向堆中該實(shí)體的起始地址并思。當(dāng)解釋器尋找引用值時(shí)莱坎,會(huì)首先檢索其
在棧中的地址住册,取得地址后從堆中獲得實(shí)體
interview09.png

5.4 通用的事件偵聽(tīng)器函數(shù)

 // event(事件)工具集兑巾,來(lái)源:github.com/markyun
    markyun.Event = {
        // 頁(yè)面加載完成后
        readyEvent : function(fn) {
            if (fn==null) {
                fn=document;
            }
            var oldonload = window.onload;
            if (typeof window.onload != 'function') {
                window.onload = fn;
            } else {
                window.onload = function() {
                    oldonload();
                    fn();
                };
            }
        },
        // 視能力分別使用dom0||dom2||IE方式 來(lái)綁定事件
        // 參數(shù): 操作的元素,事件名稱 ,事件處理程序
        addEvent : function(element, type, handler) {
            if (element.addEventListener) {
                //事件類型院峡、需要執(zhí)行的函數(shù)有滑、是否捕捉
                element.addEventListener(type, handler, false);
            } else if (element.attachEvent) {
                element.attachEvent('on' + type, function() {
                    handler.call(element);
                });
            } else {
                element['on' + type] = handler;
            }
        },
        // 移除事件
        removeEvent : function(element, type, handler) {
            if (element.removeEventListener) {
                element.removeEventListener(type, handler, false);
            } else if (element.datachEvent) {
                element.detachEvent('on' + type, handler);
            } else {
                element['on' + type] = null;
            }
        },
        // 阻止事件 (主要是事件冒泡,因?yàn)镮E不支持事件捕獲)
        stopPropagation : function(ev) {
            if (ev.stopPropagation) {
                ev.stopPropagation();
            } else {
                ev.cancelBubble = true;
            }
        },
        // 取消事件的默認(rèn)行為
        preventDefault : function(event) {
            if (event.preventDefault) {
                event.preventDefault();
            } else {
                event.returnValue = false;
            }
        },
        // 獲取事件目標(biāo)
        getTarget : function(event) {
            return event.target || event.srcElement;
        },
        // 獲取event對(duì)象的引用,取到事件的所有信息,確保隨時(shí)能使用event;
        getEvent : function(e) {
            var ev = e || window.event;
            if (!ev) {
                var c = this.getEvent.caller;
                while (c) {
                    ev = c.arguments[0];
                    if (ev && Event == ev.constructor) {
                        break;
                    }
                    c = c.caller;
                }
            }
            return ev;
        }
    };

5.5 ["1", "2", "3"].map(parseInt) 答案

 [1, NaN, NaN] 因?yàn)?parseInt 需要兩個(gè)參數(shù) (val, radix)活鹰,
 其中 radix 表示解析時(shí)用的基數(shù)攻冷。
 map 傳了 3 個(gè) (element, index, array)拥诡,對(duì)應(yīng)的 radix 不合法導(dǎo)致解析失敗礁苗。

5.6 聲明提升

5.7 那些操作會(huì)造成內(nèi)存泄漏昌粤?

內(nèi)存泄漏指任何對(duì)象在您不再擁有或需要它之后仍然存在壶熏。
垃圾回收器定期掃描對(duì)象屡谐,并計(jì)算引用了每個(gè)對(duì)象的其他對(duì)象的數(shù)量。如果一個(gè)對(duì)象的引用數(shù)量為 0(沒(méi)有其他對(duì)象引用過(guò)該對(duì)象)稠茂,或?qū)υ搶?duì)象的惟一引用是循環(huán)的,那么該對(duì)象的內(nèi)存即可回收犀概。

setTimeout 的第一個(gè)參數(shù)使用字符串而非函數(shù)的話曾沈,會(huì)引發(fā)內(nèi)存泄漏。
閉包九秀、控制臺(tái)日志系羞、循環(huán)(在兩個(gè)對(duì)象彼此引用且彼此保留時(shí)仁堪,就會(huì)產(chǎn)生一個(gè)循環(huán))

5.8 Polyfill

polyfill 是“在舊版瀏覽器上復(fù)制標(biāo)準(zhǔn) API 的 JavaScript 補(bǔ)充”,可以動(dòng)態(tài)地加載 JavaScript 代碼或庫(kù)哮洽,在不支持這些標(biāo)準(zhǔn) API 的瀏覽器中模擬它們。
例如弦聂,geolocation(地理位置)polyfill 可以在 navigator 對(duì)象上添加全局的 geolocation 對(duì)象鸟辅,還能添加 getCurrentPosition 函數(shù)以及“坐標(biāo)”回調(diào)對(duì)象,
所有這些都是 W3C 地理位置 API 定義的對(duì)象和函數(shù)横浑。因?yàn)?polyfill 模擬標(biāo)準(zhǔn) API剔桨,所以能夠以一種面向所有瀏覽器未來(lái)的方式針對(duì)這些 API 進(jìn)行開(kāi)發(fā),
一旦對(duì)這些 API 的支持變成絕對(duì)大多數(shù)徙融,則可以方便地去掉 polyfill洒缀,無(wú)需做任何額外工作。

做的項(xiàng)目中欺冀,有沒(méi)有用過(guò)或自己實(shí)現(xiàn)一些 polyfill 方案(兼容性處理方案)树绩?

比如: html5shiv、Geolocation隐轩、Placeholder 

5.9 關(guān)于arguments

1. 定義
由于JavaScript允許函數(shù)有不定數(shù)目的參數(shù)饺饭,所以我們需要一種機(jī)制,可以在函數(shù)體內(nèi)部讀取所有參數(shù)职车。這就是arguments對(duì)象的由來(lái)瘫俊。

arguments對(duì)象包含了函數(shù)運(yùn)行時(shí)的所有參數(shù)鹊杖,arguments[0]就是第一個(gè)參數(shù),arguments[1]就是第二個(gè)參數(shù)扛芽,以此類推骂蓖。這個(gè)對(duì)象只有在函數(shù)體內(nèi)部,才可以使用川尖。

var f = function(one) {
  console.log(arguments[0]);
  console.log(arguments[1]);
  console.log(arguments[2]);
}

f(1, 2, 3)
// 1
// 2
// 3

arguments對(duì)象除了可以讀取參數(shù)登下,還可以為參數(shù)賦值(嚴(yán)格模式不允許這種用法)

var f = function(a, b) {
  arguments[0] = 3;
  arguments[1] = 2;
  return a + b;
}

f(1, 1)
// 5

可以通過(guò)arguments對(duì)象的length屬性,判斷函數(shù)調(diào)用時(shí)到底帶幾個(gè)參數(shù)叮喳。

function f() {
  return arguments.length;
}

f(1, 2, 3) // 3
f(1) // 1
f() // 0

2. 與數(shù)組的關(guān)系
需要注意的是被芳,雖然arguments很像數(shù)組,但它是一個(gè)對(duì)象馍悟。數(shù)組專有的方法(比如slice和forEach)畔濒,不能在arguments對(duì)象上直接使用。

但是赋朦,可以通過(guò)apply方法篓冲,把a(bǔ)rguments作為參數(shù)傳進(jìn)去,這樣就可以讓arguments使用數(shù)組方法了宠哄。

// 用于apply方法
myfunction.apply(obj, arguments).

// 使用與另一個(gè)數(shù)組合并
Array.prototype.concat.apply([1,2,3], arguments)

要讓arguments對(duì)象使用數(shù)組方法,真正的解決方法是將arguments轉(zhuǎn)為真正的數(shù)組嗤攻。下面是兩種常用的轉(zhuǎn)換方法:slice方法和逐一填入新數(shù)組毛嫉。

var args = Array.prototype.slice.call(arguments);

// or

var args = [];
for (var i = 0; i < arguments.length; i++) {
  args.push(arguments[i]);
}

3. callee屬性
arguments對(duì)象帶有一個(gè)callee屬性,返回它所對(duì)應(yīng)的原函數(shù)妇菱。

var f = function(one) {
  console.log(arguments.callee === f);
}

f() // true

可以通過(guò)arguments.callee承粤,達(dá)到調(diào)用函數(shù)自身的目的。這個(gè)屬性在嚴(yán)格模式里面是禁用的闯团,因此不建議使用辛臊。

4. 題目sum(2)(3)

// 寫(xiě)一個(gè) function 讓下面兩行代碼輸出的結(jié)果都為 5
console.log(sum(2, 3));
console.log(sum(2)(3));

說(shuō)實(shí)話,第一眼看到的時(shí)候心里是有點(diǎn)虛的(因?yàn)榈谝淮慰吹剿┓拷弧um(2)(3)彻舰,這種形式的代碼確實(shí)少見(jiàn)。但是第一反應(yīng)就是鏈?zhǔn)秸{(diào)用候味。
鏈?zhǔn)秸{(diào)用我們熟悉啊刃唤,特別是 jQuery 里面,我們常常能看到連著寫(xiě)的代碼白群。實(shí)現(xiàn)原理就是在方法結(jié)束時(shí) return 合適的元素對(duì)象尚胞。

$('#id').parent().siblings('selector').css({
    color: 'red'
});

這道題考什么呢?認(rèn)真分析了一下帜慢,應(yīng)該有鏈?zhǔn)秸{(diào)用笼裳,toString唯卖,柯里化,數(shù)組操作等相關(guān)內(nèi)容躬柬。大概這些可以滿足需求吧耐床?
如何寫(xiě)代碼,腦海中大體上有構(gòu)思了楔脯,但是當(dāng)時(shí)手上僅有筆和紙撩轰,思路連不上來(lái)啊。還好面前放著一臺(tái)臺(tái)式機(jī)(嘿嘿嘿昧廷,機(jī)器上寫(xiě)完再抄回紙上)

我的實(shí)現(xiàn)大概是這樣的堪嫂。

var sum = (function() {
    var list = [];

    var add = function() {
        // 拼接數(shù)組
        var args = Array.prototype.slice.call(arguments);
        list = list.concat(args);
        return add;
    }
    // 覆蓋 toString 方法
    add.toString = function() {
        // 計(jì)算總和
        var sum = list.reduce(function(pre, next) {
            return pre + next;
        });
        // 清除記錄
        list.length = 0;
        return sum;
    }

    return add;
})();

sum(2, 3);
// 5
sum(2)(3);
// 5

這個(gè)方法比較復(fù)雜,下面介紹個(gè)簡(jiǎn)便的木柬。

var add = function add() {
    var cache;
    if (arguments.length === 1) {
        cache = arguments[0];
        return function ( number ) {return cache + number;};
    }
    else return arguments[0] + arguments[1];
};

6. js淺復(fù)制和深復(fù)制

6.1 js淺復(fù)制

簡(jiǎn)單的淺復(fù)制實(shí)現(xiàn):

var obj={ a:1,arr:[2,3] };
var shallowObj=shallowCopy(obj);

function shallowCopy(src){
  var dst = {};
  for (var prop in src){
    if(src.hasOwnProperty(prop)){
      dst[prop]=src[prop];
    }
  }

  return dst;
}

因?yàn)闇\復(fù)制只會(huì)將對(duì)象的各個(gè)屬性進(jìn)行依次復(fù)制皆串,并不會(huì)進(jìn)行遞歸復(fù)制,而 JavaScript 存儲(chǔ)對(duì)象都是存地址的眉枕,所以淺復(fù)制會(huì)導(dǎo)致 obj.arr 和 shadowObj.arr 指向同一塊內(nèi)存地址恶复,大概的示意圖如下。


interview10

導(dǎo)致的結(jié)果就是:

shadowObj.arr[1] = 5;
obj.arr[1]   // = 5

6.2 js深復(fù)制

而深復(fù)制則不同速挑,它不僅將原對(duì)象的各個(gè)屬性逐個(gè)復(fù)制出去谤牡,而且將原對(duì)象各個(gè)屬性所包含的對(duì)象也依次采用深復(fù)制的方法遞歸復(fù)制到新對(duì)象上。這就不會(huì)存在上面 obj 和 shadowObj 的 arr 屬性指向同一個(gè)對(duì)象的問(wèn)題姥宝。

var obj = { a:1, arr: [1,2] };
var obj2 = deepCopy(obj);

結(jié)果如下面的示意圖所示:


interview11

需要注意的是翅萤,如果對(duì)象比較大,層級(jí)也比較多腊满,深復(fù)制會(huì)帶來(lái)性能上的問(wèn)題套么。在遇到需要采用深復(fù)制的場(chǎng)景時(shí),可以考慮有沒(méi)有其他替代的方案碳蛋。在實(shí)際的應(yīng)用場(chǎng)景中胚泌,也是淺復(fù)制更為常用。

代碼遞歸實(shí)現(xiàn)如下:

function deepCopy(o,c){
  var c = c || {}
  for(var i in o){
    if(typeof o[i] == 'object'){
      if(o[i].constructor===Array){
        c[i]=[]
      }else{
        c[i]={}
      }
      deepCopy(o[i],c[i])
    }else{
      c[i]=o[i]
    }
  }
  return c
}

7. jquery題

7.1 jquery與jquery UI

  • jQuery是一個(gè)js庫(kù)肃弟,主要提供的功能是選擇器玷室,屬性修改和事件綁定等等。
  • jQuery UI則是在jQuery的基礎(chǔ)上愕乎,利用jQuery的擴(kuò)展性阵苇,設(shè)計(jì)的插件。
    提供了一些常用的界面元素感论,諸如對(duì)話框绅项、拖動(dòng)行為、改變大小行為等等

7.2 jquery擴(kuò)展

jquery 中如何將數(shù)組轉(zhuǎn)化為json字符串比肄,然后再轉(zhuǎn)化回來(lái)快耿?

   $.fn.stringifyArray = function(array) {
        return JSON.stringify(array)
    }

    $.fn.parseArray = function(array) {
        return JSON.parse(array)
    }

    然后調(diào)用:
    $("").stringifyArray(array)

7.3 jquery的優(yōu)化方法

針對(duì) jQuery 的優(yōu)化方法囊陡?

*基于Class的選擇性的性能相對(duì)于Id選擇器開(kāi)銷很大,因?yàn)樾璞闅v所有DOM元素掀亥。

*頻繁操作的DOM撞反,先緩存起來(lái)再操作。用Jquery的鏈?zhǔn)秸{(diào)用更好搪花。
 比如:var str=$("a").attr("href");

*for (var i = size; i < arr.length; i++) {}
 for 循環(huán)每一次循環(huán)都查找了數(shù)組 (arr) 的.length 屬性遏片,在開(kāi)始循環(huán)的時(shí)候設(shè)置一個(gè)變量來(lái)存儲(chǔ)這個(gè)數(shù)字,可以讓循環(huán)跑得更快:
 for (var i = size, length = arr.length; i < length; i++) {}

轉(zhuǎn)載請(qǐng)注明

極客教程-極客教程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末撮竿,一起剝皮案震驚了整個(gè)濱河市吮便,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌幢踏,老刑警劉巖髓需,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異房蝉,居然都是意外死亡僚匆,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)搭幻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)咧擂,“玉大人,你說(shuō)我怎么就攤上這事粗卜∥萑罚” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵续扔,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我焕数,道長(zhǎng)纱昧,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任堡赔,我火速辦了婚禮识脆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘善已。我一直安慰自己灼捂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布换团。 她就那樣靜靜地躺著悉稠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪艘包。 梳的紋絲不亂的頭發(fā)上的猛,一...
    開(kāi)封第一講書(shū)人閱讀 52,441評(píng)論 1 310
  • 那天耀盗,我揣著相機(jī)與錄音,去河邊找鬼卦尊。 笑死叛拷,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的岂却。 我是一名探鬼主播忿薇,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼躏哩!你這毒婦竟也來(lái)了署浩?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤震庭,失蹤者是張志新(化名)和其女友劉穎瑰抵,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體器联,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡二汛,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了拨拓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肴颊。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖渣磷,靈堂內(nèi)的尸體忽然破棺而出婿着,到底是詐尸還是另有隱情,我是刑警寧澤醋界,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布竟宋,位于F島的核電站,受9級(jí)特大地震影響形纺,放射性物質(zhì)發(fā)生泄漏丘侠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一逐样、第九天 我趴在偏房一處隱蔽的房頂上張望蜗字。 院中可真熱鬧,春花似錦脂新、人聲如沸挪捕。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)级零。三九已至,卻和暖如春始花,著一層夾襖步出監(jiān)牢的瞬間妄讯,已是汗流浹背孩锡。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留亥贸,地道東北人躬窜。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像炕置,于是被迫代替她去往敵國(guó)和親荣挨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 工廠模式類似于現(xiàn)實(shí)生活中的工廠可以產(chǎn)生大量相似的商品朴摊,去做同樣的事情默垄,實(shí)現(xiàn)同樣的效果;這時(shí)候需要使用工廠模式。簡(jiǎn)單...
    舟漁行舟閱讀 7,777評(píng)論 2 17
  • 請(qǐng)參看我github中的wiki甚纲,不定期更新口锭。https://github.com/ivonzhang/Front...
    zhangivon閱讀 7,133評(píng)論 2 19
  • 在線閱讀 http://interview.poetries.top[http://interview.poetr...
    程序員poetry閱讀 114,425評(píng)論 24 450
  • 剛?cè)氪笕磺卸甲兊么胧植患啊?每天醒來(lái)總是陷入迷茫之中介杆,我是要實(shí)習(xí)去嗎鹃操,考不考研,我們專業(yè)不好考啊春哨,英語(yǔ)不好啊荆隘,...
    趙白白閱讀 128評(píng)論 0 1
  • 每個(gè)人都在以自己的方式悄悄的努力 每個(gè)人都在以自己的方式默默改變 有時(shí)候 你不言 我不語(yǔ) 不問(wèn)征程 只問(wèn)歸程 女孩...
    愛(ài)新覺(jué)羅可欣閱讀 145評(píng)論 0 1