天天看點

每日20道面試題帶解析02

每日20道面試題帶解析02

以下題目,本人驗證無誤,查閱了相關資料,得出解析部分并做了相關總結,希望對正準備跳槽或找工作的你有幫助!

1、寫出執行結果,并解釋原因
var a = [0];
if ([0]) {
  console.log(a == true);
} else {
  console.log("wut");
}
// 輸出什麼?      

答案及解析

答案 : false
解析 : if(condition)判斷時,會把condition轉換成boolean然後做判斷,[0]是一個有值的list,是以轉成boolean是true, 
       而A == B的比較時,如果A和B的類型不一樣,會先把A和B轉化成相同的type,通常轉為number
  //分成以下步驟
  //把true轉化成number,true變成1
  [0] == 1;
  //list是object
  //先看[0].valueOf(),結果還是[0]
  //再看[0].toString(),結果是“0” type是string
  "0" == 1; 
  //把“0” string轉化成number,“0”變成0,0不等于1
  0 == 1; //結果是false      
2、寫出執行結果,并解釋原因
[1 < 2 < 3, 3 < 2 < 1]    //解析成什麼?      

答案及解析

答案 : [true,true]
解析 : 運算符優先級,分步解析
  1 < 2 < 3 => true < 3  => 1 < 3 => true 
  3 < 2 < 1 => false < 1 => 0 < 1 => true      
3、寫出執行結果,并解釋原因
function foo() { }
var oldName = foo.name;
foo.name = "bar";
[oldName, foo.name]      

答案及解析

答案 : ['foo','foo']
解析 : 函數的name是隻讀屬性不可修改      
4、寫出執行結果,并解釋原因
var lowerCaseOnly =  /^[a-z]+$/;
[lowerCaseOnly.test(null), lowerCaseOnly.test()]      

答案及解析

答案 : [true, true]
解析 : 正則容易忽視的坑,test在檢測時會隐性将内容轉為字元串,其實等同于:[lowerCaseOnly.test('null'), lowerCaseOnly.test('undefined')]      
5、寫出執行結果,并解釋原因
if ('http://giftwrapped.com/picture.jpg'.match('.gif')) {
  console.log('a gif file')
} else {
  console.log('not a gif file')
}      

答案及解析

答案 : 'a gif file'
解析 : 正則的隐式轉換,match方法第一個參數接收一個正規表達式或者一個字元串,但如果是字元串會隐式轉為正則,
  是以上述代碼等同于:'http://giftwrapped.com/picture.jpg'.match(/.gif/)
  而在正則中 點 . 表示通配符,是以成功比對到/gif,比對成功,輸出a gif file。      
6、寫出執行結果,并解釋原因
function user(obj) {
  obj.name = "北京"
  obj = new Object()
  obj.name = "上海"
} 
let person = new Object();
user(person);
console.log(person.name);      

答案及解析

答案 : 北京
解析 : 對象作為參數,傳遞進去的是這個對象的位址,
 1. obj.name是給person這個對象指派;
 2. obj = new Object(),把obj指向另一個對象,
 3. obj.name現在是給這個新對象指派,不影響person這個變量指向的對象;
 4. 兩個obj指向的對象的引用位址不同。
所有函數的參數都是按值傳遞的。
 5. 基本類型的傳遞同基本類型變量的指派一樣,按值傳遞,在函數體内修改參數的值,不會影響到函數外部。
 6. 引用類型的值傳遞同引用類型變量的指派一樣,按引用傳遞,傳入函數的是原始值的位址,是以在函數内部修改參數,将會影響到原始值。      
7、寫出執行結果,并解釋原因
let x, y;
try {
    throw new Error();
} 
catch (x) {
    x = 1;
    y = 2;
    var a = 3


    console.log(a);  // ?
    console.log(x);  // ?
}
console.log(a);  // ?
console.log(x);  // ?
console.log(y);  // ?      

答案及解析

答案 : 3  1  3  undefined  2
解析 : 
  1. catch的作用域,其實并不是常見的塊級作用域,且不能綁定自己的内部聲明的變量(如a)。
  2. catch建立的塊作用域,隻對catch的參數x有效。
  3. 對于在内部聲明的變量,catch并沒有建立一個新的作用域,隻是一個普通的代碼塊。是以塊外仍可通路      
8、寫出執行結果,并解釋原因
function fn() {
    getValue = function () { console.log(1); };
    return this;
}
fn.getValue = function () { console.log(2);};
fn.prototype.getValue = function () {console.log(3);};
var getValue = function () {console.log(4);};
function getValue() {console.log(5);}




getValue();           // ?
fn().getValue();      // ?
getValue();           // ?
new fn.getValue();    // ?
new fn().getValue();  // ?      

答案及解析

答案 : 4  1  1  2  3
考察 : 變量定義提升、this指向、運算符優先級、原型、繼承、全局變量污染、對象屬性及原型屬性優先級
解析 : 為強調重點内容,在下方使用标記語言描述。      

解析:

第一問 getValue():

  • 直接調用,關注點在4,5上:
  • JS存在一種變量聲明被提升的機制,函數聲明會被提升到作用域的最前面,即使寫在最後,也還是會被提升至最前面。
  • 函數表達式和函數聲明的差別,函數聲明解析時會提升,函數表達式的值是在JS運作時确定,并且在表達式指派完成後,該函數才能調用
  • 函數聲明5被函數表達式4覆寫, 輸出4

第二問 fn().getValue():

  • 執行fn函數,調用fn函數傳回值對象的 getValue 屬性函數;
  • 此時 getValue 函數沒有用var進行聲明,已将外層作用域的getValue函數修改;
  • fn函數傳回this,此時函數執行确定this指向window對象,相當于執行window.getValue(),而getValue已經被修改成console.log(1), 輸出1

第三問 getValue():

  • 執行完第6步,getValue函數已被修改,console.log(1), 輸出1

第四問 new fn.getValue():

  • 考察JS的運算符優先級問題,
  • 點的優先級高于new無參數清單,相當于new (fn.getValue())
  • 當點運算完後有個括号(),此時就是變成new有參數清單,優先級高于函數執行,是以直接執行new。這也是為什麼遇到()不先函數調用再new。
  • 最終相當于将 getValue函數,作為構造函數來執行, 輸出2

第五問 new fn().getValue()

  • 這裡帶括号是new 有參數清單,new有參數清單的優先級與點的優先級相同,按從左到右的順序執行。
  • 先執行有參數清單,再執行點的優先級,最後再函數調用
  • fn作為構造函數有傳回值,在JS中構造函數的傳回值可有可無
  1. 沒有傳回值:傳回執行個體化的對象
  2. 有傳回值:檢查其傳回值是否為引用類型
  3. 非引用類型:基本類型則與無傳回值相同,實際傳回其執行個體化對象。
  4. 引用類型:實際傳回值為這個引用類型
  • fn 函數傳回的是this,this在構造函數中本來就代表目前執行個體化對象, 最終fn傳回執行個體化對象。調用對象的getValue方法,而構造函數中沒有getValue,調用原型對象(prototype)上的getValue函數。輸出3。
9、寫出執行結果,并解釋原因
let length = 10;
function fn() {
  console.log(this.length);
}
var obj = {
  length: 5,
  method: function (fn) {
    fn();
    arguments[0]();
  }
};
obj.method(fn, 1);      

答案及解析

答案 : 0  2 
解析 : 為強調重點内容,在下方使用标記語言描述。      

解析

第一問:fn()

  1. 任意函數裡嵌套非箭頭函數,嵌套函數内this 在未指定的情況下,指向的是 window 對象,這裡執行 fn會列印window.length,
  2. let聲明的變量有形成塊級作用域,且不存在聲明提升,length屬性并沒有挂載到window對象中。(test:let a = 1; window.a // undefined)
  3. 此時列印的便是window自帶的length屬性,表示iframe個數,預設為0。輸出0。

第二問:arguments[0]()

  1. arguments類數組是函數參數的引用, arguments[0]指向 fn,
  2. arguments[0]() 是作為 arguments對象的屬性[0]來調用 fn的,誰調用 this 就指向誰;是以 fn 中的 this 指向arguments(對象的屬性調用方法,this指向該對象)
  3. arguments有兩個參數,fn和1,是以argumengts.length = 2 ,輸出2。

擴充

  1. [function fn(){console.log(this.length)}][0](); // 1
  2. 數組也是對象,調用數組對象的0屬性,函數作為數組對象的屬性調用,函數中的this 當然指向這個數組,是以傳回數組的length
10、寫出執行結果,并解釋原因
var a=10;
var foo={
  a:20,
  bar:function(){
      var a=30;
      return this.a;
    }
}
console.log(foo.bar());             // ?
console.log((foo.bar)());           // ?
console.log((foo.bar=foo.bar)());   // ?
console.log((foo.bar,foo.bar)());   // ?      

答案及解析

答案 : 20 20 10 10
解析 : 為強調重點内容,在下方使用标記語言描述。      

本題主要考察this指向問題,推薦閱讀《​​一文搞懂 this、apply、call、bind​​》

解析

第一問 foo.bar()

  • foo調用,this指向foo , 輸出20。

第二問 (foo.bar)()

  • 表達式加了括号,括号的作用是改變表達式的運算順序,而在這加與不加并無影響,相當于foo.bar(), 輸出20。

第三問 (foo.bar=foo.bar)()

  • 等号運算相當于重新給foo.bar定義,相當于一個匿名函數指派給一個全局變量,foo.bar是在window作用域下,this指代的是window,輸出10。
foo.bar = function () {
  var a = 10;
  return this.a;
}      

第四問 (foo.bar,foo.bar)()

  • 逗号運算符求解過程是:先計算表達式1的值,再計算表達式2的值,……一直計算到表達式n的值,最後整個逗号運算符的傳回值是最後一個表達式的值。
  • 經過逗号運算符後,就是純函數,不再是對象方法的引用,是以this指向window,輸出10。
  • 技巧:經過指派,運算符運算後,都是純函數,不是對象方法的引用。函數中this指向都是windows。
11、寫出執行結果,并解釋原因
function getName(){
  return
  {
    name:'Echoyya'
  }
}
console.log(getName());     // ?      

答案及解析

答案 : undefined
解析 : 如果continue、break、return、throw 這四個語句後面,直接跟換行符,則會自動添加分号      
12、寫出執行結果,并解釋原因
const num = parseInt("2*4",10);
console.log(num);      // ?      

答案及解析

答案 : 2
解析 : parseInt會檢查字元串中的字元是否合法. 一旦遇到一個在指定進制(第二個參數)中不合法的字元後,立即停止解析并且忽略後面所有的字元。
    *為非法數字。是以隻解析到 2,并将其解析為十進制的2. 值即為 2      
13、寫出執行結果,并解釋原因
var x = 20;
var temp = {
  x: 40,
  foo: function () {
    var x = 10;
    console.log(this.x);
  }
};
(temp.foo, temp.foo)();    // ?      

答案及解析

答案 : 20
技巧 : 經過指派,運算符運算後,都是純函數,不是對象方法的引用。函數中this指向都是windows。
解析 : 逗号操作符會從左到右計算它的操作數,傳回最後一個操作數的值。是以(temp.foo, temp.foo)();等價于var fun = temp.foo; 
      fun();fun調用時this指向window,是以傳回 20。      
14、寫出執行結果,并解釋原因
const company = { name: "Echoyya" };
Object.defineProperty(company, "address", { value: "北京" });
console.log(company);               // ?
console.log(Object.keys(company));  // ?      

答案及解析

答案 : {name:"Echoyya",address:"北京"},  ["name"]
解析 : defineProperty方法可以給對象添加一個新屬性,或者修改已經存在的屬性。而使用defineProperty給對象添加屬性之後,屬性預設為不可枚舉,
     Object.keys方法僅傳回對象中可枚舉的屬性,是以隻列印name      
15、寫出執行結果,并解釋原因
let num = 10;
const inNum = () => num++;
const inPaNum = number => number++;
const num1 = inNum();
const num2 = inPaNum(num1);


console.log(num1);  // ?
console.log(num2);  // ?      

答案及解析

答案 : 10 10 
解析 : 一進制操作符 ++ 先傳回操作值, 再執行自增操作值。
    1. num1 是10,因為 inNum 函數先傳回 num 的值,在執行 num 自增
    2. num2 是10,因為 num1 作為參數傳入 inPaNum,同理函數先傳回 number 的值,在執行 number 自增      
16、寫出執行結果,并解釋原因
const value = { number: 10 };
const multiply = (x = { ...value }) => {
  console.log(x.number *= 2);
};


multiply();      // ?
multiply();      // ?
multiply(value); // ?
multiply(value); // ?      

答案及解析

答案 : 20 20 20 40
解析 : 
  1. ES6中可以使用參數預設值, 函數未傳參,或參數為undefined,将使用參數預設值。
  2. 解構 value 對象并指派給一個新對象,是以 x 的預設值為 {number:10} 。
  3. 預設參數在調用時才會計算,每次調用函數,都會建立一個新的對象。調用 multiply(),x的預設值都為 {number:10},是以輸出 20 
  4. 調用 multiply(value),實際上修改了 value.number的值,輸出 20
  5. 再次調用調用 multiply(value),value.number之前被修改為 20,是以輸出 40。      
17、寫出執行結果,并解釋原因
// index.js
console.log('running index.js');
import { sum } from './sum.js';
console.log(sum(1, 2));


// sum.js
console.log('running sum.js');
export const sum = (a, b) => a + b;      

答案及解析

答案 : running sum.js,  running index.js,  3
解析 : import指令是編譯階段執行的,在運作之前。是以被導入的子產品會先運作,而導入子產品的檔案會後執行。
    這是CommonJS 中 require() 和 import之間的差別。require()可以在運作代碼時按需加載。
    如果使用 require,那麼running index.js、running sum.js、 3會被依次列印。      
18、寫出執行結果,并解釋原因
function addToList(item, list) {
  return list.push(item);
}
const result = addToList("company", ["yideng"]);
console.log(result);    // ?      

答案及解析

答案 : 2
解析 : push()方法傳回新數組的長度。若想傳回新數組,應該在push之後傳回list。      
19、實作(5).add(3).minus(2) 功能
// 實作 (5).add(3).minus(2) 功能
console.log((5).add(3).minus(2)); // 6      

答案及解析

答案 : 
    Number.prototype.add = function (number) {
        if (typeof number !== 'number') {
            throw new Error('請輸入數字~');
        }
        return this + number;
    };
    Number.prototype.minus = function (number) {
        if (typeof number !== 'number') {
            throw new Error('請輸入數字~');
        }
        return this - number;
    };
    console.log((5).add(3).minus(2));    // 6      
20、不使用模運算符的情況下,檢查一個數是否是偶數
isEven(num)   // true Or false      

答案及解析

答案 : 
1)遞歸方式
  function isEven(num){
    const number = Math.abs(num);   // 取絕對值
    if(number === 1)  return false;
    if(number == 0 )  return true;
    return isEven(number -2);
  }
-------------------------------------------------
2)通過Math.round,利用奇數除以2會有小數的特點
  function isEven(num){
    return parseInt(num/2) === Math.round(num/2);
  }