1. 程式人生 > >前端工作面試問題(下)

前端工作面試問題(下)

JS相關問題:

  • 解釋下事件代理。

   在傳統的事件處理中,你按照需要為每一個元素新增或者是刪除事件處理器。然而,事件處理器將有可能導致記憶體洩露或者是效能下降——你用得越多這種風險就越大。JavaScript事件代理則是一種簡單的技巧,通過它你可以把事件處理器新增到一個父級元素上,這樣就避免了把事件處理器新增到多個子級元素上

  事件代理用到了兩個在JavaSciprt事件中常被忽略的特性:事件冒泡以及目標元素。當一個元素上的事件被觸發的時候,比如說滑鼠點選了一個按鈕,同樣的事件將會在那個元素的所有祖先元素中被觸發。這一過程被稱為事件冒泡;這個事件從原始元素開始一直冒泡到DOM樹的最上層

。任何一個事件的目標元素都是最開始的那個元素,在我們的這個例子中也就是按鈕,並且它在我們的元素物件中以屬性的形式出現。使用事件代理,我們可以把事件處理器新增到一個元素上,等待一個事件從它的子級元素裡冒泡上來,並且可以得知這個事件是從哪個元素開始的

1 // 獲取父節點,併為它新增一個click事件
2 document.getElementById("parent-list").addEventListener("click",function(e) {
3   // 檢查事件源e.targe是否為Li
4   if(e.target && e.target.nodeName.toUpperCase == "LI") {
5 // 真正的處理過程在這裡 6 console.log("List item ",e.target.id.replace("post-")," was clicked!"); 7 } 8 });

  這樣做的好處:那些需要建立的以及駐留在記憶體中的事件處理器少了,這樣我們就提高了效能,並降低了崩潰的風險。在DOM更新後無須重新繫結事件處理器了。如果你的頁面是動態生成的,比如說通過Ajax,你不再需要在元素被載入或者解除安裝的時候來新增或者刪除事件處理器了。 

  • 解釋下 JavaScript 中 this 是如何工作的。

  this 永遠指向函式執行

時所在的物件,而不是函式被建立時所在的物件。匿名函式或不處於任何物件中的函式指向 window 。函式中的this的值取決於函式呼叫的模式

方法呼叫模式

當函式被儲存為物件的一個屬性時,成該函式為該物件的方法。函式中this的值為該物件

var foo = {
    name: 'fooname',
    getName: function (){
        return this.name  
    }
}
foo.getName();  // this => foo

函式呼叫模式
當函式並不是物件的屬性。函式中this的值為全域性物件
note:某個方法中的內部函式中的this的值也是全域性物件,而非外部函式的this

function foo(){
    this.name = 'fooname';  
}
foo();  // this => window

構造器呼叫模式
即使用new呼叫的函式,則其中this將會被繫結到那個新構造的物件。構造器呼叫將一個全新的物件作為this變數的值,並隱式返回這個新物件作為呼叫結果。

function Foo(){
    this.name = 'fooname';
}
var foo = new Foo();  // this => foo

如果你不是使用new來呼叫構造器,那其實你就是在使用一個實函式。因此this就不會是你預期的值。在Sloppy模式中,this 指向的就是window 而你將會建立全域性變數。不過如果使用的是strict模式,那你還是會得到警告(this===undefined)。

使用apply或call呼叫模式
該模式呼叫時,函式中this被繫結到apply或call方法呼叫時接受的第一個引數。

function getName(name){
    this.name = name;
}
var foo = {};
getName.call(foo, name);  // this =>foo

在方法中this 的用法更傾向於傳統的面嚮物件語言:this指向的接收方,也就是包含有這個方法的物件。

 箭頭函式就是沒有自己的this的函式。在這樣的函式中你可以隨便使用this,也不用擔心有沒有隱式的存在。下面提供了三種思路來解決這個問題:

1)that=this,將this 賦值到一個變數上,這樣就把this顯性地表現出來了(除了that,self 也是個很常見的用於存放this的變數名),之後就使用那個變數。

2)bind()。使用bind()來建立一個函式,這個函式的this 總是存有你想要傳遞的值(下面這個例子中,方法的this):

this.friends.forEach(function (friend) {
        console.log(this.name+' knows '+friend);
    }.bind(this));

3)用forEach的第二個引數。forEach的第二個引數會被傳入回撥函式中,作為回撥函式的this來使用。

this.friends.forEach(function (friend) {
        console.log(this.name+' knows '+friend);
    }, this);
  • 解釋下原型繼承的原理。

   在javascript中,類(定義類是模組開發和重用程式碼的有效方式之一)的實現是基於其原型繼承機制的。如果兩個例項都從一個原型物件上繼承了屬性,我們說它們是同一個類的例項。如果兩個物件繼承自同一個原型,往往意味著(但不是絕對)它們是由同一個建構函式建立並初始化的

  1)類和物件

在javascript中,類的所有例項物件都從一個型別物件上繼承屬性。因此,原型物件是類的核心。

  2)類和建構函式

建構函式是用來初始化和建立物件的。使用new關鍵字來呼叫建構函式,建立一個新物件。呼叫建構函式的一個重要特徵是,建構函式的prototype屬性被用做新物件的原型(var  object.prototype = new Object())。這意味著通過同一個建構函式建立的物件都是繼承自一個相同的物件,因此它們都是一個類的成員。

javascript中的類牽扯三種不同的物件,三種物件的屬性的行為和下面三種類成員非常相似:

    建構函式物件
         Js所有的函式都有一個prototype屬性,這個屬性引用了一個物件,即原型物件,也簡稱原型。這個函式包括建構函式和普通函式,判斷一個函式F是否是Object物件的例項:F.prototype instanceof Object //->true

    原型物件屬性被類的所有例項所繼承,如果原型物件的屬性值是函式的話,這個函式就作為類的例項方法來呼叫
    例項物件,類的每個例項物件都是一個獨立的物件,直接 給這個例項定義的屬性是不會為所有例項物件鎖共享的。定義在例項上的非函式屬性,實際上是例項的欄位。

在javascript中定義類的步奏可以縮減為一個分三步的演算法。第一步,先定義一個建構函式,並設定初始化新物件的例項屬性。第二步,給建構函式的prototype物件定義例項的方法。第三步:給建構函式定義類欄位和類屬性。

   (面試官可能會問到“javascript繼承和其他語言繼承的區別”,可以從基於物件和訪問修飾符分析)

javascript中基於原型的繼承機制是動態的:物件從其原型繼承屬性,如果建立物件之後原型的屬性發生改變,也會影響到繼承這個原型的所有例項物件。這意味著我們可以通過給原型物件新增新的方法來擴充javascript類

  • 你是如何測試 JavaScript 程式碼的?

   2)chrome的單步除錯功能

    JavaScript 斷點設定和除錯功能和java編輯工具的除錯方法類似

  Sources Panel 的左邊是內容源,包括頁面中的各種資源;中間主區域用於展示左邊資原始檔的內容;右邊是除錯功能區,最上面的一排按鈕分別是暫停/繼續、單步執行、單步跳入、單步跳出、禁用/啟用所有斷點。滑鼠點選檔案的行號就可以設定和刪除斷點。新增的每個斷點都會出現在右側除錯區的 Breakpoints 列表中,點選列表中斷點就會定位到內容區的斷點上。Call Stack 列表的下方是 Scope Variables 列表,在這裡可以檢視此時區域性變數和全域性變數的值。
  • *AMD vs. CommonJS?

CommonJs 是伺服器端模組的規範,Node.js採用了這個規範。根據CommonJS規範,一個單獨的檔案就是一個模組。載入模組使用require方法,該方法讀取一個檔案並執行,最後返回檔案內部的exports物件。

//require方法預設讀取js檔案,所以可以省略js字尾
var test = require('./boobar').foobar;
 //test為boobar檔案中的foobar物件的例項
test.bar();
//呼叫方法bar()

CommonJS 載入模組是同步的,所以只有載入完成才能執行後面的操作。像Node.js主要用於伺服器的程式設計,載入的模組檔案一般都已經存在本地硬碟,所以載入起來比較快,不用考慮非同步載入的方式,所以CommonJS規範比較適用。 

AMD  CMD 採用非同步模式,方便瀏覽器環境要從伺服器載入模組。AMD 是 RequireJS 在推廣過程中對模組定義的規範化產出。AMD非同步載入模組。它的模組支援物件、函式、構造器、字串、JSON等各種型別的模組。適用AMD規範適用define方法定義模組。

//通過陣列引入依賴 ,回撥函式通過形參傳入依賴
define(['someModule1', ‘someModule2’], function (someModule1, someModule2) {
    function foo () {
        /// something
        someModule1.test();
    }
    return {foo: foo}
});

本題內容整理自:http://my.oschina.net/felumanman/blog/263330?p=1

  • 什麼是雜湊表?

   類比陣列,陣列是程式設計上的雜湊表,雜湊表是一種資料結構,關鍵點就是用一個key值來取對應資料,就像陣列的下標。

  • 解釋下為什麼接下來這段程式碼不是 IIFE(立即呼叫的函式表示式):function foo(){ }();.

   而函式定義(語句以function關鍵字開始)是不能被立即執行的,這無疑會導致語法的錯誤(SyntaxError)。當函式定義程式碼段包裹在括號內,使解析器可以將之識別為函式表示式,然後呼叫。IIFE:(function foo(){})()

之前面試,遇到問題,區分(function(){})();和(function(){}());其實兩者實現效果一樣。
函式字面量:首先宣告一個函式物件,然後執行它。(function () { alert(1); })(); 優先表示式:由於Javascript執行表示式是從圓括號裡面到外面,所以可以用圓括號強制執行宣告的函式。(function () { alert(2); }());
  • 描述以下變數的區別:null,undefined 或 undeclared?

    'undefined'是未定義,在變數沒有賦值的時候的值即為undefined。"缺少值",就是此處應該有一個值,但是還沒有定義。

  'underclared'即為被汙染的命名,訪問沒有被宣告的變數,會丟擲異常,終止執行。

  'null'是一個空的物件引用。"沒有物件",即該處不應該有值

  undefined和null在if語句中,都會被自動轉為false,相等運算子甚至直接報告兩者相等。typeof undefined會返回undefined ,而typeof null 總返回 object(typeof有六種可能:"number"、"string"、"boolean"、"object"、"function"、"undefined")

false == undefined;//false
false == null;//false
null == undefined;//true
  • 什麼是閉包,如何使用它,為什麼要使用它?

當某個函式呼叫時會建立一個執行環境以及作用域鏈,然後根據arguments和其它命名引數初始化形成活動物件。在外部函式呼叫結束後,其執行環境與作用域鏈被銷燬,但是其活動物件儲存在了閉包之中,最後在閉包函式呼叫結束後才銷燬。簡單的說,閉包就是能夠讀取其他函式內部變數的函式。

由於在Javascript語言中,只有函式內部的子函式才能讀取區域性變數,因此可以把閉包簡單理解成“定義在一個函式內部的函式”。

注意:

1)由於閉包會使得函式中的變數都被儲存在記憶體中,記憶體消耗很大,所以不能濫用閉包,否則會造成網頁的效能問題,在IE中可能導致記憶體洩露。解決方法是,在退出函式之前,將不使用的區域性變數全部刪除。

2)閉包會在父函式外部,改變父函式內部變數的值。所以,如果你把父函式當作物件(object)使用,把閉包當作它的公用方法(Public Method),把內部變數當作它的私有屬性(private value),這時一定要小心,不要隨便改變父函式內部變數的值。

 閉包可以用在許多地方。它的最大用處有兩個,一個是前面提到的可以讀取函式內部的變數,另一個就是讓這些變數的值始終保持在記憶體中。

用法

//第1種寫法  :這種寫法沒什麼特別的,只是給函式新增一些屬性。
function Circle(r) {   this.r = r;  }  
Circle.PI = 3.14159;  
Circle.prototype.area = function() {   return Circle.PI * this.r * this.r;  }  
var c = new Circle(1.0);     
alert(c.area());   

 
//第2種寫法  :這種寫法是宣告一個變數,將一個函式當作值賦給變數。
var Circle = function() {  
   var obj = new Object();  
   obj.PI = 3.14159;  
   obj.area = function( r ) {    return this.PI * r * r;     }  
   return obj;  
}    
var c = new Circle();  
alert( c.area( 1.0 ) );  

 
//第3種寫法  :這種方法最好理解,就是new 一個物件,然後給物件新增屬性和方法。
var Circle = new Object();  
Circle.PI = 3.14159;  
Circle.Area = function( r ) {      return this.PI * r * r;  }  
alert( Circle.Area( 1.0 ) );  

 
//第4種寫法  :這種方法使用較多,也最為方便。var obj = {}就是宣告一個空的物件。
var Circle={  
   "PI":3.14159,  
 "area":function(r){   return this.PI * r * r;  }  
};  
alert( Circle.area(1.0) );  

閉包的用途    

事實上,通過使用閉包,我們可以做很多事情。比如模擬面向物件的程式碼風格;更優雅,更簡潔的表達出程式碼;在某些方面提升程式碼的執行效率

1)匿名自執行函式

  全域性物件過於龐大,影響訪問速度(因為變數的取值是需要從原型鏈上遍歷的)

  除了每次使用變數都是用var關鍵字外,我們在實際情況下經常遇到這樣一種情況,即有的函式只需要執行一次,其內部變數無需維護,我們可以使用閉包。

我們建立了一個匿名的函式,並立即執行它,由於外部無法引用它內部的變數,因此在函式執行完後會立刻釋放資源,關鍵是不汙染全域性物件

2)結果快取

我們開發中會碰到很多情況,設想我們有一個處理過程很耗時的函式物件,每次呼叫都會花費很長時間,那麼我們就需要將計算出來的值儲存起來,當呼叫這個函式的時候,首先在快取中查詢,如果找不到,則進行計算,然後更新快取並返回值,如果找到了,直接返回查詢到的值即可。閉包正是可以做到這一點,因為它不會釋放外部的引用,從而函式內部的值可以得以保留。

3)封裝

4)實現類和繼承

  • 你喜歡的使用閉包的模式是什麼?兩種模式用在不同場合。參見jQuery原始碼,立即呼叫模式,把$的jQuery原始碼放在了全域性作用域下。返回函式型別的,製作一個隨時可以使用的函式。
  • 請舉出一個匿名函式的典型用例?

   $.("input").each(function(e){this.val('OK')});

  • ------------------------------解釋 “JavaScript 模組模式” 以及你在何時使用它。

   我們在做radf庫的時候,把所有的函式寫在var function =  radf(){}裡,為的是在全域性作用域下,只有一個radf物件,所有的屬性和方法全在radf名稱空間下面。這樣就是一個無汙染的環境。

    • 如果有提到無汙染的名稱空間,可以考慮加分。
    • 如果你的模組沒有自己的名稱空間會怎麼樣?
    • 與其它庫或內容造成衝突。
  • 如果有提到無汙染的名稱空間,可以考慮加分。
  • 如果你的模組沒有自己的名稱空間會怎麼樣?
  • 你是如何組織自己的程式碼?是使用模組模式,還是使用經典繼承的方法?

   在模組模式中使用繼承。例如我們的庫中有pannel佈局型元件和data型元件,其餘都依照這兩個基本元件繼承而來。

  • 請指出 JavaScript 宿主物件和原生物件的區別? 

  原生物件,獨立於宿主環境的 ECMAScript 實現提供的物件。為array obj regexp date function等可以new例項化的物件。

  內建物件為gload Math 等,開發者不必明確例項化內建物件,它已被例項化了。類似於isNaN()、parseInt()和parseFloat()方法等,看起來都是函式,而實際上,它們都是Global物件的方法。具體可以參考 JavaScript 全域性物件

  宿主物件即由 ECMAScript 實現的宿主環境(作業系統和瀏覽器)提供的物件。所有的BOM和DOM物件都是宿主物件。因為其對於不同的“宿主”環境所展示的內容不同(這就是相容性和特性檢測的緣由)。ECMAScript官方未定義的物件都屬於宿主物件。

  • 指出下列程式碼的區別:

  function Person(){}

  var person = Person();

  var person = new Person();

1、定義一個函式為Person()  
2、定義一個匿名函式指向person  
3、例項化一個person、原型來自於函式Person

  • .call 和 .apply 的區別是什麼?   

  call和apply都是呼叫一個物件的一個方法,以另一個物件替換當前物件。它們都屬於Function.prototype的一個方法,所以每個function例項都有call和apply屬性。這兩個方法可以用來代替另一個物件呼叫一個方法,可將一個函式的物件上下文從初始的上下文改變為由 thisObj 指定的新物件。

  區別在於,兩者傳遞的引數不同,雖然函式第一個引數都是要傳入給當前物件的物件,但是,apply的第二個引數是一個引數陣列,將多個引數組合成為一個數組傳入;而call第二個引數則是直接的引數列表。

  • 請解釋 Function.prototype.bind?

 Function.prototype.bind()其實就是函式繫結。函式的接收者取決於他是如何被呼叫,可以通過呼叫.bind()給函式繫結作用域上下文,即函式的接收者。

var foo = { x: 3} 
var bar = function(){console.log(this.x);}
 
bar(); // undefined
var boundFunc = bar.bind(foo);//隱式看作是在foo作用域裡呼叫bar方法
boundFunc(); // 3

我們建立了一個新的函式,當它被執行的時候,它的 this 會被設定成 foo —— 而不是像我們呼叫 bar() 時的全域性作用域。

對於改變上下文作用域(具體可以檢視上題“解釋下 JavaScript 中 this 是如何工作的”),可以將this設定到一個變數上,這樣改變了上下文之後繼續引用到它。同時可以選擇 self, _this 或者 context 作為變數名稱(也有人使用 that)。

.bind()建立了一個函式,當這個函式在被呼叫的時候,它的 this 關鍵詞會被設定成被傳入的值(這裡指呼叫bind()時傳入的引數)也就是我們傳入想要的上下文。

簡單的用法:

關於 Function.prototype.bind() 內部,這裡有個非常簡單的例子:

Function.prototype.bind = function (scope) {
    var fn = this;
    return function () {
        return fn.apply(scope);//使用call效果一樣
    };
}
  • 你何時優化自己的程式碼?

  優化程式碼是在不改變程式行為的基礎上進行小的改動,是程式碼逐漸改善的過程。移除長期累積下來的爛碼,以得到更清晰和更容易維護,除錯以及新增新功能的程式碼,這做法不能單純只出現在編碼的後期,甚至是你意識到你的程式碼已經無從再下手非重寫不可的時候,而是從開始開發起,逐漸積累,逐漸修改。以前因為日常編碼的隨意性,導致問題日益積累,逐步擴散,最後只能推倒重來。如果時間經受不起推倒重來,你別無選擇,唯一實現的選擇就是重構。整體的優化設計雖然惹人注目令人難忘,但沒有平日的積累,何以收穫龐大的成就?你的目標應該是讓程式碼每天都有新變化。堅持幾個月,我相信我們都能擁有驕傲地,清晰程式碼。

  • 在什麼時候你會使用 document.write()?
  document.write()方法可以用在兩個方面:頁面載入過程中用實時指令碼建立頁面內容,以及用延時指令碼建立本視窗或新視窗的內容。該方法需要一個字串引數,它是寫到視窗或框架中的HTML內容
  記住,在載入頁面後,瀏覽器輸出流自動關閉。在此之後,任何一個對當前頁面進行操作的document.write()方法將開啟—個新的輸出流,它將清除當前頁面內容(包括源文件的任何變數或值)。因此,假如希望用指令碼生成的HTML替換當前頁面,就必須把HTML內容連線起來賦給一個變數,使用一個document.write()方法完成寫操作。不必清除文件並開啟一個新資料流,一個document.write()呼叫就可完成所有的操作。
  關於document.write()方法還有一點要說明的是它的相關方法document.close()。指令碼向視窗(不管是本視窗或其他視窗)寫完內容後,必須關閉輸出流。在延時指令碼的最後一個document.write()方法後面,必須確保含有document.close()方法,不這樣做就不能顯示影象和表單並且,任何後面呼叫的document.write()方法只會把內容追加到頁面後,而不會清除現有內容來寫入新值。為了演示document.write()方法,我們提供了同一個應用程式的兩個版本。
  • 大多數生成的廣告程式碼依舊使用 document.write(),雖然這種用法會讓人很不爽。
  • 請指出瀏覽器特性檢測,特性推斷和瀏覽器 UA 字串嗅探的區別?

檢測瀏覽器的特殊名稱和版本(使用者代理檢測)即瀏覽器UA字串嗅探。瀏覽器嗅探技術可以快捷的將程式碼進行分支,以便針對不同的瀏覽器應用不同的指令;針對特定瀏覽器的特定版本,超出範圍之外都是不可靠的

if (navigator.userAgent.indexOf("MSIE 7") > -1){
  //do something
}

var sUserAgent = navigator.userAgent;
//檢測Opera、KHTML
var isOpera = sUserAgent.indexOf(“Opera”) > -1;
var isKHTML = sUserAgent.indexOf(“KHTML”) > -1 || sUserAgent.indexOf(“Konqueror”) > -1 || sUserAgent.indexOf(“AppleWebKit”) > -1;
//檢測IE、Mozilla
var isIE = sUserAgent.indexOf(“compatible”) > -1 && sUserAgent.indexOf(“MSIE”) > -1 && !isOpera;
var isMoz = sUserAgent.indexOf(“Gecko”) > -1 && !isKHTML;
//檢測作業系統
var isWin = (navigator.platform == “Win32″) || (navigator.platform == “Windows”);
var isMac = (navigator.platform == “Mac68K”) || (navigator.platform == “MacPPC”) || (navigator.platform == “Macintosh”);
var isUnix = (navigator.platform == “X11″) && !isWin && !isMac;

檢測瀏覽器的特性(特性檢測)

if(document.all){ 
  //do something 

另外IE獨有children,parentElement,innerText,outerText,outerHTML,FF沒有;

直接進行特性檢測是個很好的方法,並且大部分情況下能滿足需求。一般只要在檢測前知道這個特性是否被實現即可,而不會去考慮它們之間的關係。 

另外,針對CSS3中新特性@font-face、border-radius、 border-image、box-shadow、rgba() 等,HTML5的特性——比如audio、video、本地儲存、和新的 <input>標籤的型別和屬性等,必要時要進行優雅降級。

  • 請儘可能詳盡的解釋 AJAX 的工作原理。

   非ajax是把要提交的內容放在submit裡面,瀏覽器重新整理提交資料。ajax即非同步資料重新整理,將要提交的資料與伺服器介面交換資料,將得到的資料返回用於重組dom元素,以及改變一些頁面效果。

  Ajax的原理簡單來說通過XmlHttpRequest物件來向伺服器發非同步請求,從伺服器獲得資料,然後用javascript來操作DOM而更新頁面。這其中最關鍵的一步就是從伺服器獲得請求資料。

   XMLHttpRequest是ajax的核心機制,它是在IE5中首先引入的,是一種支援非同步請求的技術。簡單的說,也就是javascript可以及時向伺服器提出請求和處理響應,而不阻塞使用者。達到無重新整理的效果。

    簡單地說,我們可以把伺服器端看成一個數據介面,它返回的是一個純文字流,當然,這個文字流可以是XML格式,可以是Html,可以是Javascript程式碼,也可以只是一個字串。這時候,XMLHttpRequest向伺服器端請求這個頁面,伺服器端將文字的結果寫入頁面,這和普通的web開發流程是一樣的,不同的是,客戶端在非同步獲取這個結果後,不是直接顯示在頁面,而是先由javascript來處理,然後再顯示在頁面。

  • 請解釋 JSONP 的工作原理,以及它為什麼不是真正的 AJAX。

  JSONP動態建立script標籤,回撥函式。Ajax是頁面無重新整理請求資料操作

  動態新增一個<script>標籤,而script標籤的src屬性是沒有跨域的限制的。這樣說來,這種跨域方式其實與ajax XmlHttpRequest協議無關了。當GET請求從被呼叫頁面返回時,可以返回一段JavaScript程式碼,這段程式碼會自動呼叫主頁面中的一個callback函式。

  Jsonp優點不受同源策略的影響,它的相容性更好,在更加古老的瀏覽器中都可以執行,不需要XMLHttpRequest或ActiveX的支援;並且在請求完畢後可以通過呼叫callback的方式回傳結果

  Jsonp缺點,只支援GET請求而不支援POST等其它型別的HTTP請求;它只支援跨域HTTP請求這種情況,不能解決不同域的兩個頁面之間如何進行JavaScript呼叫的問題。

  • 你使用過 JavaScript 模板系統嗎?

 沒用過,但是知道:在生成各種頁面內容結合javascript模板技術,能讓邏輯和資料之間更加清晰。邏輯是寫在"<%"與"%>"之間,如果是註釋,則用"<%#"與"%>",後臺傳過來的變數使用@來標記。

  • 如有使用過,請談談你都使用過哪些庫,比如 Mustache.js,Handlebars 等等。
  • 請解釋變數宣告提升。

   javascript不支援塊級作用域,即變數定義的作用域並不是離其最近的封閉語句或程式碼塊,而是包含它的函式:

var foo = 1;
function bar() {
    if (!foo) {var foo = 10;}
    alert(foo);//10
}
bar();
var a = 1;
function b() {a = 10;return;function a() {}}
b();alert(a);//1

對於被函式作用域包圍的變數的作用域為函式,函式內部訪問變數,將會返回函式體內最近的變數值;函式外部訪問變數將會函式體外所宣告的變數值。

也但是,對於被if語句包裹的程式碼段,不能看作是另外一個獨立的作用域,也就是說,對於被非函式的{}所包圍的程式碼段內所定義的變數,變數的宣告將會提升到{}所在的作用域。

function f(){{var x =0;}}等同於function f(){var x ;{x =0;}}

function foo() {bar();var x = 1;}會被解釋為function foo() {var x;bar();x = 1;}

變數賦值並沒有被提升,只是宣告被提升了。但是,函式的宣告有點不一樣,函式體也會一同被提升。但是請注意,函式的宣告有兩種方式:

function test() {
    foo(); // TypeError "foo is not a function"
    bar(); // "this will run!"
    var foo = function () { // 變數指向函式表示式
        alert("this won't run!");
    }
    function bar() { // 函式宣告 函式名為bar
        alert("this will run!");
    }
}
test();

對於var a=1;  function a(){ }  alert(a);function a(){  } var a=1;  alert(a);都是會打印出1  

對於全域性作用於範圍的變數,var與不var是有區別的. 沒有var的寫法,其變數不會被提升。比如下面的程式會報錯:alert(a);a=1;

eval中建立的區域性變數是不會被提升var a = 1;function t(){console.info(a);eval('var a = 2');console.info(a);}t();console.info(a);結果按照順序為1,2,1

  • 請描述下事件冒泡機制。

   從目標元素開始,往頂層元素傳播。途中如果有節點綁定了相應的事件處理函式,這些函式都會被一次觸發。如果想阻止事件起泡,可以使用e.stopPropagation()(Firefox)或者e.cancelBubble=true(IE)來組織事件的冒泡傳播。

  • "attribute" 和 "property" 的區別是什麼?

  DOM元素的attribute和property兩者是不同的東西。attribute翻譯為“特性”,property翻譯為“屬性”。
  attribute是一個特性節點,每個DOM元素都有一個對應的attributes屬性來存放所有的attribute節點,attributes是一個類陣列的容器,說得準確點就是NameNodeMap,不繼承於Array.prototype,不能直接呼叫Array的方法。attributes的每個數字索引以名值對(name=”value”)的形式存放了一個attribute節點。<div class="box" id="box" gameid="880">hello</div>

  property就是一個屬性,如果把DOM元素看成是一個普通的Object物件,那麼property就是一個以名值對(name=”value”)的形式存放在Object中的屬性。要新增和刪除property和普通的物件類似。

很多attribute節點還有一個相對應的property屬性,比如上面的div元素的id和class既是attribute,也有對應的property,不管使用哪種方法都可以訪問和修改。

總之,attribute節點都是在HTML程式碼中可見的,而property只是一個普通的名值對屬性。

  • 為什麼擴充套件 JavaScript 內建物件不是好的做法?
因為你不知道哪一天瀏覽器或javascript本身就會實現這個方法,而且和你擴充套件的實現有不一致的表現。到時候你的javascript程式碼可能已經在無數個頁面中執行了數年,而瀏覽器的實現導致所有使用擴充套件原型的程式碼都崩潰了。
  需要給Array原型新增一個distinct的方法,最好檢查是否存在同名的方法,避免自定義方法覆蓋原生方法:
  Arrray.prototype.distinct =
Arrray.prototype.distinct || function(){/*.....*/}
  • 請指出 document load 和 document ready 兩個事件的區別。
document.ready和onload的區別——JavaScript文件載入完成事件。頁面載入完成有兩種事件,一是ready,表示文件結構已經載入完成(不包含圖片等非文字媒體檔案),二是onload,指示頁面包含圖片等檔案在內的所有元素都載入完成。

jQuery中$(function(){/* do something*/});他的作用或者意義就是:在DOM載入完成後就可以可以對DOM進行操作。一般情況先一個頁面響應載入的順序是,域名解析-載入html-載入js和css-載入圖片等其他資訊。
  • == 和 === 有什麼不同?  
”==”:判斷值是否相等。應用一套難以理解的隱式強制轉換規則。
”===”判斷值及型別是否完全相等。讀者不需要涉及任何的隱式轉換。

注意:


1)如果兩個值的型別不同,它們就不相同。
2)如果兩個值是數字,而且值相同,那麼除非其中一個或兩個都是NaN(這種情況它們不是等同的),否則它們是等同的。值NaN永遠不會與其他任何值等同,包括它自身(奇怪的傢伙),要檢測一個值是否是NaN,可以使用全域性函式isNaN()。
3)如果兩個值都是字串,而且在串中同一位置上的字元完全相同,那麼它們就完全等同。如果字串的長度或內容不同,它們就不是等同的。
4)如果兩個值都是布林型true,或者兩個值都是布林型false,那麼它們等同。
5)如果兩個值引用的是同一個物件、陣列或函式,那麼它們完全等同。如果它們引用的是不同的物件(陣列或函式),它們就不完全等同,即使這兩個物件具有完全相同的屬性,或兩個陣列具有完全相同的元素。
6)如果兩個值都是null或都是undefined,“==”返回true,“===”返回false。

  • 請解釋一下 JavaScript 的同源策略。

   同源策略是客戶端指令碼(尤其是Javascript)的重要的安全度量標準。所謂同源是指,域名,協議,埠相同。如果我們又想利用XMLHTTP的無重新整理非同步互動能力,又不願意公然突破Javascript的安全策略,可以選擇的方案就是給XMLHTTP加上嚴格的同源限制。

  同源策略阻止從一個源載入的文件或指令碼獲取或設定另一個源載入的文件的屬性。

  處理跨域方法:

   1)document.domain+iframe的設定

  2)動態建立script

  3)利用iframe和location.hash

  4)window.name實現的跨域資料傳輸

  5)使用HTML5 postMessage

  • 如何實現下列程式碼:

[1,2,3,4,5].duplicator(); // [1,2,3,4,5,1,2,3,4,5]

Array.prototype.duplicator = function(){
  var l = this.length,i;
  for(i=0;i<l;i++){
   this.push(this[i]) 
}
}
  • 什麼是三元表示式?“三元” 表示什麼意思?   

    三元運算子需要三個運算元。

    語法是 條件 ? 結果1 : 結果2;. 這裡你把條件寫在問號(?)的前面後面跟著用冒號(:)分隔的結果1和結果2。滿足條件時結果1否則結果2。

  • 什麼是 "use strict"; ? 使用它的好處和壞處分別是什麼?

  在所有的函式 (或者所有最外層函式) 的開始處加入 "use strict"; 指令啟動嚴格模式。

"嚴格模式"有兩種呼叫方法

      1)將"use strict"放在指令碼檔案的第一行,則整個指令碼都將以"嚴格模式"執行。如果這行語句不在第一行,則無效,整個指令碼以"正常模式"執行。如果不同模式的程式碼檔案合併成一個檔案,這一點需要特別注意。