高級JS試題

本文是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
相關文章
相關標籤/搜索