前言
目前的前端世界,三大框架橫行,原生JavaScript所用越來越少,但我認為JavaScript作為每一個前端工程師的立身之本,學再多遍都不為過,
因此我決定整理JavaScript中容易忽視或者混淆的知識點,寫一系列文章,以靈魂拷問的方式,系統且完整的帶大家遨游基礎的的JavaScript,給大家帶來不一樣的體驗,
系列文章鏈接:
- 【面試利器】原生JS靈魂拷問,你能答上多少(一)
JavaScript運算子之問
第十六問 你知道||與&&的回傳值規則嗎?
-
短路效應:
&&和||都會發生短路&&只有在兩個運算元都為true時,條件判斷的結果才為true,如果運算元一為false,不會判斷運算元二,||兩個運算元只要有一個為true,條件判斷的結果就為true,因此運算元一為true時,不會判斷運算元二,
-
回傳值規則
||和&&首先會對運算元一執行條件判斷,如果不是布林值就先強制轉換為布爾型別,然后再執行條件判斷,- 對于
||來說,如果條件判斷結果為true就回傳第一個運算元的值,如果為false就回傳第二個運算元的值, &&則相反,如果條件判斷結果為true就回傳第二個運算元的值,如果為false就回傳第一個運算元的值,||和&&回傳它們其中一個運算元的值,而非條件判斷的結果
-
(2<3)||(3<2)回傳值是多少?
第十七問 1 + - + + + - + 1結果是多少?
+/- 號在 JavaScript 通常有三種用途:
- 普通加減法: 二元運算子
++/--: 自增自減,一元運算子+/-: 正負,一元運算子
上面運算式中沒有涉及自增與自減的情況,一元運算子的優先級大于二元運算子,上述運算式執行順序為:
1 + (- (+ (+ (+ (- (+ 1)))))) ----> 1 + 1 = 2
第十八問 你會用位運算子嗎?
- 判斷奇偶數
二進制的奇數最低位是1,偶數最低位是0,可以通過& 1運算后可以判斷奇偶,
num & 1 === 1 // num 為奇數
num & 1 === 0 // num 為偶數
- 交換a,b的值
可以采用異或來實作兩個變數值的交換
let a = 1
let b = 2
a ^= b
b ^= a
a ^= b
console.log(a) // 2
console.log(b) // 1
-
取整
~~num
第十九問 你能準確的做出自增與自減的題目嗎?
請回答:y 的值為?
var x = 1;
var y = x + ++x + 3 * (x = ++x + x + x++ + 1)
+ x++ + 3;
console.log(y);
咱們來解剖一下這個長的要死的運算式:
第一個x值為1
第二個自增先加后用,x = 2
將 ++x + x + x++ + 1 的運算結果賦值給x
++x 先加后用,x = 3
x = 3
x++ 先用后加,x = 3
++x + x + x++ + 1 = 3 + 3 + 3 + 1 = 10
x++的后加,x = 4
將運算式的值賦給x,x由4變為10
x++ 先用后加,x = 10
此時所有的變數都已經求出
y = 1 + 2 + 3*10 + 10 + 3 = 46
x++,x最終值為11
第二十問 你知道new運算子的有兩種優先級嗎?
MDN 對 new 運算子的描述中,語法是:
new constructor[([arguments])]
([arguments]) 意味著可以預設,會存在 new constructor(...args) 和 new constructor 兩種模式,并且前者的優先級高于后者,更詳細的優先級見下圖:

這個知識點非常重要,只有區分開了 new 帶參串列和不帶參串列,才能準確并且透徹的理解下面這道面試題,
function Foo(){
getName = function(){ console.log(1); };
return this;
}
Foo.getName = function(){ console.log(2); };
Foo.prototype.getName = function(){ console.log(3); };
var getName = function(){ console.log(4); };
function getName(){ console.log(5) };
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
JavaScript字串之問
第二十一問:你知道toString()的妙用嗎?
- 可以轉換數字的進制(
2-36)
// 引數為要轉換的進制
var a = 10;
a.toString(2) // "1010"
a.toString(8) // "12"
a.toString(16) // "a"
- 可以生成隨機驗證碼
利用 toString 的進制轉換,可生成隨機驗證碼
Math.random().toString(36).substring(3,7) //生成四位數的隨機驗證碼
- 判斷資料型別
toString.call(()=>{}) // [object Function]
toString.call({}) // [object Object]
toString.call([]) // [object Array]
toString.call('') // [object String]
toString.call(22) // [object Number]
toString.call(undefined) // [object undefined]
toString.call(null) // [object null]
toString.call(new Date) // [object Date]
toString.call(Math) // [object Math]
toString.call(window) // [object Window]
第二十二問:字串是原始值,那為什么可以呼叫字串方法那?
原始值是沒有屬性也沒有方法的,那為什么字串可以呼叫方法那?
JavaScript 為了便于基本型別操作,提供了三個特殊的參考型別(包裝類),即 Number,String,Boolean ,它們的 [[PrimitiveValue]] 屬性存盤它們的本身值,
光說這些有可能有些難理解,咱們來舉個例子:
var str = 'zcxiaobao'
str2 = str.toUpperCase()
其實js引擎內部會這樣處理:
var str = 'zcxiaobao'
// 呼叫方法,創建String的一個實體
new String(str)
// 呼叫實體上的方法,并將值回傳
str2 = new String(str).toUpperCase()
// 銷毀實體
但這里有一個需要注意的點,new String('1') 和 '1' 型別相同嗎,我們來測驗一下:
var str1 = new String('1');
var str2 = '1';
console.log(str1 === str2); // false
console.log(str1 == str2); // true
console.log(typeof(str1)) // object
// new String()得到的字串為object格式
第二十三問:修改string.length大小能改變字串長度嗎?為什么?
先看一個示例:
var str = '123456';
str.length = 0;
console.log(str, str.length); // 123456 6
很明顯,修改 str.length 是無法做到修改字串的長度的,
原因跟第二十二問是相同的,str 為原始值,呼叫 length 相當用 new String(str).length,修改的是 new String(str) 的 length ,跟原始值 str 無關,
第二十四問:修改new String()生成字串的length會生效嗎?為什么?
如果將上面代碼修改一下,str 是由 new String 產生,修改 length 屬性會生效嗎?
var str = new String('123456');
str.length = 0;
console.log(str, str.length); // String {"123456"} 6
答案告訴我們,還是失敗了,
二十三問中:new String() 生成的字串是物件型別,為啥還不能使用 length 屬性,那說明 length 屬性,很有可能配置了不可寫,測驗一下上述猜想:
Object.getOwnPropertyDescriptor(str, 'length')
/*
{
value: 6,
writable: false,
enumerable: false,
configurable: false
}
*/
由控制臺的列印可知:new String() 生成的字串的 length 屬性不止是不可寫,而且是不可列舉、不可配置的,
第二十五問:你知道如何給數字添加千分符嗎?
千分符:每隔三位數加進一個逗號,也就是千位分隔符,以便更加容易認出數值,最經典的案例就是銀行賬單:
200,100,100.00元,
- 呼叫字串的
toLocaleString()方法
var num = 1450068.12;
console.log(num.toLocaleString()) // 1,450,068.12
但這種方法經過測驗,不管多少小數點后有多少位小數,都只會回傳三位小數,并且經過四舍五入,
var num = 1450068.129999;
console.log(num.toLocaleString()) // 1,450,068.13
- 對字串進行操作,每三位添加一個逗號,
function addThousands(num) {
var result = '',
counter = 0;
[numInter, numDecimal] = (num || 0).toString().split('.');
// 倒序處理整數部分
for (var i = numInter.length - 1; i >= 0; i--) {
counter++;
result = numInter[i] + result;
if (!(counter % 3) && i != 0) {
result = ',' + result;
}
}
// 大家可以補充一下小數部分的代碼
// 小數部分與整數部分類似
return result;
}
console.log(addThousands(42371582378423)) // 42,371,582,378,423
- 正則運算式
該方法實作太過驚天動地了,這里只留答案,剩下的留到正則部分再詳細講述,
function toThousands(num) {
var numStr = (num || 0).toString();
return numStr.replace(/(\d)(?=(?:\d{3})+$)/g, '$1,');
}
第二十六問:你真的理解透 JSON.stringify 了嗎?
JSON.stringfy的基本使用:
JSON.stringify 可以將陣列或者物件轉化成 JSON 字串,JSON.parse 可以將 JSON 字串轉化為陣列或物件,基于這兩個方法,可以產生很多用處,例如:
- 物件的深拷貝(存在缺點,物件那里再詳細說明缺點)
localStorage只能存取字串格式的內容,因此存之前轉換成JSON字串,取出來用時,在轉化成陣列或物件,
- 學透
JSON.stringfy
語法:
JSON.stringify(value[, replacer [, space]])
引數:
value: 將要序列化成 一個JSON字串的值,replacer(可選):- 如果該引數是一個函式,則在序列化程序中,被序列化的值的每個屬性都會經過該函式的轉換和處理;
- 如果該引數是一個陣列,則只有包含在這個陣列中的屬性名才會被序列化到最終的
JSON字串中; - 如果該引數為
null或者未提供,則物件所有的屬性都會被序列化,
space:指定縮進用的空白字串,用于美化輸出
- (重要): 九大特性
特別要注意1、3、5條特性
- 布林值、數字、字串的包裝物件在序列化程序中會自動轉換成對應的原始值,
- 所有以
symbol為屬性鍵的屬性都會被完全忽略掉,即便replacer引數中強制指定包含了它們, - 對包含回圈參考的物件(物件之間相互參考,形成無限回圈)執行此方法,會拋出錯誤,
NaN和Infinity格式的數值及null都會被當做null,undefined、任意的函式以及symbol值:- 出現在非陣列物件的屬性值中時在序列化程序中會被忽略
- 出現在陣列中時會被轉換成
null - 單獨轉換時,會回傳
undefined
- 轉換值如果有
toJSON()方法,該方法定義什么值將被序列化, Date日期呼叫了toJSON()將其轉換為了string字串(同Date.toISOString()),因此會被當做字串處理,- 其他型別的物件,包括
Map/Set/WeakMap/WeakSet,僅會序列化可列舉的屬性 - 非陣列物件的屬性不能保證以特定的順序出現在序列化后的字串中,
JavaScript陣列之問
第二十七問:那些陣列方法會修改原陣列嗎?那些不會?
這是一個比較重要的考點,我在看牛客的前端面試題時,該問題反復出現,
- 改變自身值的方法(共9個)
pop push shift unshift splice sort reverse
// ES6新增
copyWithin
fill
- 不改變自身值的方法
join // 回傳字串
forEach // 無回傳值
concat map filter slice // 回傳新陣列
every some // 回傳布林值
reduce // 不一定回傳什么
indexOf lastIndexOf // 回傳數值(索引值)
// ES6新增
find findIndex
第二十八問:shift 與 unshift 的回傳值是什么?
unshift()在陣列開始處插入元素,shift()洗掉陣列第一個元素- 關于兩者回傳值,直接上測驗代碼:
const arr = [1,2,3,4,5]
undefined
console.log(arr.unshift(0)) // 6
console.log(arr.shift()) // 0
由上述代碼可知:unshift回傳插入元素后的新陣列元素個數,shift回傳洗掉的元素值,
那么可以類比推理一下,push回傳的是插入元素的新陣列元素格式,pop回傳洗掉的元素值,
第二十九問:new Array() 與 Array.of() 的區別是什么?
Array.of()方法創建一個具有可變數量引數的新陣列實體,而不考慮引數的數量或型別,這好像跟new Array的功能極度接近,為什么ES6添加了新方法那?
原因就在于 new Array(n) ,只有一個引數時,構造的并非只含 n 的陣列,而是 1*n 的陣列,值全為 undefined,具體看測驗:
Array.of(7); // [7]
Array.of(1, 2, 3); // [1, 2, 3]
Array(7); // [ , , , , , , ]
Array(1, 2, 3); // [1, 2, 3]
第三十問:你能靈活運用 splice() 方法嗎?splice() 的回傳值是什么?
- 基本使用
語法:
array.splice(start[, deleteCount[, item1[, item2[, ...]]]])
引數:
start?: 指定修改的開始位置(從0計數)deleteCount(可選): 整數,表示要移除的陣列元素的個數,item1, item2...(可選): 添加到陣列的元素
回傳值:由被洗掉的元素組成的一個陣列,沒有洗掉回傳空陣列,
2. 陣列中插入元素
陣列中只提供了在陣列頭尾插入與洗掉的方法,沒有提供在中間位置插入的方法,但可以通過將 deleteCount 設定為0,來實作插入效果,
const arr = [1,2,3,4,5]
console.log(arr.splice(2,0,100)) // []
console.log(arr) // [1, 2, 100, 3, 4, 5]
第三十一問:splice() 洗掉元素時有沒有要注意的地方?
大家看到這個問題,可能會感覺有幾分蒙,沒遇到過應該比較難想到,我來假設一個場景大家就懂了,
現在有這樣一個陣列 [1,-1,2,-1,-5],我想洗掉掉所有的負數,于是我就寫出了下面的代碼:
const arr = [1,-1,-2,1,-5]
for(let i = 0; i<arr.length; i++) {
if (arr[i] < 0) {
arr.splice(i, 1)
}
}
console.log(arr) // [1, -2, 1]
這就奇怪了,為啥 -2 沒被洗掉掉?我在上面的代碼加一句列印,大家應該就懂了,
for(let i = 0; i<arr.length; i++) {
if (arr[i] < 0) {
arr.splice(i, 1)
console.log(a[i]) // -2 undefined
}
}
陣列洗掉 -1 之后,當前的 i 值為 -2,此次遍歷結束,i++,正好空過了 -2 元素,因此如果splice洗掉元素發生在陣列中,一定要注意回呼i的位置,
第三十二問:你會使用 reduce() 嗎?
- 累加累乘
function Accumulation(...vals) {
return vals.reduce((t, v) => t + v, 0);
}
function Multiplication(...vals) {
return vals.reduce((t, v) => t * v, 1);
}
- 代替
reverse
function Reverse(arr = []) {
return arr.reduceRight((t, v) => (t.push(v), t), []);
}
- 陣列扁平化
function Flat(arr = []) {
return arr.reduce((t, v) => t.concat(Array.isArray(v) ? Flat(v) : v), [])
}
- 反轉字串
[..."hello world"].reduce((a, v) => v + a)
[..."hello world"].reverse().join('')
- 驗證括號是否合法
// 這是一個十分巧妙的用法,如果結果等于0說明,括號數量是合法的,
[..."(())()(()())"].reduce((a,i)=> i === '(' ? a+1 : a-1 , 0);
第三十三問:map、filter、every、some 如何使用?
map(): 創建一個新陣列,其結果是該陣列中的每個元素是呼叫一次提供的函式后的回傳值,其中原始陣列不會發生改變,
// 回傳numbers的平方根
var numbers = [1, 4, 9];
var roots = numbers.map(Math.sqrt); // [1, 2, 3]
filter(): 創建一個新陣列, 其包含通過所提供函式實作的測驗的所有元素,
// 篩選排除所有較小的值
function isBigEnough(element) {
return element >= 10;
}
var filtered = [12, 5, 8, 130, 44].filter(isBigEnough); // filtered is [12, 130, 44]
-
some:用于檢測陣列中的元素是否滿足指定條件,- 如果有一個元素滿足條件,則運算式回傳
true, 剩余的元素不會再執行檢測 - 如果沒有滿足條件的元素,則回傳
false
- 如果有一個元素滿足條件,則運算式回傳
// 檢測在陣列中是否有元素大于 10
function isBiggerThan10(element, index, array) {
return element > 10;
}
[2, 5, 8, 1, 4].some(isBiggerThan10); // false
[12, 5, 8, 1, 4].some(isBiggerThan10); // true
-
every: 檢測陣列所有元素是否都符合指定條件.- 如果陣列中檢測到有一個元素不滿足,則整個運算式回傳
false,且剩余的元素不會再進行檢測 - 如果所有元素都滿足條件,則回傳
true
- 如果陣列中檢測到有一個元素不滿足,則整個運算式回傳
// 檢測在陣列中元素是否都大于 10
function isBigEnough(element, index, array) {
return element >= 10;
}
[12, 5, 8, 130, 44].every(isBigEnough); // false
[12, 54, 18, 130, 44].every(isBigEnough); // true
第三十四問:陣列有多少種遍歷方式,各自的效率如何?
第三十五問:如何實作陣列亂序?
Math.random
提到亂序,大家首先會想到使用 Math.random,比如下面代碼:
var values = [1, 2, 3, 4, 5];
values.sort(function(){
return Math.random() - 0.5;
});
console.log(values)
Math.random() - 0.5 隨機得到一個正數,負數或者0,之后通過sort實作亂序,
但這種方法的效果其實并不如人意,具體測驗可參考博客:JavaScript專題之亂序
Fisher–Yates洗牌演算法
function shuffle(a) {
var j, x, i;
for (i = a.length; i; i--) {
j = Math.floor(Math.random() * i);
x = a[i - 1];
a[i - 1] = a[j];
a[j] = x;
}
return a;
}
第三十六問:你知道多少種陣列去重的方法嗎?
- 解法一:使用雙重
for和splice
function unique(arr){
for(var i=0; i<arr.length; i++){
for(var j=i+1; j<arr.length; j++){
if(arr[i]==arr[j]){
//第一個等同于第二個,splice方法洗掉第二個
arr.splice(j,1);
// 洗掉后注意回呼j
j--;
}
}
}
return arr;
}
- 使用
indexOf或includes加新陣列
//使用indexof
function unique(arr) {
var uniqueArr = []; // 新陣列
for (let i = 0; i < arr.length; i++) {
if (uniqueArr.indexOf(arr[i]) === -1) {
//indexof回傳-1表示在新陣列中不存在該元素
uniqueArr.push(arr[i])//是新陣列里沒有的元素就push入
}
}
return uniqueArr;
}
// 使用includes
function unique(arr) {
var uniqueArr = [];
for (let i = 0; i < arr.length; i++) {
//includes 檢測陣列是否有某個值
if (!uniqueArr.includes(arr[i])) {
uniqueArr.push(arr[i])//
}
}
return uniqueArr;
}
sort排序后,使用快慢指標的思想
function unique(arr) {
arr.sort((a, b) => a - b);
var slow = 1,
fast = 1;
while (fast < arr.length) {
if (arr[fast] != arr[fast - 1]) {
arr[slow ++] = arr[fast];
}
++ fast;
}
arr.length = slow;
return arr;
}
sort 方法用于從小到大排序(回傳一個新陣列),其引數中不帶以上回呼函式就會在兩位數及以上時出現排序錯誤(如果省略,元素按照轉換為的字串的各個字符的 Unicode 位點進行排序,兩位數會變為長度為二的字串來計算),
4. ES6 提供的 Set 去重
function unique(arr) {
const result = new Set(arr);
return [...result];
//使用擴展運算子將Set資料結構轉為陣列
}
Set 中的元素只會出現一次,即 Set 中的元素是唯一的,
5. 使用哈希表存盤元素是否出現(ES6 提供的 map)
function unique(arr) {
let map = new Map();
let uniqueArr = new Array(); // 陣列用于回傳結果
for (let i = 0; i < arr.length; i++) {
if(map.has(arr[i])) { // 如果有該key值
map.set(arr[i], true);
} else {
map.set(arr[i], false); // 如果沒有該key值
uniqueArr.push(arr[i]);
}
}
return uniqueArr ;
}
map 物件保存鍵值對,與物件類似,但 map 的鍵可以是任意型別,物件的鍵只能是字串型別,
如果陣列中只有數字也可以使用普通物件作為哈希表,
6. filter 配合 indexOf
function unique(arr) {
return arr.filter(function (item, index, arr) {
//當前元素,在原始陣列中的第一個索引==當前索引值,否則回傳當前元素
//不是那么就證明是重復項,就舍棄
return arr.indexOf(item) === index;
})
}
這里有可能存在疑問,我來舉個例子:
const arr = [1,1,2,1,3]
arr.indexOf(arr[0]) === 0 // 1 的第一次出現
arr.indexOf(arr[1]) !== 1 // 說明前面曾經出現過1
reduce配合includes
function unique(arr){
let uniqueArr = arr.reduce((acc,cur)=>{
if(!acc.includes(cur)){
acc.push(cur);
}
return acc;
},[]) // []作為回呼函式的第一個引數的初始值
return uniqueArr
}
第三十七問:你知道類陣列如何轉化為陣列嗎?
Array.prototype.slice.call()
const arrayLike = {
0: '111',
1: '222',
length: 2
}
console.log(Array.prototype.slice.call(arrayLike)) // ["1", "2"]
Array.from()
Array.from 是 ES6 新增的方法,它可以將**類陣列物件和可遍歷(iterable)**轉變為真正的陣列,
const arrayLike = {
0: '1',
1: '2',
length: 2
}
console.log(Array.from(arrayLike)) // ["1", "2"]
(...)擴展運算子
擴展運算子呼叫的是遍歷器介面,如果一個物件沒有部署此介面就無法完成轉換,
上面咱們自己寫的普通類陣列就無法使用…運算子,
const arrayLike = {
0: '1',
1: '2',
length: 2
}
// Uncaught TypeError: arrayLike is not iterable
console.log([...arrayLike]) // ["1", "2"]
如果部署了遍歷器介面,例如 arguments 類陣列,便可以使用擴展運算子,
function fn() {
console.log([...arguments])
}
fn(1,2,3) // [1, 2, 3]
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/328023.html
標籤:其他
下一篇:vue基礎指令
