摘要:本文是高級入門的配套試題,題目解析的部分內容將引用高級入門。接收一個對象,并由這個對象去調用函數。循環內,調用函數,并設置第二個參數的值為。所以相當于執行而的作用是往消息隊列中存放一個回調函數,并在特定時間間隔后執行它。
本文是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)。
向函數傳遞了obj對象內部的_logName,而_logName是指向logName的。所以實際上doFun1接收的是指向logName函數的變量。即等價于doFun1(logName)。
而在doFun1內部是直接執行logName的。沒有明確的調用者。則這時等價于由window對象去調用,等價于window.logName。
而再由于所有在全局作用域(注意僅僅是全局作用域)中定義的變量都是window對象下的變量。都可以通過window對象進行訪問。
所以這時訪問到的就是name。
所以輸出的是HanMeiMei。
【】doFun2(obj)。
傳遞給doFun2的是obj的地址值,即doFun2中的o指向的就是obj,等價于obj。
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
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/81869.html
摘要:前端工程師學習資料,快速查找面試題,經典技術文章的總結,編程技巧,幫助學習者快速定位問題花點時間整理出一下前端工程師日常工作所需要的學習資料查找,幫助學習者快速掌握前端工程師開發的基本知識編程始于足下記住再牛逼的夢想也抵不住傻逼似的堅持蝴蝶 前端工程師學習資料,快速查找面試題,經典技術文章的總結,編程技巧,幫助學習者快速定位問題花點時間整理出一下web前端工程師日常工作所需要的學習資料...
摘要:面試題來源于網絡,看一下高級前端的面試題,可以知道自己和高級前端的差距。 面試題來源于網絡,看一下高級前端的面試題,可以知道自己和高級前端的差距。有些面試題會重復。 使用過的koa2中間件 koa-body原理 介紹自己寫過的中間件 有沒有涉及到Cluster 介紹pm2 master掛了的話pm2怎么處理 如何和MySQL進行通信 React聲明周期及自己的理解 如何...
閱讀 3936·2021-11-16 11:50
閱讀 934·2021-11-11 16:55
閱讀 3662·2021-10-26 09:51
閱讀 866·2021-09-22 15:03
閱讀 3422·2019-08-30 15:54
閱讀 3265·2019-08-30 15:54
閱讀 2476·2019-08-30 14:04
閱讀 922·2019-08-30 13:53