首頁 web前端 js教程 前端JS面試題

前端JS面試題

Nov 17, 2017 pm 02:45 PM
javascript 前端 試題

前端JS面試題

最近發現了一道前端JS面試題,仔細審這道前端JS面試題後發現還是很有意思的,給大家分享過來。

請實作一個函數,運算結果可以滿足如下預期結果:

add(1)(2) // 3

add(1 , 2, 3)(10) // 16

add(1)(2)(3)(4)(5) // 15

對於一個好奇的切圖仔來說,忍不住動手嘗試了一下,看到題目首先想到的是會用到高階函數以及Array.prototype.reduce()

推薦相關文章2020年最全js面試題整理(最新)

高階函數(Higher-order function):高階函數的意思是它接收另一個函數作為參數。在 javascript 中,函數是一等公民,允許函數作為參數或傳回值傳遞。

得到了下面這個解法:

function add() {
    var args = Array.prototype.slice.call(arguments);
 
    return function() {
        var arg2 = Array.prototype.slice.call(arguments);
        return args.concat(arg2).reduce(function(a, b){
            return a + b;
        });
    }
}
登入後複製

驗證了一下,發現錯了:

add(1)(2) // 3
add(1, 2)(3) // 6
add(1)(2)(3) // Uncaught TypeError: add(...)(...) is not a function(…)
登入後複製

上面的解法,只有在add()() 情形下是正確的。而當鍊式操作的參數多於兩個或少於兩個的時候,無法傳回結果。

而這個也是這題的一個難點所在,add()的時候,如何既回傳一個值又回傳一個函數以供後續繼續呼叫?

後來經過高人指點,透過重寫函數的 valueOf 方法或 toString 方法,可以得到其中一種解法:

function add () {
    var args = Array.prototype.slice.call(arguments);
 
    var fn = function () {
        var arg_fn = Array.prototype.slice.call(arguments);
        return add.apply(null, args.concat(arg_fn));
    }
 
    fn.valueOf = function () {
        return args.reduce(function(a, b) {
            return a + b;
        })
    }
 
    return fn;
}
登入後複製

嗯?第一眼看到這個解法的時候,我是懵逼的。因為我感覺 fn.valueOf() 從頭到尾都沒有被呼叫過,但是驗證了下結果:

add(1) // 1
add(1,2)(3) //6
add(1)(2)(3)(4)(5) // 15
登入後複製

神奇的對了!那麼玄機必然是在上面的 fn.valueOf = function() {} 內了。為何會是這樣呢?這個方法是在函數的什麼時刻執行的?且聽我一步一步道來。

valueOf toString

先來簡單了解下這兩個方法:

Object.prototype.valueOf()
登入後複製

用MDN 的話來說, valueOf() 方法傳回指定物件的原始值。

JavaScript 呼叫 valueOf() 方法用來把物件轉換成原始類型的值(數值、字串和布林值)。但是我們很少需要自己呼叫此函數,valueOf 方法一般都會被 JavaScript 自動呼叫。

記住上面這句話,下面我們會細說所謂的自動呼叫是什麼意思。

Object.prototype.toString()
登入後複製

toString() 方法傳回一個表示該物件的字串。

每個物件都有一個 toString() 方法,當物件被表示為文字值時或以期望字串的方式引用物件時,該方法會自動呼叫。

這裡先記住,valueOf() 和 toString() 在特定的場合下會自行呼叫。

原始類型

好,鋪墊一下,先了解下javascript 的幾個原始類型,除去Object 和Symbol,有以下幾種原始類型:

Number
String
Boolean
Undefined
Null
登入後複製

在JavaScript進行比較或各種運算的時候會把物件轉換成這些類型,以便進行後續的操作,下面逐一說明:

String 類型轉換

在某個操作或運算需要字串而該物件又不是字串的時候,會觸發該物件的String 轉換,會將非字串的類型嘗試自動轉為String 類型。系統內部會自動呼叫 toString 函數。舉個例子:

var obj = {name: 'Coco'};
var str = '123' + obj;
console.log(str);  // 123[object Object]
登入後複製

轉換規則:

如果 toString 方法存在並且傳回原始型別,傳回 toString 的結果。

如果 toString 方法不存在或傳回的不是原始類型,呼叫 valueOf 方法,如果 valueOf 方法存在,並且傳回原始類型數據,傳回 valueOf 的結果。

其他情況,拋出錯誤。

上面的範例其實是:

var obj = {name: 'Coco'};
var str = '123' + obj.toString();
登入後複製

其中,obj.toString() 的值為 "[object Object]"。

假設是陣列:

var arr = [1, 2];
var str = '123' + arr;
 
console.log(str); // 1231,2
登入後複製

上面+ arr ,由於這裡是個字串加上操作,後面的arr 需要轉換成一個字串型,所以其實是呼叫了+ arr.toString( ) 。

但是,我們可以自己改寫物件的toString,valueOf 方法:

var obj = {
    toString: function() {
        console.log('调用了 obj.toString');
        return {};
    },
    valueOf: function() {
        console.log('调用了 obj.valueOf')
        return '110';
    }
}
 
alert(obj);
// 调用了 obj.toString
// 调用了 obj.valueOf
// 110
登入後複製
登入後複製

上面alert(obj + '1') ,obj 會自動呼叫自己的obj.toString() 方法轉換為原始型,如果我們不重寫它的toString 方法,將輸出[object Object]1 ,這裡我們重寫了toString ,而且回傳了一個原始型別字串111 ,所以最終alert 出了1111。

上面的轉換規則寫了,toString 方法需要存在並且傳回原始類型,那麼如果傳回的不是一個原始類型,則會去繼續尋找物件的valueOf 方法:

下面我們嘗試證明如果在一個物件嘗試轉換為字串的過程中,如果toString() 方法不可用的時候,會發生什麼。

這時候系統會再去呼叫 valueOf() 方法,下面我們改寫物件的 toString 和 valueOf:

var obj = {
    toString: function() {
        console.log('调用了 obj.toString');
        return {};
    },
    valueOf: function() {
        console.log('调用了 obj.valueOf')
        return '110';
    }
}
 
alert(obj);
// 调用了 obj.toString
// 调用了 obj.valueOf
// 110
登入後複製
登入後複製

从结果可以看到,当 toString 不可用的时候,系统会再尝试 valueOf 方法,如果 valueOf 方法存在,并且返回原始类型(String、Number、Boolean)数据,返回valueOf的结果。

那么如果,toString 和 valueOf 返回的都不是原始类型呢?看下面这个例子:

var obj = {
    toString: function() {
        console.log('调用了 obj.toString');
        return {};
    },
    valueOf: function() {
        console.log('调用了 obj.valueOf')
        return {};
    }
}
 
alert(obj);
// 调用了 obj.toString
// 调用了 obj.valueOf
// Uncaught TypeError: Cannot convert object to primitive value
登入後複製

可以发现,如果 toString 和 valueOf 方法均不可用的情况下,系统会直接返回一个错误。

在查证了 ECMAScript5 官方文档后,发现上面的描述有一点问题,Object 类型转换为 String 类型的转换规则远比上面复杂。转换规则为:1.设原始值为调用 ToPrimitive 的结果;2.返回 ToString(原始值) 。关于 ToPrimitive 和 ToString 的规则可以看看官方文档:ECMAScript5 — ToString

Number 类型转换

上面描述的是 String 类型的转换,很多时候也会发生 Number 类型的转换:

  • 调用 Number() 函数,强制进行 Number 类型转换

  • 调用 Math.sqrt() 这类参数需要 Number 类型的方法

  • obj == 1 ,进行对比的时候

  • obj + 1 , 进行运算的时候

与 String 类型转换相似,但是 Number 类型刚好反过来,先查询自身的 valueOf 方法,再查询自己 toString 方法:

  • 如果 valueOf 存在,且返回原始类型数据,返回 valueOf 的结果。

  • 如果 toString 存在,且返回原始类型数据,返回 toString 的结果。

其他情况,抛出错误。

按照上述步骤,分别尝试一下:

var obj = {
    valueOf: function() {
        console.log('调用 valueOf');
        return 5;
    }
}
 
console.log(obj + 1);
// 调用 valueOf
// 6
var obj = {
    valueOf: function() {
        console.log('调用 valueOf');
        return {};
    },
    toString: function() {
        console.log('调用 toString');
        return 10;
    }
}
 
console.log(obj + 1);
// 调用 valueOf
// 调用 toString
// 11
var obj = {
    valueOf: function() {
        console.log('调用 valueOf');
        return {};
    },
    toString: function() {
        console.log('调用 toString');
        return {};
    }
}
 
console.log(obj + 1);
// 调用 valueOf
// 调用 toString
// Uncaught TypeError: Cannot convert object to primitive value
登入後複製

Boolean 转换

什么时候会进行布尔转换呢:

布尔比较时

if(obj) , while(obj) 等判断时

简单来说,除了下述 6 个值转换结果为 false,其他全部为 true:

undefined

null

-0

0或+0

NaN

Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false
登入後複製

Function 转换

好,最后回到我们一开始的题目,来讲讲函数的转换。

我们定义一个函数如下:

function test() {
    var a = 1;
    console.log(1);
}
登入後複製

如果我们仅仅是调用 test 而不是 test() ,看看会发生什么?

可以看到,这里把我们定义的 test 函数的重新打印了一遍,其实,这里自行调用了函数的 valueOf 方法:

我们改写一下 test 函数的 valueOf 方法。

test.valueOf = function() {
    console.log('调用 valueOf 方法');
    return 2;
}
 
test;
// 输出如下:
// 调用 valueOf 方法
// 2
登入後複製

与 Number 转换类似,如果函数的 valueOf 方法返回的不是一个原始类型,会继续找到它的 toString 方法:

test.valueOf = function() {
    console.log('调用 valueOf 方法');
    return {};
}
 
test.toString= function() {
    console.log('调用 toString 方法');
    return 3;
}
 
test;
// 输出如下:
// 调用 valueOf 方法
// 调用 toString 方法
// 3
登入後複製

破题

再看回我正文开头那题的答案,正是运用了函数会自行调用 valueOf 方法这个技巧,并改写了该方法。我们稍作改变,变形如下:

function add () {
    console.log('进入add');
    var args = Array.prototype.slice.call(arguments);
 
    var fn = function () {
        var arg_fn = Array.prototype.slice.call(arguments);
        console.log('调用fn');
        return add.apply(null, args.concat(arg_fn));
    }
 
    fn.valueOf = function () {
        console.log('调用valueOf');
        return args.reduce(function(a, b) {
            return a + b;
        })
    }
 
    return fn;
}
登入後複製

当调用一次 add 的时候,实际是是返回 fn 这个 function,实际是也就是返回 fn.valueOf();

add(1);
// 输出如下:
// 进入add
// 调用valueOf
// 1
其实也就是相当于:
[1].reduce(function(a, b) {
    return a + b;
})
// 1
登入後複製

当链式调用两次的时候:

add(1)(2);
// 输出如下:
// 进入add
// 调用fn
// 进入add
// 调用valueOf
// 3
登入後複製

当链式调用三次的时候:

add(1)(2)(3);
// 输出如下:
// 进入add
// 调用fn
// 进入add
// 调用fn
// 进入add
// 调用valueOf
// 6
登入後複製

可以看到,这里其实有一种循环。只有最后一次调用才真正调用到 valueOf,而之前的操作都是合并参数,递归调用本身,由于最后一次调用返回的是一个 fn 函数,所以最终调用了函数的 fn.valueOf,并且利用了 reduce 方法对所有参数求和。

除了改写 valueOf 方法,也可以改写 toString 方法,所以,如果你喜欢,下面这样也可以:

function add () {
    var args = Array.prototype.slice.call(arguments);
 
    var fn = function () {
        var arg_fn = Array.prototype.slice.call(arguments);
        return add.apply(null, args.concat(arg_fn));
    }
 
    fn.toString = function() {
        return args.reduce(function(a, b) {
            return a + b;
        })
    }
 
    return fn;
}
登入後複製

这里有个规律,如果只改写 valueOf() 或是 toString() 其中一个,会优先调用被改写了的方法,而如果两个同时改写,则会像 Number 类型转换规则一样,优先查询 valueOf() 方法,在 valueOf() 方法返回的是非原始类型的情况下再查询 toString() 方法。

后记

在尝试了更多的浏览器之后,发现了上述解法的诸多问题,在 chrome 56 55 下,结果正常。在更新到最新的 chrome57 ,控制台下,结果都会带上 function 字段,在 firefox 下,直接不生效,感觉自己可能陷入了追求某种解法而忽略了一些底层的具体规范,会在彻底弄清楚后给出另一篇文章。

对于类型转换,最好还是看看 ECMAScript 规范,拒绝成为伸手党,自己多尝试。另外评论处有很多人提出了自己的疑问,值得一看。

正如俗话所说:“炫耀从来不是我写作的动机,好奇才是”。此前端JS面试题的解读也是我自己学习的一个过程,过程中我也遇到了很多困惑,所以即便查阅了官方文档及大量的文章,但是错误及疏漏仍然在所难免,欢迎指正及给出更好的方法。

到此本文结束,如果还有什么疑问或者建议,可以多多交流,原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知。

相关推荐:

什麼是JavaScript? JavaScript的用法?

JavaScript代表算術常數e即自然對數的底數的屬性Math.E

JavaScript根據格林威治時間把Date物件轉換為字串並傳回結果toGMTString()

以上是前端JS面試題的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
威爾R.E.P.O.有交叉遊戲嗎?
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

WebSocket與JavaScript:實現即時監控系統的關鍵技術 WebSocket與JavaScript:實現即時監控系統的關鍵技術 Dec 17, 2023 pm 05:30 PM

WebSocket與JavaScript:實現即時監控系統的關鍵技術引言:隨著互聯網技術的快速發展,即時監控系統在各個領域中得到了廣泛的應用。而實現即時監控的關鍵技術之一就是WebSocket與JavaScript的結合使用。本文將介紹WebSocket與JavaScript在即時監控系統中的應用,並給出程式碼範例,詳細解釋其實作原理。一、WebSocket技

PHP與Vue:完美搭檔的前端開發利器 PHP與Vue:完美搭檔的前端開發利器 Mar 16, 2024 pm 12:09 PM

PHP與Vue:完美搭檔的前端開發利器在當今網路快速發展的時代,前端開發變得愈發重要。隨著使用者對網站和應用的體驗要求越來越高,前端開發人員需要使用更有效率和靈活的工具來創建響應式和互動式的介面。 PHP和Vue.js作為前端開發領域的兩個重要技術,搭配起來可以稱得上是完美的利器。本文將探討PHP和Vue的結合,以及詳細的程式碼範例,幫助讀者更好地理解和應用這兩

前端面試官常問的問題 前端面試官常問的問題 Mar 19, 2024 pm 02:24 PM

在前端開發面試中,常見問題涵蓋廣泛,包括HTML/CSS基礎、JavaScript基礎、框架和函式庫、專案經驗、演算法和資料結構、效能最佳化、跨域請求、前端工程化、設計模式以及新技術和趨勢。面試官的問題旨在評估候選人的技術技能、專案經驗以及對行業趨勢的理解。因此,應試者應充分準備這些方面,以展現自己的能力和專業知識。

JavaScript與WebSocket:打造高效率的即時天氣預報系統 JavaScript與WebSocket:打造高效率的即時天氣預報系統 Dec 17, 2023 pm 05:13 PM

JavaScript和WebSocket:打造高效的即時天氣預報系統引言:如今,天氣預報的準確性對於日常生活以及決策制定具有重要意義。隨著技術的發展,我們可以透過即時獲取天氣數據來提供更準確可靠的天氣預報。在本文中,我們將學習如何使用JavaScript和WebSocket技術,來建立一個高效的即時天氣預報系統。本文將透過具體的程式碼範例來展示實現的過程。 We

簡易JavaScript教學:取得HTTP狀態碼的方法 簡易JavaScript教學:取得HTTP狀態碼的方法 Jan 05, 2024 pm 06:08 PM

JavaScript教學:如何取得HTTP狀態碼,需要具體程式碼範例前言:在Web開發中,經常會涉及到與伺服器進行資料互動的場景。在與伺服器進行通訊時,我們經常需要取得傳回的HTTP狀態碼來判斷操作是否成功,並根據不同的狀態碼來進行對應的處理。本篇文章將教你如何使用JavaScript來取得HTTP狀態碼,並提供一些實用的程式碼範例。使用XMLHttpRequest

Django是前端還是後端?一探究竟! Django是前端還是後端?一探究竟! Jan 19, 2024 am 08:37 AM

Django是一個由Python編寫的web應用框架,它強調快速開發和乾淨方法。儘管Django是web框架,但要回答Django是前端還是後端這個問題,需要深入理解前後端的概念。前端是指使用者直接和互動的介面,後端是指伺服器端的程序,他們透過HTTP協定進行資料的互動。在前端和後端分離的情況下,前後端程式可以獨立開發,分別實現業務邏輯和互動效果,資料的交

Go語言前端技術探秘:前端開發新視野 Go語言前端技術探秘:前端開發新視野 Mar 28, 2024 pm 01:06 PM

Go語言作為一種快速、高效的程式語言,在後端開發領域廣受歡迎。然而,很少有人將Go語言與前端開發聯繫起來。事實上,使用Go語言進行前端開發不僅可以提高效率,還能為開發者帶來全新的視野。本文將探討使用Go語言進行前端開發的可能性,並提供具體的程式碼範例,幫助讀者更了解這一領域。在傳統的前端開發中,通常會使用JavaScript、HTML和CSS來建立使用者介面

Django:前端和後端開發都能搞定的神奇框架! Django:前端和後端開發都能搞定的神奇框架! Jan 19, 2024 am 08:52 AM

Django:前端和後端開發都能搞定的神奇框架! Django是一個高效、可擴展的網路應用程式框架。它能夠支援多種Web開發模式,包括MVC和MTV,可以輕鬆地開發出高品質的Web應用程式。 Django不僅支援後端開發,還能夠快速建構出前端的介面,透過模板語言,實現靈活的視圖展示。 Django把前端開發和後端開發融合成了一種無縫的整合,讓開發人員不必專門學習

See all articles