摘要:另一個重復輸出一個給定的字符串第一個參數次第二個參數,如果第二個參數不是正數的時候,返回空字符串。這里是解決方案步驟檢查是否為負數,如果為則返回一個空字符串步驟檢查是否等于,如果是,返回字符串本身。
走在前端的大道上
問題1: 作用域(Scope)考慮以下代碼:
(function() { var a = b = 5; })(); console.log(b);
控制臺(console)會打印出什么?
答案
上述代碼會打印出5。
這個問題的陷阱就是,在立即執行函數表達式(IIFE)中,有兩個賦值,但是其中變量a使用關鍵詞var來聲明。這就意味著a是這個函數的局部變量。與此相反,b被分配給了全局作用域(譯注:也就是全局變量)。
這個問題另一個陷阱就是,在函數中沒有使用”嚴格模式” ("use strict";)。如果 嚴格模式開啟,那么代碼就會報錯 ” Uncaught ReferenceError: b is not defined” 。請記住,如果這是預期的行為,嚴格模式要求你顯式地引用全局作用域。所以,你需要像下面這么寫:
(function() { "use strict"; var a = window.b = 5; })(); console.log(b);問題2: 創建 “原生(native)” 方法
在 String 對象上定義一個 repeatify 函數。這個函數接受一個整數參數,來明確字符串需要重復幾次。這個函數要求字符串重復指定的次數。舉個例子:
console.log("hello".repeatify(3));
應該打印出hellohellohello.
答案
一個可行的做法如下:
String.prototype.repeatify = String.prototype.repeatify || function(times) { var str = ""; for (var i = 0; i < times; i++) { str += this; } return str; };
這個問題測試了開發人員對 javascript 中繼承及原型(prototype)屬性的知識。這也驗證了開發人員是否有能力擴展原生數據類型功能(雖然不應該這么做)。
在這里,另一個關鍵點是,看你怎樣避免重寫可能已經定義了的方法。這可以通過在定義自己的方法之前,檢測方法是否已經存在。
String.prototype.repeatify = String.prototype.repeatify || function(times) {/* code here */};
當你被問起去擴展一個Javascript方法時,這個技術非常有用。
另一個:重復輸出一個給定的字符串(str第一個參數)n 次 (num第二個參數),如果第二個參數num不是正數的時候,返回空字符串。
function repeatStringNumTimes(str, num) { return str; } repeatStringNumTimes("abc", 3);
提供測試情況:
repeatStringNumTimes("*", 3) //應該返回 "***". repeatStringNumTimes("abc", 3) //應該返回 "abcabcabc". repeatStringNumTimes("abc", 4) //應該返回 "abcabcabcabc". repeatStringNumTimes("abc", 1) //應該返回 "abc". repeatStringNumTimes("*", 8) //應該返回 "********". repeatStringNumTimes("abc", -2) //應該返回 "".
解題思路:
三種方法:
使用 while 循環方法1:通過 while 循環重復輸出一個字符串
使用遞歸
使用ES6 repeat()
這可能是最常規的解題思路。while 語句只要指定的條件計算結果為true的時候,就執行其語句。while 語句結構大概是這樣的:
while (condition) statement
在每次通過循環之前計算條件結果。如果條件為true,則執行語句。如果條件為false,則執行繼續 while 循環之后的任何語句。
只要條件為true,語句就會執行。 這里是解決方案:
function repeatStringNumTimes(string, times) { // 第1步. 常見一個空字符,用來寄存重復的字符串 var repeatedString = ""; // 第2步. 設置 while 循環的條件為(times > 0) 作為檢查 while (times > 0) { // 只要 times 大于 0, 語句就會執行 // 執行語句 statement repeatedString += string; // 等價于 repeatedString = repeatedString + string; times--; // 遞減,等價于 times = times - 1; } /* while循環邏輯 條件 T/F repeatedString += string 結果 次數 1th (3 > 0) true "" + "abc" "abc" 2 2th (2 > 0) true "abc" + "abc" "abcabc" 1 3th (1 > 0) true "abcabc" + "abc" "abcabcabc" 0 4th (0 > 0) false } */ // 第3步. 返回重復字符串 return repeatedString; // "abcabcabc" } repeatStringNumTimes("abc", 3);
去掉注釋后:
function repeatStringNumTimes(string, times) { var repeatedString = ""; while (times > 0) { repeatedString += string; times--; } return repeatedString; } repeatStringNumTimes("abc", 3);
好,輕松完成!不過這里還可以有幾個變種:
對于老前端來說,首先一個可能會將字符串拼接,修改為 數組join()拼接字符串,例如:
function repeatStringNumTimes(string, times) { var repeatedArr = []; // while (times > 0) { repeatedArr.push(string); times--; } return repeatedArr.join(""); } repeatStringNumTimes("abc", 3)
很多老前端都有用數組join()拼接字符串的“情懷”,因為很早以前普遍認為數組join()拼接字符串比字符串+拼接速度要快得多。不過現在未必,例如,V8 下+拼接字符串,要比數組join()拼接字符串快。我用這兩個方法測試了3萬次重復輸出,只相差了幾毫秒。
另一個變種可以用 for 循環:
function repeatStringNumTimes(string, times) { var repeatedString = ""; for(var i = 0; i < times ;i++) { repeatedString += string; } return repeatedString; } repeatStringNumTimes("abc", 3)方法2:通過條件判斷和遞歸重復輸出一個字符串
遞歸是一種通過重復地調用函數本身,直到它達到達結果為止的迭代操作的技術。為了使其正常工作,必須包括遞歸的一些關鍵特征。
第一種是基本情況:一個語句,通常在一個條件語句(如if)中,停止遞歸。
第二種是遞歸情況:調用遞歸函數本身的語句。
這里是解決方案:
function repeatStringNumTimes(string, times) { // 步驟1.檢查 times 是否為負數,如果為 true 則返回一個空字符串 if (times < 0) { return ""; } // 步驟2.檢查times是否等于1,如果是,返回字符串本身。 if (times === 1) { return string; } // 步驟3. 使用遞歸 else { return string + repeatStringNumTimes(string, times - 1); // return "abcabcabc"; } /* 遞歸方法的第一部分你需要記住,你不會只調用一次,您將有好幾個嵌套調用 times string + repeatStringNumTimes(string, times - 1) 1st call 3 "abc" + ("abc", 3 - 1) 2nd call 2 "abc" + ("abc", 2 - 1) 3rd call 1 "abc" => if (times === 1) return string; 4th call 0 "" => if (times <= 0) return ""; 遞歸方法的第二部分 4th call will return "" 3rd call will return "abc" 2nd call will return "abc" 1st call will return "abc" 最后調用是串聯所有字符串 return "abc" + "abc" + "abc"; // return "abcabcabc"; */ } repeatStringNumTimes("abc", 3);
去掉注釋后:
function repeatStringNumTimes(string, times) { if(times < 0) return ""; if(times === 1) return string; else return string + repeatStringNumTimes(string, times - 1); } repeatStringNumTimes("abc", 3);方法3:使用ES6 repeat() 方法重復輸出一個字符串
這個解決方案比較新潮,您將使用 String.prototype.repeat() 方法:
repeat() 方法構造并返回一個新字符串,該字符串包含被連接在一起的指定數量的字符串的副本。 這個方法有一個參數 count 表示重復次數,介于0和正無窮大之間的整數 : [0, +∞) 。表示在新構造的字符串中重復了多少遍原字符串。重復次數不能為負數。重復次數必須小于 infinity,且長度不會大于最長的字符串。
這里是解決方案:
function repeatStringNumTimes(string, times) { //步驟1.如果 times 為正數,返回重復的字符串 if (times > 0) { // (3 > 0) => true return string.repeat(times); // return "abc".repeat(3); => return "abcabcabc"; } //Step 2. Else 如果times是負數,如果為true則返回一個空字符串 else { return ""; } } repeatStringNumTimes("abc", 3);
去掉注釋后:
function repeatStringNumTimes(string, times) { if (times > 0) return string.repeat(times); else return ""; } repeatStringNumTimes("abc", 3);
您可以使用三元表達式作為 if/else 語句的快捷方式,如下所示:
function repeatStringNumTimes(string, times) { return times > 0 ? string.repeat(times) : ""; } repeatStringNumTimes("abc", 3);問題3: 變量提升(Hoisting)
執行以下代碼的結果是什么?為什么?
function test() { console.log(a); console.log(foo()); var a = 1; function foo() { return 2; } } test();
答案
這段代碼的執行結果是undefined 和 2。
這個結果的原因是,變量和函數都被提升(hoisted) 到了函數體的頂部。因此,當打印變量a時,它雖存在于函數體(因為a已經被聲明),但仍然是undefined。換言之,上面的代碼等同于下面的代碼:
function test() { var a; function foo() { return 2; } console.log(a); console.log(foo()); a = 1; } test();問題4: 在javascript中,this是如何工作的
以下代碼的結果是什么?請解釋你的答案。
var fullname = "John Doe"; var obj = { fullname: "Colin Ihrig", prop: { fullname: "Aurelio De Rosa", getFullname: function() { return this.fullname; } } }; console.log(obj.prop.getFullname()); var test = obj.prop.getFullname; console.log(test());
答案
這段代碼打印結果是:Aurelio De Rosa 和 John Doe 。原因是,JavaScript中關鍵字this所引用的是函數上下文,取決于函數是如何調用的,而不是怎么被定義的。
在第一個console.log(),getFullname()是作為obj.prop對象的函數被調用。因此,當前的上下文指代后者,并且函數返回這個對象的fullname屬性。相反,當getFullname()被賦值給test變量時,當前的上下文是全局對象window,這是因為test被隱式地作為全局對象的屬性。基于這一點,函數返回window的fullname,在本例中即為第一行代碼設置的。
問題5: call() 和 apply()修復前一個問題,讓最后一個console.log() 打印輸出Aurelio De Rosa.
答案
這個問題可以通過運用call()或者apply()方法強制轉換上下文環境。如果你不了解這兩個方法及它們的區別,我建議你看看這篇文章 function.call和function.apply之間有和區別?。 下面的代碼中,我用了call(),但apply()也能產生同樣的結果:
console.log(test.call(obj.prop));
問題6: 閉包(Closures)
考慮下面的代碼:
var nodes = document.getElementsByTagName("button"); for (var i = 0; i < nodes.length; i++) { nodes[i].addEventListener("click", function() { console.log("You clicked element #" + i); }); }
請問,如果用戶點擊第一個和第四個按鈕的時候,控制臺分別打印的結果是什么?為什么?
答案
上面的代碼考察了一個非常重要的 JavaScript 概念:閉包(Closures)。對于每一個JavaScript開發者來說,如果你想在網頁中編寫5行以上的代碼,那么準確理解和恰當使用閉包是非常重要的。如果你想開始學習或者只是想簡單地溫習一下閉包,那么我強烈建議你去閱讀 Colin Ihrig 這個教程:JavaScript Closures Demystified
也就是說,代碼打印兩次You clicked element #NODES_LENGTH,其中NODES_LENGTH是nodes的結點個數。原因是在for循環完成后,變量i的值等于節點列表的長度。此外,因為i在代碼添加處理程序的作用域中,該變量屬于處理程序的閉包。你會記得,閉包中的變量的值不是靜態的,因此i的值不是添加處理程序時的值(對于列表來說,第一個按鈕為0,對于第二個按鈕為1,依此類推)。在處理程序將被執行的時候,在控制臺上將打印變量i的當前值,等于節點列表的長度。
問題7: 閉包(Closures)修復上題的問題,使得點擊第一個按鈕時輸出0,點擊第二個按鈕時輸出1,依此類推。
答案
有多種辦法可以解決這個問題,下面主要使用兩種方法解決這個問題。
第一個解決方案使用立即執行函數表達式(IIFE)再創建一個閉包,從而得到所期望的i的值。實現此方法的代碼如下:
var nodes = document.getElementsByTagName("button"); for (var i = 0; i < nodes.length; i++) { nodes[i].addEventListener("click", (function(i) { return function() { console.log("You clicked element #" + i); } })(i)); }
另一個解決方案不使用IIFE,而是將函數移到循環的外面。這種方法由下面的代碼實現:
function handlerWrapper(i) { return function() { console.log("You clicked element #" + i); } } var nodes = document.getElementsByTagName("button"); for (var i = 0; i < nodes.length; i++) { nodes[i].addEventListener("click", handlerWrapper(i)); }
問題8:數據類型
考慮如下代碼:
console.log(typeof null); console.log(typeof {}); console.log(typeof []); console.log(typeof undefined);
答案
前面的問題似乎有點傻,但它考察 typeof 操作符的知識。很多JavaScript開發人員不知道typeof的一些特性。在此示例中,控制臺將顯示以下內容:
object object object undefined
最令人驚訝的輸出結果可能是第三個。大多數開發人員認為typeof []會返回Array。如果你想測試一個變量是否為數組,您可以執行以下測試:
var myArray = []; if (myArray instanceof Array) { // do something... }問題9:事件循環
下面代碼運行結果是什么?請解釋。
function printing() { console.log(1); setTimeout(function() { console.log(2); }, 1000); setTimeout(function() { console.log(3); }, 0); console.log(4); } printing();
答案
輸出結果:
1 4 3 2
想知道為什么輸出順序是這樣的,你需要弄了解setTimeout()做了什么,以及瀏覽器的事件循環原理。瀏覽器有一個事件循環用于檢查事件隊列,處理延遲的事件。UI事件(例如,點擊,滾動等),Ajax回調,以及提供給setTimeout()和setInterval()的回調都會依次被事件循環處理。因此,當調用setTimeout()函數時,即使延遲的時間被設置為0,提供的回調也會被排隊。回調會呆在隊列中,直到指定的時間用完后,引擎開始執行動作(如果它在當前不執行其他的動作)。因此,即使setTimeout()回調被延遲0毫秒,它仍然會被排隊,并且直到函數中其他非延遲的語句被執行完了之后,才會執行。
有了這些認識,理解輸出結果為“1”就容易了,因為它是函數的第一句并且沒有使用setTimeout()函數來延遲。接著輸出“4”,因為它是沒有被延遲的數字,也沒有進行排隊。然后,剩下了“2”,“3”,兩者都被排隊,但是前者需要等待一秒,后者等待0秒(這意味著引擎完成前兩個輸出之后馬上進行)。這就解釋了為什么“3”在“2”之前。
問題10:算法寫一個isPrime()函數,當其為質數時返回true,否則返回false。
答案
我認為這是面試中最常見的問題之一。然而,盡管這個問題經常出現并且也很簡單,但是從被面試人提供的答案中能很好地看出被面試人的數學和算法水平。
首先, 因為JavaScript不同于C或者Java,因此你不能信任傳遞來的數據類型。如果面試官沒有明確地告訴你,你應該詢問他是否需要做輸入檢查,還是不進行檢查直接寫函數。嚴格上說,應該對函數的輸入進行檢查。
第二點要記住:負數不是質數。同樣的,1和0也不是,因此,首先測試這些數字。此外,2是質數中唯一的偶數。沒有必要用一個循環來驗證4,6,8。再則,如果一個數字不能被2整除,那么它不能被4,6,8等整除。因此,你的循環必須跳過這些數字。如果你測試輸入偶數,你的算法將慢2倍(你測試雙倍數字)。可以采取其他一些更明智的優化手段,我這里采用的是適用于大多數情況的。例如,如果一個數字不能被5整除,它也不會被5的倍數整除。所以,沒有必要檢測10,15,20等等。如果你深入了解這個問題的解決方案,我建議你去看相關的Wikipedia介紹。
最后一點,你不需要檢查比輸入數字的開方還要大的數字。我感覺人們會遺漏掉這一點,并且也不會因為此而獲得消極的反饋。但是,展示出這一方面的知識會給你額外加分。
現在你具備了這個問題的背景知識,下面是總結以上所有考慮的解決方案:
function isPrime(number) { // If your browser doesn"t support the method Number.isInteger of ECMAScript 6, // you can implement your own pretty easily if (typeof number !== "number" || !Number.isInteger(number)) { // Alternatively you can throw an error. return false; } if (number < 2) { return false; } if (number === 2) { return true; } else if (number % 2 === 0) { return false; } var squareRoot = Math.sqrt(number); for(var i = 3; i <= squareRoot; i += 2) { if (number % i === 0) { return false; } } return true; }問題11:數據類型
var a = {n : 1}; var b = a; a.x = a = {n : 2}; console.log(a.x); console.log(b.x);
解析:
var a = {n : 1}; var b = a; // 此時b = {n:1}; //如果此時a.n=4,那么b.n也等于4 a.x = a = {n : 2}; // 從右往左賦值,a = {n:2}; 新對象 // b = {n:2},//此時筆者認為b應該還是{n:1}待考證確認 // a.x 中的a是{n:1}; {n:1}.x = {n:2}; 舊對象 // 因為b和a是引用的關系所以b.x也等于 {n:2} console.log(a.x); undefined // 此時的a是新對象,新對象上沒有a.x 所以是undefined console.log(b.x); {n:2}
var i = 10; i += i *= i; // i*=i 100 // i+= 這里的i是 =10不是100 console.log(i);問題12:
if (!("a" in window)) { var a = 1; } console.log(a);
解析:
在瀏覽器環境中,全局變量都是window的一個屬性,即
var a = 1 等價于 window.a = 1。in操作符用來判斷某個屬性屬于某個對象,可以是對象的直接屬性,也可以是通過prototype繼承的屬性。
再看題目,在瀏覽器中,如果沒有全局變量 a ,則聲明一個全局變量 a (ES5沒有塊級作用域),并且賦值為1。很多人會認為打印的是1。非也,大家不要忘了變量聲明會被前置!什么意思呢?題目也就等價于
var a; if (!("a" in window)) { a = 1; } console.log(a);
所以其實已經聲明了變量a,只不過if語句之前值是undefined,所以if語句壓根不會執行。
最后答案就是 undefined
var a = 1, b = function a(x) { x && a(--x); }; console.log(a);
解析:
這道題有幾個需要注意的地方:
1.變量聲明、函數聲明會被前置,但是函數表達式并不會,準確說類似變量聲明前置,舉個栗子:
console.log("b", b); // b undefined var b = function() {} console.log("b", b); // b function () {}
2.具名的函數表達式的名字只能在該函數內部取到,舉個例子(排除老的IE?):
var foo = function bar () {} console.log("foo", foo); // foo function bar(){} console.log("bar", bar); // Uncaught ReferenceError: bar is not defined
綜合這兩點,再看題目,最后輸出的內容就為 1
問題14:function a(x) { return x * 2; } var a; console.log(a);
解析:
函數聲明會覆蓋變量聲明,但不會覆蓋變量賦值,舉個栗子簡單粗暴:
function foo(){ return 1; } var foo; console.log(typeof foo); // "function"
函數聲明的優先級高于變量聲明的優先級,但如果該變量foo賦值了,那結果就完全不一樣了:
function foo(){ return 1; } var foo = 1; console.log(typeof foo); // "number"
變量foo賦值以后,變量賦值初始化就覆蓋了函數聲明。這個需要注意
再看題目
function a(x) { return x * 2; } var a; console.log(a); // function a(x) {...}問題15:
function b(x, y, a) { arguments[2] = 10; console.log(a); } b(1, 2, 3);
解析:
這題考察 arguments 對象的用法(詳看JavaScript中的arguments對象)
一般情況,arguments與函數參數是動態綁定關系(為什么說是一般稍后會解釋),所以很好理解,最后輸出的是10
但是但是但是,我們不要忘了一個特殊情況–嚴格模式,在嚴格模式中 arguments 與相當于函數參數的一個拷貝,并沒有動態綁定關系,舉個栗子:
"use strict" // 嚴格模式!! function b(x, y, a) { arguments[2] = 10; console.log(a); } b(1, 2, 3); // 3問題16:
function a() { console.log(this); } a.call(null);
解析:
function a() { console.log(this); } a.call(null);
關于 a.call(null); 根據ECMAScript262規范規定:
如果第一個參數傳入的對象調用者是null或者undefined的話,call方法將把全局對象(瀏覽器上是window對象)作為this的值。所以,不管你什么時候傳入null或者 undefined,其this都是全局對象window。所以,在瀏覽器上答案是輸出 window 對象。
但是但是但是,我們依舊不能忘記一個特殊情況–嚴格模式,在嚴格模式中,null 就是 null,undefined 就是 undefined ,舉個栗子:
"use strict"; // 嚴格模式!! function a() { console.log(this); } a.call(null); // null a.call(undefined); // undefined
參考文章:
1.10道典型的JavaScript面試題
2.對匿名函數的深入理解(徹底版) 見評論區
3.你真的知道JS嗎?
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/92023.html
摘要:定義變量如果不使用則變量為為全局作用域。當然嚴格模式是禁止這樣做的。遵循詞法作用域原則,其中后兩題來源于權威指南。非箭頭函數下的指向運行時所在作用域。中逗號操作符會從左到右計算它的操作數,返回最后一個操作數的值。原文發表于我的博客 (function(){ var a = b =1; })() console.log(b) 答案:1。定義變量如果不使用 var 則變量為為全局作...
摘要:第一遞歸函數功能假設的功能是求第項的值,代碼如下找出遞歸結束的條件顯然,當或者我們可以輕易著知道結果。定義遞歸函數功能假設函數的功能是反轉但鏈表,其中表示鏈表的頭節點。可能很多人在大一的時候,就已經接觸了遞歸了,不過,我敢保證很多人初學者剛開始接觸遞歸的時候,是一臉懵逼的,我當初也是,給我的感覺就是,遞歸太神奇了! 可能也有一大部分人知道遞歸,也能看的懂遞歸,但在實際做題過程中,卻不知道怎么...
摘要:碰到這種面試官,你只有是個題霸,再加上眼緣夠才能順利入圍。只要按照我題目的思路,甚至打出來測試用例看看,就能實現這個題目了。答案根據的,對答案做出修正。另我的答案絕不敢稱最佳,隨時歡迎優化修正。但了解總歸是好的。 我們在長期的面試過程中,經歷了種種苦不堪言,不訴苦感覺不過癮(我盡量控制),然后主要聊聊常見JavaScript面試題的解法,以及面試注意事項 憶苦 面試第一苦,面試官的土 ...
摘要:對象方法中的當以對象里的方法的方式調用函數時,它們的是調用該函數的對象。注意,在何處或者如何定義調用函數完全不會影響到的行為。在這次執行期間,函數中的將指向。 原文鏈接 與其他語言相比,函數的this關鍵字在JavaScript中的行為略有不同。并且它在嚴格模式和非嚴格模式之間也有一些區別。 在絕大多數情況下,函數的調用方式決定了this的值。this不能在執行期間被賦值,在每次函數被...
閱讀 3070·2021-11-22 13:54
閱讀 834·2021-11-04 16:08
閱讀 4463·2021-10-11 11:09
閱讀 3597·2021-09-22 16:05
閱讀 910·2019-08-30 15:54
閱讀 387·2019-08-30 15:44
閱讀 594·2019-08-30 14:05
閱讀 1014·2019-08-30 12:46