本文是JS高級入門的配套試題,題目解析的部份內容將引用JS高級入門
。面試
題目一(this指針)
function logName(){ console.log(this.name); } function doFun1(fn){ fn(); } function doFun2(o){ o.logName(); } var obj = { name: "LiLei", logName: logName }; var name = "HanMeiMei"; doFun1(obj.logName); doFun2(obj);
題目首先定義了三個函數:
【】logName:打印調用者(即誰去調用它,一般是一個對象)的name屬性。
【】doFun1:接收一個函數,並直接運行這個函數。
【】doFun2:接收一個對象,並由這個對象去調用logName函數。
然後定義了兩個變量:
【】obj是一個對象,裏面定義了name(假設另命名爲_name)的字符串變量。定義了logName(假設另起名字爲_logName),指向外部的logName函數。
【】name是一個字符串變量。
最後分別對兩個函數進行調用,
【】doFun1(obj.logName)。segmentfault
向函數傳遞了obj對象內部的_logName,而_logName是指向logName的。因此實際上doFun1接收的是指向logName函數的變量。即等價於doFun1(logName)。數組
而在doFun1內部是直接執行logName的。沒有明確的調用者。則這時等價於由window對象去調用,等價於window.logName。閉包
而再因爲全部在全局做用域(注意僅僅是全局做用域)中定義的變量都是window對象下的變量。均可以經過window對象進行訪問。
因此這時訪問到的就是name。app
因此輸出的是HanMeiMei。dom
【】doFun2(obj)。函數
傳遞給doFun2的是obj的地址值,即doFun2中的o指向的就是obj,等價於obj。this
o.logName是由o去調用logName。至關於obj.logName。線程
因此找到的是obj內部的name(即咱們假定的_name)。指針
因此打印出的是LiLei。
HanMeiMei LiLei
題目二(this指針的修改apply)
function fun(somthing) { console.log(this.name, somthing); } function bindFun(fn, obj) { return function () { return fn.apply(obj, arguments); } } var obj = { name: "LiLei" }; var bar = bindFun(fun, obj); var b = bar("HanMeiMei"); console.log(b);
本題的主要考點是this的相關函數apply。
首先定義了三個函數:
【】fun函數:接收變量,而且打印這個函數的調用者的name值,以及形參something。
【】bindFun函數:接收兩個參數,分別是函數以及對象。bindFun沒有邏輯操做。只是返回了一個匿名函數(咱們假定爲_fun)
【】_fun函數:一樣沒有操做,直接返回一個已經經過apply修改了this的函數的執行結果
。(即直接返回一個函數執行結果值,而這個函數的this的值是已經被修改過的)
然後定義了三個變量:
【】obj是一個對象,裏面只定義了name字符串變量。
【】bar是一個變量,這個變量的值爲bindFun函數執行的結果值。
【】b是一個變量,值爲bar函數執行的結果值。
整段代碼中,除了定義,一共只執行了3句邏輯語句:
【】在bindFun函數的執行,傳遞了兩個函數,分別是fun函數,obj對象。bindFun函數執行結果是返回一個函數_fun。因此bar指向_fun函數。
【】bar函數的調用,即至關於_fun函數的調用。
_fun函數的返回fn.apply(obj, arguments)的運行值;
其中這裏的fn是bindFun函數接受的第一個參數fun,即返回的是fun函數。
而這個fun函數進行了apply的函數調用,修改了函數中this的值。
this指向爲bindFun函數的第二個參數值,即外部的obj變量。
apply函數還接受了第二個參數arguments,即_fun函數的參數數組。即bar函數的參數數組【'HanMeiMei'】。做爲fun函數的參數。
apply函數的做用是立刻執行調用者函數,即立刻執行bar(即_fun,即fun)函數。
fun函數執行是打印this.name,和參數something。this指向obj,因此this.name爲LiLei。而something值爲【'HanMeiMei'】
因此打印了,LiLeiHanMeiMei。而且沒有返回值。
【】b是bar函數的返回值,然而bar函數並無返回東西,因此是undefined。因此console.log(b)打印的值是undefined。
繞了一圈,整個過程至關於執行。fun.call(obj,'HanMeiMei');
LiLeiHanMeiMei undefined
題目三(自執行函數)
function logName() { console.log(this); console.log(this.name); } var name = "XiaoMing"; var LiLei = { name: "LiLei", logName: logName }; var HanMeiMei = { name: "HanMeiMei" }; (HanMeiMei.logName = LiLei.logName)();
這題相對簡單一些。只考到自執行函數的定義。並無藉助他的特性作代碼隔離。只是想考一下是否已經掌握了自執行函數的定義。
首先定義了一個函數:
【】logName,打印出調用者(即this指針的指向),已經調用者的name屬性值
然後定義了三個變量:
【】name:字符串變量,因爲全局的,因此至關於window對象的屬性值。
【】LiLei:定義了對象,該對象包含name值,以及logName(咱們假定LiLei的logName從新命名爲_logName)。指向外部的logName函數
【】HanMeiMei:定義了對象,該對象只包含name值。
最終只執行了一條語句,兩個步驟。
分別是賦值語句:
爲HanMeiMei對象添加一個參數logName,賦值爲LiLei的_logName。即指向外部的logName。這不是重點,重點是賦值語句的結果是什麼?
是所賦的值,即賦值語句的結果是LiLei.logName,即HanMeiMei.logName = LiLei.logName終止獲得的是logName。
而後對這個函數進行調用。即logName(),這時this沒有發生改變,仍是window,因此輸出是打印window對象。已經window.name即XiaoMing。
XiaoMing
題目四(聲明提早)
test(); var test = 0; function test() { console.log(1); } test(); test = function () { console.log(2); }; test();
聲明提早的題,把背後的代碼執行順序理順就好。
首先將聲明都放到代碼的最上面:
var test;//定義變量
function test(){console.log(1)}//定義函數
而後執行的操做:
test();//函數調用操做
test = 0;//賦值操做
test();//函數調用操做
test = function(){console.log(2)}//賦值操做,將test賦值爲函數
test();//函數調用操做
因此上述代碼等價於(聲明提早,先定義,後執行):
var test;
function test(){console.log(1)}
test();//調用函數,輸出1
test = 0;
test();//此時test爲0,不是函數,將報錯test is not a function
test = function(){console.log(2)}//因爲JS報錯,後面的代碼將不被運行
test();//因爲JS報錯,後面的代碼將不被運行
綜合來講,這裏設置了三個考點:
聲明提早。
函數的定義與函數賦值的區別,function xx爲函數定義,將總體上移。
JS報錯後,後面的代碼將不被運行。
1 Uncaught TypeError: test is not a function
題目五(基本類型與引用類型)
var name = "LiLei"; var people = {name: "HanMeiMei"}; function test1(name) { name = "LaoWang"; } function test2(obj) { obj.name = "LaoWang"; } test1(name); test2(people); console.log("name " + name); console.log("name " + people.name);
題目首先定義了兩個函數:
【】test1:接受一個參數,並修改該參數的值。
【】test2:接收一個對象,並修改該對象的屬性的值。
然後定義了兩個變量:
【】name是一個字符串參數,值爲'LiLei'
【】people是一個對象,包含一個name屬性。值爲'HanMeiMei'
而後分別對兩個函數進行調用:
test1(name):即將name做爲參數,調用test1函數。
這時函數的內部將產生一個新的參數name(記做_name),它的值等於外部的name的值(LiLei);
test1函數將_name的值修改成'LaoWang',可是因爲_name和name是兩份獨立的變量。因此name的值不受改變。
test2(people):將people做爲參數,調用test1函數。
這時函數的內部將產生一個新的參數obj,它的值等於外部的people的值;
而people是引用類型,其值爲對象{name:"HanMeiMei"}的地址值
。因此obj也爲對象{name:"HanMeiMei"}的地址值。
test2對對象{name:"HanMeiMei"}的name屬性進行修改,改成'LaoWang'
因此obj和people的name值都發生了改變。
這裏涉及到兩個知識點
基本數據類型是按值訪問的,即該變量就存在了實際值。而引用數據類型保存的是則是對實際值的引用(即指向實際值的指針)。
函數形參(即在函數中實際使用的值,如test函數裏面的name)和參數的實參(即往調用函數時調用的參數,如test(name)中的name)的值相同,但並非"同一個值"(在內存中的地址是不一樣的,至關於var a = b =0;)。
在函數參數的傳遞,是經過按值傳遞的。
LiLei LaoWang
題目六(JS線程與閉包)
執行的結果是什麼,分別在什麼時間輸出 for (var i = 1; i < 5; i++) { setTimeout(function () { console.log(i); }, i * 1000); }
JS線程的規則:程序將先把主邏輯的內容作完,再去讀取消息列表,調用消息列表中的回調函數
在這裏主邏輯爲一個for循環,從i爲1到i爲4循環執行4次for循環的內容。
for循環內,調用setTimeout函數,並設置第二個參數的值爲i。注意這裏是對setTimeout函數直接進行調用。因此參數中i的值是隨for循環改變的。因此至關於執行
setTimeout(function () {doSomething}, 1000);
setTimeout(function () {doSomething}, 2000);
setTimeout(function () {doSomething}, 3000);
setTimeout(function () {doSomething}, 4000);
而setTimeout的做用是往消息隊列中存放一個回調函數,並在特定時間間隔後執行它。因此該回調函數會在for循環以後完成。
for循環執行完時,i的值爲5。(由於5<5不成立,結束循環)。因此調用回調函數function(){console.log(i)}時,i的值爲5。因此輸出爲5。
閉包是由於回調函數引用到了for循環的i,回調函數沒執行完,i不能被回收。因此仍是能訪問到。
輸出4次,每一秒輸出一個5
題目七(做用域陷阱)
function logName(){ console.log(name); } function test () { var name = 3; logName(); } var name = 2; test();
一句話能夠解釋完:做用域的層級關係與函數定義時所處的層級關係相同
注意是,函數定義時的層級關係,而不是調用時的層級關係。
在這裏,logName函數,test函數以及外部的name變量(值爲2)處於同一個層級。
因此調用logName時,找到的是外部的name變量。
因此打印出2
2
題目八(隱式閉包)
function logNum(num) { console.log("num " + num); } for (var i = 0; i < 2; i++) { var fun = logNum.bind(null, i); setTimeout(fun, 100); }
這道題和第六題很像,可是運行結果並不同。
緣由是bind函數產生了隱式閉包。
爲何bind函數能修改this指針?底層實現筆者不知道,可是咱們能夠利用閉包的特性經過自執行函數來模擬bind函數。
bind(obj,elem0,elem1,elem2...)函數至關於
(function(_obj,_elem0,_elem1,_elem2...){ return function(){//這個函數就是調用bind函數的函數 doSomething; //將this.替換成_obj. //這裏將可能使用到_elem0,_elem1,_elem2參數。 } })(obj,elem0,elem1,elem2...)
經過自執行函數返回一個函數,造成一個閉包,內部函數調用的參數是自執行函數的參數,而不是外部的元素。
根據前面第五題的解析函數形參(即在函數中實際使用的值,如test函數裏面的name)和參數的實參(即往調用函數時調用的參數,如test(name)中的name)的值相同,但並非"同一個值"(在內存中的地址是不一樣的,至關於var a = b =0;)。
doSomething中使用到的參數是自執行函數的形參(_elemX),而不是外部的實參elem。
因此外部的elem的變化對doSomething沒有影響。
根據解析,對題目進行改造,至關於
function logNum(num) { console.log("num " + num); } for (var i = 0; i < 2; i++) { var fun = (function(_i){ return function(){ console.log(_i); } })(i); setTimeout(fun, 100); }
而自執行函數是定義完立刻執行的。因此拿到的值_i也是隨着for循環改變的。
因此輸出0 1
利用該特性還能夠解決常見的面試題:經過原生JS返回所點擊元素索引值。
var domList = document.getElementsByTagName('div'); for (var i = 0, length = domList.length; i < length; i++) { var item = domList[ i ]; (function(_item, _i) { _item.addEventListener('click', function() { console.log(_i); }) })(item, i) }
0 1