JS 类型相关 typeof 没定义的变量会报错吗?typeof let 定义了的呢?
未声明的变量使用 typeof 返回字符串 “undefined”
typeof 一个 let 定义的变量会因为暂时性死区报错 ReferenceError (前提:let/const 未声明之前赋值或使用)
1 2 3 4 5 6 7 8 9 10 11 var tmp = 123 ;if (true ) { tmp = 'abc' ; let tmp; } console .log(typeof tmp); let tmp;let tmp;console .log(typeof tmp);
typeof 的值有哪些 7 种数据类型(返回的都是字符串形式):
string, number, function, object, undefined, boolean, symbol(表独一无二的值)
注意:
null 和 数组 返回的都是 object
NaN 返回的是 number
valueOf 和 toString
toString(): 返回一个反映这个对象的字符串。
valueOf(): 返回它相应的原始值。
字符串 判断一个单词是否是回文 1 2 3 4 var str = 'mamam' ;function checkPalindrom (str ) { return str === str.split('' ).reverse().join('' ); }
验证回文字符串 给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
输入: “A man, a plan, a canal: Panama” 输出: true
输入: “race a car” 输出: false
1 2 3 4 5 6 7 8 9 var str = 'A man, a plan, a canal: Panama' ;var isPalindrome = function (s ) { var reg = /[^A-Za-z0-9]/g ; var tempStr = s.replace(reg, '' ); var reverseStr = tempStr.split('' ).reverse().join('' ); return reverseStr.toLowerCase() === tempStr.toLowerCase(); }; console .log(isPalindrome(str));
统计字符串出现最多的字母 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 var str = 'afjghdfraaaasdenas' ;function findMaxDuplicateChar (str ) { if (str.length == 1 ) return str; let charObj = {}; for (let i = 0 ; i < str.length; i++) { if (!charObj[str.charAt(i)]) { charObj[str.charAt(i)] = 1 ; } else { charObj[str.charAt(i)] += 1 ; } } let maxChar = '' , maxValue = 1 ; for (var key in charObj) { if (charObj[key] >= maxValue) { maxChar = key; maxValue = charObj[key]; } } return maxChar; }
字符串中的第一个唯一字符 给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。
s = “leetcode” 返回 0.
s = “loveleetcode” 返回 2.
1 2 3 4 5 6 7 8 9 10 11 var str = 'leetcode' ;function firstUniqChar (str ) { for (var i = 0 ; i < str.length; i++) { var curChar = str[i]; if (str.lastIndexOf(curChar) === str.indexOf(curChar)) { return i; } } return -1 ; } console .log(firstUniqChar(str));
有效的字母异位词 即判断字符串中是否只有字符的位置不同,也就是判断两个字符串中包含的字符以及这些字符出现的次数是否相同
输入: s = “anagram”, t = “nagaram” 输出: true
输入: s = “rat”, t = “car” 输出: false
1 2 3 4 5 6 7 8 9 var s = 'anagram' , t = 'nagaram' ; function isAnagram (s, t ) { if (s.length !== t.length) return false ; var ss = s.split('' ).sort().join('' ); var tt = t.split('' ).sort().join('' ); return ss === tt ? true : false ; } console .log(isAnagram(s, t));
判断一个给定的字符串是否是同构的
输入: s = “egg”, t = “add” 输出: true 输入: s = “foo”, t = “bar” 输出: false
1 2 3 4 5 6 7 8 9 10 var isIsomorphic = function (s, t ) { var i = 0 ; while (i < s.length) { if (s.indexOf(s[i]) != t.indexOf(t[i])) { return false ; } i++; } return true ; };
报数 报数序列是一个整数序列,按照其中的整数的顺序进行报数,得到下一个数。其前五项如下:
1 2 3 4 5 1. 1 2. 11 3. 21 4. 1211 5. 111221
1
被读作 "one 1"
("一个一"
) , 即 11
。
11
被读作 "two 1s"
("两个一"
), 即 21
。
21
被读作 "one 2"
, “one 1"
("一个二"
, "一个一"
) , 即 1211
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 var countAndSay = function (n ) { var result = '1' ; for (let i = 1 ; i < n; i++) { var repeatCount = 1 ; var str = '' ; for (let j = 0 ; j < result.length; j++) { console .log(result[j], result[j + 1 ]); if (result[j] === result[j + 1 ]) { repeatCount++; } else { str += repeatCount + result[j]; repeatCount = 1 ; } } result = str; } return result; };
最长公共前缀 编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 ""
。
输入: [“flower”,”flow”,”flight”] 输出: “fl”
输入: [“dog”,”racecar”,”car”] 输出: “”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var arr = ['flower' , 'flow' , 'flight' ];function longestCommonPrefix (arr ) { if (!arr.length) return '' ; var firstStr = arr[0 ]; var result = '' ; for (var i = 0 ; i < firstStr.length; i++) { for (var j = 1 ; j < arr.length; j++) { if (firstStr[i] !== arr[j][i]) { return result; } } result += firstStr[i]; } return result; } console .log(longestCommonPrefix(arr));
生成指定长度的随机字母数字字符串 1 2 3 4 5 6 7 function getRandomStr (len ) { var str = '' ; for (var i = 0 ; i < len; i++) { str += Math .random().toString(36 ).substring(2 ); } return str.substring(0 , len); }
如何把一个字符串的大小写取反(大写变小写小写变大写),例如 ’AbC’ 变成 ‘aBc’ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function transformStr (str ) { let tempArr = str.split('' ); let result = tempArr.map(char => { return char === char.toUpperCase() ? char.toLowerCase() : char.toUpperCase(); }); return result.join('' ); } console .log(transformStr('aBc' ));'aBc' .replace(/[A-Za-z]/g , char => char === char.toUpperCase() ? char.toLowerCase() : char.toUpperCase() );
题目来源:Daily-Interview-Question 第 69 题
实现一个字符串匹配算法,从长度为 n 的字符串 S 中,查找是否存在字符串 T,T 的长度是 m,若存在返回所在位置。 1 2 3 4 5 6 7 const find = (S, T ) => { if (S.length < T.length) return -1 ; for (let i = 0 ; i < S.length; i++) { if (S.slice(i, i + T.length) === T) return i; } return -1 ; };
用 JavaScript 写一个函数,输入 int 型,返回整数逆序后的字符串。如:输入整型 1234,返回字符串“4321”。要求必须使用递归函数调用,不能用全局变量,输入函数必须只有一个参数传入,必须返回字符串。 1 2 3 4 5 6 7 8 9 10 11 12 function test (num ) { var str = num + '' ; if (str.length > 1 ) { var newStr = str.substring(str.length - 1 ); var oldStr = str.substring(0 , str.length - 1 ); return newStr + test(oldStr); } else { return str; } } console .log(test(123 ));
找出字符串中连续出现最多的字符和个数 1 2 'abcaakjbb' => {'a' :2 ,'b' :2 }'abbkejsbcccwqaa' => {'c' :3 }
解答:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 const str = 'abcaakjbb' ;function findLongest (str ) { if (!str || typeof str !== 'string' ) return {}; var obj = {}, maxCount = 0 , curCount = 1 ; for (let i = 0 ; i < str.length; i++) { var curChar = str[i]; if (curChar === str[i + 1 ]) { ++curCount; if (maxCount < curCount) { maxCount = curCount; obj = { [curChar]: maxCount }; } if (maxCount === curCount) { obj[curChar] = maxCount; } } else { curCount = 1 ; } } return obj; } console .log(findLongest(str));
数组 判断数组的方法 1 2 3 4 5 6 7 8 9 10 var arr = [1 , 2 , 3 ];arr instanceof Array ; Object .prototype.toString.call(arr) === '[object Array]' ;Array .isArray(arr);arr.constructor === Array ;
JS 数组去重 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 var arr = [1 , 1 , '1' , '1' , NaN , NaN , {}, NaN ];function myDistinct (arr ) { var result = []; for (var i = 0 ; i < arr.length; i++) { if (result.indexOf(arr[i]) === -1 ) { result.push(arr[i]); } } return result; } function myDistinct (arr ) { var result = arr.filter((item, index, arr ) => { return arr.indexOf(item) === index; }); return result; } function myDistinct (arr ) { var obj = {}; return arr.filter((item, index ) => { return obj.hasOwnProperty(typeof item + JSON .stringify(item)) ? false : (obj[typeof item + JSON .stringify(item)] = true ); }); } ~(function ( ) { var arr = [1 , 1 , '1' , '1' , NaN , NaN , {}, NaN ]; var prop = Array .prototype; prop.myDistinct = function myDistinct ( ) { var hash = {}; for (var i = 0 ; i < this .length; i++) { var temp = typeof this [i] + JSON .stringify(this [i]); if (hash[temp]) { this [i] = this [this .length - 1 ]; this .length--; i--; continue ; } hash[temp] = true ; } return this ; }; console .log(arr.myDistinct()); })(); function myDistinct (arr ) { return [...new Set (arr)]; }
两个数组比较,判断是否有相同元素(交集) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 var nums1 = [1 , 2 , 3 , NaN , {}];var nums2 = [2 , 2 , 8 , 10 , NaN , {}];function intersect (nums1, nums2 ) { return nums1.filter(v => { const index = nums2.indexOf(v); if (index > -1 ) { nums2.splice(index, 1 ); return v; } }); } console .log(intersect(nums1, nums2));function intersect (nums1, nums2 ) { const aHasNaN = nums1.some(v => isNaN (v)); const bHasNaN = nums2.some(v => isNaN (v)); return nums1 .filter(v => { const index = nums2.indexOf(v); if (index > -1 ) { nums2.splice(index, 1 ); return v; } }) .concat(aHasNaN && bHasNaN ? [NaN ] : []); } console .log(intersect(nums1, nums2));function intersect (nums1, nums2 ) { const map = {}; const res = []; for (let n of nums1) { if (map[n]) { map[n]++; } else { map[n] = 1 ; } } for (let n of nums2) { if (map[n] > 0 ) { res.push(n); map[n]--; } } return res; } console .log(intersect(nums1, nums2));
如何实现数组的随机排序? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 var arr = [1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ];function result (arr ) { for (let i = 0 ; i < arr.length; i++) { let randomIndex = parseInt (Math .random() * arr.length); let curNum = arr[i]; arr[i] = arr[randomIndex]; arr[randomIndex] = curNum; } return arr; } arr.sort(function ( ) { return Math .random() - 0.5 ; }); console .log(arr);
找出正数组中的最大差值 1 2 3 4 5 6 7 8 9 10 var arr = [10 , 5 , 11 , 7 , 8 , 9 ]; function getMaxProfit (arr ) { var min = (max = arr[0 ]); for (var i = 0 ; i < arr.length; i++) { min = min <= arr[i] ? min : arr[i]; max = max >= arr[i] ? max : arr[i]; } return Math .abs(max - min); }
从排序数组中删除重复项 1 2 3 4 5 6 7 8 9 10 11 12 var nums = [0 , 0 , 1 , 1 , 1 , 2 , 2 , 3 , 3 , 4 ];function removeDuplicates (nums ) { for (var i = 1 ; i < nums.length; i++) { if (nums[i - 1 ] === nums[i]) { nums.splice(i - 1 , 1 ); i--; } } return nums.length; }
找出数组中出现次数最多的元素,并给出其出现过的位置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 var arr = [1 , 1 , 2 , 1 , 10 , 10 , 11 , 1 , 7 ];function fn (arr ) { var bestItem, indexs = [], obj = {}; for (var i = 0 , len = arr.length; i < len; i++) { var item = arr[i].toString(); obj[item] ? obj[item].push(i) : (obj[item] = [].concat([i])); } var tempArr = Object .entries(obj); bestItem = parseInt (tempArr[0 ][0 ]); indexs = tempArr[0 ][1 ]; for (const [key, value] of tempArr) { if (indexs.length < value.length) { bestItem = parseInt (key); indexs = value; } } return { bestItem, indexs }; } console .log(fn(arr));
数组扁平化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function flatten (arr ) { let result = []; for (let i = 0 ; i < arr.length; i++) { if (Array .isArray(arr[i])) { result = result.concat(flatten(arr[i])); } else { result = result.concat(arr[i]); } } return result; } Array .prototype.flat = function ( ) { return [].concat( ...this.map(item => (Array .isArray(item) ? item.flat() : [item])) ); }; const a = [1 , [2 , [3 , 4 ]]];console .log(flatten(a));console .log(a.flat());
将数组扁平化并去除其中重复数据,最终得到一个升序且不重复的数组
已知如下数组:
var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];
编写一个程序将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 Array .prototype.flat = function ( ) { return [].concat( ...this.map(item => (Array .isArray(item) ? item.flat() : [item])) ); }; Array .prototype.unique = function ( ) { return [...new Set (this )]; }; const sort = (a, b ) => a - b;console .log(arr.flat().unique().sort(sort));Array .prototype.flat = function ( ) { return this .toString() .split(',' ) .map(v => +v); }; Array .prototype.unique = function ( ) { var obj = {}; return this .filter((item, index ) => { var tempItem = typeof item + JSON .stringify(item); return obj.hasOwnProperty(tempItem) ? false : (obj[tempItem] = true ); }); }; const sort = (a, b ) => a - b;console .log(arr.flat().unique().sort(sort));
使用迭代的方式实现 flatten 函数
迭代实现
1 2 3 4 5 6 7 8 let arr = [1 , 2 , [3 , 4 , 5 , [6 , 7 ], 8 ], 9 , 10 , [11 , [12 , 13 ]]];const flatten = function (arr ) { while (arr.some(v => Array .isArray(v))) { arr = [].concat(...arr); } return arr; };
递归实现
1 2 3 const flatten = function (arr ) { return [].concat(...arr.map(v => (Array .isArray(v) ? flatten(v) : [v]))); };
字符串转换
1 2 3 4 5 6 function flatten (arr ) { return arr .join(',' ) .split(',' ) .map(v => Number (v)); }
买卖股票的最佳时机 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意 :你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
输入: [7,1,5,3,6,4] 输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
1 2 3 4 5 6 7 8 9 10 11 12 var arr = [7 , 1 , 5 , 3 , 6 , 4 ];function maxProfit (arr ) { var income = 0 ; for (var i = 1 , len = arr.length; i < len; i++) { var gap = arr[i] - arr[i - 1 ]; if (gap > 0 ) { income += gap; } } return income; } console .log(maxProfit(arr));
旋转数组 给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。
输入: [1,2,3,4,5,6,7] 和 k = 3 输出: [5,6,7,1,2,3,4]
解释:
向右旋转 1 步: [7,1,2,3,4,5,6]
向右旋转 2 步: [6,7,1,2,3,4,5]
向右旋转 3 步: [5,6,7,1,2,3,4]
1 2 3 4 5 6 7 8 9 10 11 var arr = [1 , 2 , 3 , 4 , 5 , 6 , 7 ];function rotate (arr, k ) { for (var i = 0 ; i < k; i++) { var temp = arr.pop(); arr.unshift(temp); } return arr; } console .log(rotate(arr, 3 ));
判断数组是否存在重复 给定一个整数数组,判断是否存在重复元素。
如果任何值在数组中出现至少两次,函数返回 true。如果数组中每个元素都不相同,则返回 false。
输入: [1,2,3,1] 输出: true
输入: [1,2,3,4] 输出: false
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 var arr = [1 , 2 , 3 , 1 ];function containsDuplicate (arr ) { var obj = {}; for (var i = 0 , len = arr.length; i < len; i++) { if (obj[arr[i]]) { return true ; } else { obj[arr[i]] = true ; } } return false ; } function containsDuplicate (arr ) { return [...new Set (arr)].length !== arr.length; } console .log(containsDuplicate(arr));
找出只出现一次的数字 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 var arr = [4 , 1 , 2 , 1 , 2 ];function singleNumber (arr ) { var tempArr = arr; tempArr.sort(); var onceNumber = null ; for (var i = 0 , len = tempArr.length - 1 ; i < len; i++) { if (tempArr[i] !== tempArr[i + 1 ]) { onceNumber = tempArr[i]; } else { ++i; } } return onceNumber !== null ? onceNumber : tempArr[tempArr.length - 1 ]; } function singleNumber (arr ) { const a = [...new Set (arr)].reduce((total, cur ) => total + cur, 0 ); const b = arr.reduce((total, cur ) => total + cur, 0 ); return 2 * a - b; } console .log(singleNumber(arr));
加一 给定一个由整数 组成的非空 数组所表示的非负整数,在该数的基础上加一。
最高位数字存放在数组的首位, 数组中每个元素只存储一个数字。
你可以假设除了整数 0 之外,这个整数不会以零开头。
示例 1:
1 2 3 输入: [1,2,3] 输出: [1,2,4] 解释: 输入数组表示数字 123。
示例 2:
1 2 3 输入: [4,3,2,1] 输出: [4,3,2,2] 解释: 输入数组表示数字 4321。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 var arr = [9 , 9 , 9 ]; function plusOne (arr ) { var count = arr.length - 1 ; while (count > -1 ) { if (arr[count] + 1 > 9 ) { arr[count] = 0 ; count--; } else { arr[count]++; break ; } } if (arr[0 ] === 0 ) { arr.unshift(1 ); } return arr; } console .log(plusOne(arr));
移动零 给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
示例:
1 2 输入: [0,1,0,3,12] 输出: [1,3,12,0,0]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var arr = [0 , 1 , 0 , 3 , 12 ]; var moveZeroes = function (nums ) { var len = nums.length; for (var i = 0 ; i < len; i++) { if (nums[i] === 0 ) { nums.splice(i, 1 ); nums.push(0 ); i--; len--; } } return nums; }; console .log(moveZeroes(arr));
某公司 1 到 12 月份的销售额存在一个对象里面 如下:{1:222, 2:123, 5:888},请把数据处理为如下结构:[222, 123, null, null, 888, null, null, null, null, null, null, null]。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let obj = { 1 : 222 , 2 : 123 , 5 : 888 };function fn (obj ) { let arr = []; for (let i = 1 ; i <= 12 ; i++) { obj[i] ? arr.push(obj[i]) : arr.push(null ); } return arr; } console .log(fn(obj));let obj = { 1 : 222 , 2 : 123 , 5 : 888 };const result = Array .from({ length : 12 }).map( (_, index) => obj[index + 1 ] || null ); console .log(result);
两数之和 给定一个整数数组 nums
和一个目标值 target
,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
示例:
1 2 3 4 给定 nums = [2, 7, 11, 15], target = 9 因为 nums[0] + nums[1] = 2 + 7 = 9 所以返回 [0, 1]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var nums = [2 , 7 , 11 , 15 ], target = 9 ; function twoSum (nums, target ) { for (let i = 0 ; i < nums.length; i++) { const num = nums[i]; const targetIndex = nums.indexOf(target - num); if (targetIndex > -1 && targetIndex !== i) { return [i, targetIndex]; } } } console .log(twoSum(nums, target));
旋转图像 给定一个 n × n 的二维矩阵表示一个图像。
将图像顺时针旋转 90 度。
说明:
你必须在原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
示例 1:
1 2 3 4 5 6 7 8 9 10 11 12 13 给定 matrix = [ [1,2,3], [4,5,6], [7,8,9] ], 原地旋转输入矩阵,使其变为: [ [7,4,1], [8,5,2], [9,6,3] ]
示例 2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 给定 matrix = [ [ 5, 1, 9,11], [ 2, 4, 8,10], [13, 3, 6, 7], [15,14,12,16] ], 原地旋转输入矩阵,使其变为: [ [15,13, 2, 5], [14, 3, 4, 1], [12, 6, 8, 9], [16, 7,10,11] ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 var matrix = [ [5 , 1 , 9 , 11 ], [2 , 4 , 8 , 10 ], [13 , 3 , 6 , 7 ], [15 , 14 , 12 , 16 ] ]; function rotate (arr ) { for (var i = 0 ; i < arr.length; i++) { for (var j = i + 1 ; j < arr.length; j++) { var tempArr = arr[j][i]; arr[j][i] = arr[i][j]; arr[i][j] = tempArr; } arr[i].reverse(); } return arr; } console .log(rotate(matrix));
如何将浮点数点左边的数每三位添加一个逗号,如 12000000.11 转化为『12,000,000.11』? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 var num = 12000000.11 ;num.toLocaleString(); function toThousands (num ) { if (typeof num !== 'number' ) return 0 ; if (num < 0 ) { flag = '-' ; num = Math .abs(num); } var arr = num.toString().split('.' ); var left = [...arr[0 ]]; var right = '' ; if (arr.length > 1 ) { right = '.' + arr[1 ]; } var count = left.length - 1 ; while (count > 0 ) { if (count % 3 === 0 ) { left.splice(-count, 0 , ',' ); } count--; } return flag + left.join('' ) + right; } function toThousands (num ) { return ( num && num.toString().replace(/(\d)(?=(\d{3})+\.)/g , function ($1 , $2 ) { return $2 + ',' ; }) ); }
两个数组合并成一个数组 请把两个数组 ['A1', 'A2', 'B1', 'B2', 'C1', 'C2', 'D1', 'D2']
和 ['A', 'B', 'C', 'D']
,合并为 ['A1', 'A2', 'A', 'B1', 'B2', 'B', 'C1', 'C2', 'C', 'D1', 'D2', 'D']
。
考察点:假设有一种情况,让你在一个列表中插入一个广告,不光是数组,对象依然有这种需求,这道题其实就是平常经常需要用到的一个小功能。
1 2 3 4 5 6 7 8 9 10 11 let arr1 = ['A1' , 'A2' , 'B1' , 'B2' , 'C1' , 'C2' , 'D1' , 'D2' ];let arr2 = ['A' , 'B' , 'C' , 'D' ];console .log( [...arr1, ...arr2].sort( (v2, v1) => v2.codePointAt(0 ) - v1.codePointAt(0 ) || v1.length - v2.length || v2.codePointAt(1 ) - v1.codePointAt(1 ) ) );
第一个条件 v2.codePointAt(0) - v1.codePointAt(0) 保证了所有已 A 开头的字符串会放在最前边,然后依次是 B 和 C。
第二个条件 v1.length - v2.length 保证 A 会被放在 A1 和 A2 之后。
第三个条件 v2.codePointAt(1) - v1.codePointAt(1)保证了 A1 会被放在 A2 前边。
这是我挑的一个比较好的答案,更多解法可在此 查看
随机生成一个长度为 10 的整数类型的数组,例如 [2, 10, 3, 4, 5, 11, 10, 11, 20],将其排列成一个新数组,要求新数组形式如下,例如 [[2, 3, 4, 5], [10, 11], [20]]。 解析:由于题意没有说清楚,到底是按「数字区间」排还是按照「连续数字」排,所以两种都写出来供参考
答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 function randomArr (min, max ) { return Math .round(Math .random() * (max - min) + min); } let initArr = Array .from({ length : 10 }, () => randomArr(20 , 100 ));initArr = [...new Set (initArr)]; initArr.sort((a, b ) => a - b); function createTargetArr (initArr ) { let obj = {}; initArr.map(num => { const initNum = Math .floor(num / 10 ); if (!obj[initNum]) obj[initNum] = []; obj[initNum].push(num); }); let result = []; for (const key in obj) { result.push(obj[key]); } return result; } console .log(createTargetArr(initArr));function createTargetArr (initArr ) { let continueArr = [], tempArr = []; console .log(initArr); initArr.map((e, index ) => { tempArr.push(e); if (initArr[index + 1 ] !== ++e) { continueArr.push(tempArr); tempArr = []; } }); return continueArr; } console .log(createTargetArr(initArr));
题目来源:Daily-Interview-Question 第 67 题
打印出 1 - 10000 之间的所有对称数 例如:121、1331 等 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 function symmetry (begin, end ) { let result = []; for (let i = begin > 10 ? begin : 11 ; i < end; i++) { let str = i + '' ; if (str === str.split('' ).reverse().join('' )) { result.push(str); } } return result; } console .log(symmetry(11 , 100 ));function symmetry ( ) { var arr = []; for (let i = 1 ; i < 10 ; i++) { arr.push(i * 11 ); for (let j = 0 ; j < 10 ; j++) { arr.push(i * 101 + j * 10 ); arr.push(i * 1001 + j * 110 ); } } return arr; } console .log(symmetry());
题目来源:Daily-Interview-Question 第 81 题
实现 convert 方法,把原始 list 转换成树形结构,要求尽可能降低时间复杂度 以下数据结构中,id 代表部门编号,name 是部门名称,parentId 是父部门编号,为 0 代表一级部门,现在要求实现一个 convert 方法,把原始 list 转换成树形结构,parentId 为多少就挂载在该 id 的属性 children 数组下,结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 let list = [ {id :1 ,name :'部门A' ,parentId :0 }, {id :2 ,name :'部门B' ,parentId :0 }, {id :3 ,name :'部门C' ,parentId :1 }, {id :4 ,name :'部门D' ,parentId :1 }, {id :5 ,name :'部门E' ,parentId :2 }, {id :6 ,name :'部门F' ,parentId :3 }, {id :7 ,name :'部门G' ,parentId :2 }, {id :8 ,name :'部门H' ,parentId :4 } ]; const result = convert(list, ...);let result = [ { id: 1 , name: '部门A' , parentId: 0 , children: [ { id: 3 , name: '部门C' , parentId: 1 , children: [ { id: 6 , name: '部门F' , parentId: 3 }, { id: 16 , name: '部门L' , parentId: 3 } ] }, { id: 4 , name: '部门D' , parentId: 1 , children: [ { id: 8 , name: '部门H' , parentId: 4 } ] } ] }, ··· ];
解答:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 function convert (list ) { function fn (pid ) { return list.filter(dep => { if (dep.parentId === pid) { let children = fn(dep.id); dep.children = children.length ? children : null ; return true ; } }); } return fn(0 ); } function convert (arr ) { var obj = {}; for (const item of arr) { obj[item.id] = item; } var result = []; for (const item of arr) { if (item.parentId === 0 ) { result.push(item); continue ; } if (item.parentId in obj) { let parent = obj[item.parentId]; parent.children = parent.children || []; parent.children.push(item); } } return result; }
题目来源:Daily-Interview-Question 第 88 题
编程题,请写一个函数,完成以下功能 1 2 输入:'1,2,3,5,7,8,10' 输出:'1~3,5,7~8,10'
解答:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 let str = '1,2,3,5,7,8,10,11' ;function fn (str ) { let arr = str.split(',' ).map(v => +v); let continueNum = arr[0 ]; let result = []; for (let i = 0 ; i < arr.length; i++) { let curNum = arr[i], nextNum = arr[i + 1 ]; if (curNum + 1 !== nextNum) { if (curNum !== continueNum) { result.push(continueNum + '~' + curNum); } else { result.push(curNum); } continueNum = nextNum; } } return result.join(',' ); } console .log(fn(str));
补全代码,实现数组按姓名、年纪、任意字段排序。 1 2 3 4 5 6 7 var users = [ { name : 'John' , age : 20 , company : 'Baidu' }, { name : 'Pete' , age : 18 , company : 'Alibaba' }, { name : 'Ann' , age : 19 , company : 'Tecent' } ]; users.sort(byField('age' )); users.sort(byField('company' ));
解答:
1 2 3 4 5 6 7 8 9 10 11 12 var users = [ { name : 'John' , age : 20 , company : 'Baidu' }, { name : 'Pete' , age : 18 , company : 'Alibaba' }, { name : 'Ann' , age : 19 , company : 'Tecent' } ]; var byField = function (key ) { return (a, b ) => a[key] > b[key]; }; users.sort(byField('age' )); console .log(users);users.sort(byField('company' )); console .log(users);
题目来源: 2019 前端面试 | “HTML + CSS + JS”专题 「JavaScript 基础」部分编号 js_12
取数组的最大值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function max (arr ) { if (arr.length < 1 ) return undefined ; return Math .max.apply(null , arr); } function max (arr ) { if (arr.length < 1 ) return undefined ; return Math .max(...arr); } function max (arr ) { if (arr.length < 1 ) return undefined ; return arr.reduce((previous, current ) => { return previous > current ? previous : current; }); } console .log(max([1 , 22 , 3435 , 321 , 9990 , 100001 , 2133 ]));
实现一个 reduce 函数 解析:reduce 是归并方法,它并不对每一项执行目标函数,简单来说概括为如下步骤:
不断地对数组的前两项“取出”,对其执行目标函数,计算得到的返回值
把上述返回值“填回”数组首部,作为新的 array[0]
持续循环执行这个过程,直到数组中每一项都访问了一次
返回最终结果
举个例子 🌰
对数组 [1,2,3] 归并执行 (prev, cur) => prev + cur
,流程如图:
答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function reduce (arr, fn, initValue ) { const tempArr = (initValue === undefined ? [] : [initValue]).concat(arr); while (tempArr.length > 1 ) { tempArr.splice(0 , 2 , fn(tempArr[0 ], tempArr[1 ])); } return tempArr[0 ]; } var sum = reduce( [1 , 2 , 3 ], function (previous, current ) { return previous + current; }, 0 ); console .log(sum);
写法 2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function reduce (arr, reduceCallback, initialValue ) { if (!Array .isArray(arr) || !arr.length || typeof reduceCallback !== 'function' ) { return []; } else { let hasInitialValue = initialValue !== undefined ; let value = hasInitialValue ? initialValue : arr[0 ]; 、 for (let i = hasInitialValue ? 0 : 1 , len = arr.length; i < len; i++) { value = reduceCallback(value, arr[i], i, arr); } return value; } }
手动实现 Array.prototype.map 方法 map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function map (arr, mapCallback ) { if (!Array .isArray(arr) || !arr.length || typeof mapCallback !== 'function' ) { return []; } else { let result = []; for (let i = 0 , len = arr.length; i < len; i++) { result.push(mapCallback(arr[i], i, arr)); } return result; } }
手动实现 Array.prototype.filter 方法 filter()方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function filter (arr, filterCallback ) { if ( !Array .isArray(arr) || !arr.length || typeof filterCallback !== 'function' ) { return []; } else { let result = []; for (let i = 0 , len = arr.length; i < len; i++) { if (filterCallback(arr[i], i, arr)) { result.push(arr[i]); } } return result; } }
随机数 / 数字 如何获取 0-9 的随机数
Math.round(num)**:将 **num 四舍五入取整
Math.floor(num)**:将 **num 向下取整,即返回 num 的整数部分。当然我们也可以使用 parseInt() 方法代替。
**Math.ceil(num)**:向上取整
1 2 3 4 5 6 7 8 return Math .round(Math .random() * (max - min - 2 ) + min + 1 );return Math .round(Math .random() * (max - min) + min);return Math .ceil(Math .random() * (max - min) + min);return Math .floor(Math .random() * (max - min) + min);
随机获取数组中的元素 1 2 3 4 5 6 7 8 9 var arr = ['前端' , '后端' , '全栈' ];function fn (arr ) { return arr[parseInt (Math .random() * arr.length)]; } var i = 0 ;while (i < 10 ) { console .log(fn(arr)); i++; }
打乱数组顺序 1 2 3 4 5 6 7 8 9 10 var arr = [1 , 2 , 3 , 4 , 5 , 6 , 7 , 'a' , 'dsfs' , 8 , 9 , 'v' ];function fn (arr ) { for (var i = 0 ; i < arr.length; i++) { var randomIndex = parseInt (Math .random() * arr.length); var temp = arr[i]; arr[i] = arr[randomIndex]; arr[randomIndex] = temp; } return arr; }
保留指定小数位 1 2 var num = 4.345678 ;num = num.toFixed(4 );
前端价格展示,保留 2 位小数。位数不够补零 1 2 3 function priceFormat (price ) { return parseFloat (Math .round(price * 100 ) / 100 ).toFixed(2 ); }
如何将字符串转化为数字,例如’12.3b’?
如何检查一个数字是否为整数?
将它对 1 进行取模,看看是否有余数。
1 2 3 4 5 6 7 function isInt (num ) { return num % 1 === 0 ; } console .log(isInt(4 )); console .log(isInt(12.2 )); console .log(isInt(0.3 ));
为什么 0.1+0.2 不等于 0.3?在什么场景下遇到这个问题,如何解决?
二进制模拟十进制进行计算时 的精度问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function numbersEqual (a, b ) { return Math .abs(a - b) < Number .EPSILON; } var a = 0.1 + 0.2 , b = 0.3 ; console .log(numbersEqual(a, b)); function formatNum (num, fixed = 10 ) { return parseFloat (a.toFixed(fixed)); } var a = 0.1 + 0.2 ;console .log(formatNum(a))( 0.1 + 0.2 ) .toPrecision(10 ) == 0.3 ;
参考:
写一个函数,生成一个随机颜色字符串,合法的颜色为 #000000 ~ #ffffff 1 2 3 4 5 6 7 8 9 10 11 function getRandColor ( ) { const dict = '0123456789abcdef' ; let result = '#' ; for (let i = 0 ; i < 6 ; i++) { result += dict[Math .floor(Math .random() * dict.length)]; } return result; } var color = getRandColor();console .log(color);
写一个函数,生成一个随机 IP 地址,一个合法的 IP 地址为 0.0.0.0 ~ 255.255.255.255。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function getRandIP ( ) { return Array .from({ length : 4 }).reduce((ip, cur, idx ) => { const randomIdx = Math .floor(Math .random() * 256 ); return idx === 3 ? ip + randomIdx : ip + randomIdx + '.' ; }, '' ); } var ip = getRandIP();console .log(ip);function getRandIP ( ) { let ip = '' ; for (let i = 0 ; i < 3 ; i++) { ip = ip + Math .floor(Math .random() * 256 ) + '.' ; } return ip + Math .floor(Math .random() * 256 ); } var ip = getRandIP();console .log(ip);
题目来源:2019 前端面试 | “HTML + CSS + JS”专题 「《JS 提供的对象:④ Math》[编号:js_19]」
函数 实现柯里化 预先设置一些参数
柯里化是什么:是指这样一个函数,它接收函数 A,并且能返回一个新的函数,这个新的函数能够处理函数 A 的剩余参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function createCurry (func, args ) { var argity = func.length; var args = args || []; return function ( ) { var _args = [].slice.apply(arguments ); args.push(..._args); if (args.length < argity) { return createCurry.call(this , func, args); } return func.apply(this , args); }; }
this 手写 bind、apply、call call 1 2 3 4 5 6 7 8 9 Function .prototype.call = function (context, ...args ) { context = context || window ; const fnSymbol = Symbol ('fn' ); context[fnSymbol] = this ; context[fnSymbol](...args); delete context[fnSymbol]; };
apply 1 2 3 4 5 6 7 8 9 Function .prototype.apply = function (context, argsArr ) { context = context || window ; const fnSymbol = Symbol ('fn' ); context[fnSymbol] = this ; context[fnSymbol](...argsArr); delete context[fnSymbol]; };
bind 1 2 3 4 5 6 7 8 9 10 11 12 Function .prototype.bind = function (context, ...args ) { context = context || window ; const fnSymbol = Symbol ('fn' ); context[fnSymbol] = this ; return function (..._args ) { _args = _args.concat(args); context[fnSymbol](..._args); delete context[fnSymbol]; }; };
闭包 如下代码输出多少?如果想输出 3,那如何改造代码? 1 2 3 4 5 6 7 var fnArr = [];for (var i = 0 ; i < 10 ; i++) { fnArr[i] = function ( ) { return i; }; } console .log(fnArr[3 ]());
答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 var fnArr = [];for (var i = 0 ; i < 10 ; i++) { fnArr[i] = (function (j ) { return function ( ) { return j; }; })(i); } console .log(fnArr[3 ]()); var fnArr = [];for (var i = 0 ; i < 10 ; i++) { (function (i ) { fnArr[i] = function ( ) { return i; }; })(i); } console .log(fnArr[3 ]()); var fnArr = [];for (let i = 0 ; i < 10 ; i++) { fnArr[i] = function ( ) { return i; }; } console .log(fnArr[3 ]());
题目来源: 2019 前端面试 | “HTML + CSS + JS”专题 的 「JavaScript 基础」部分,编号[js_11]中第 5 题。
对象 & 原型 & 原型链 写一下浅/深拷贝
深拷贝和浅拷贝针对的是引用类型,JS 中的变量类型分为值类型(基本类型)和引用类型;对值类型进行复制操作会对值进行一份拷贝,而对引用类型复制,则会进行地址的拷贝,最终两个变量指向同一份数据。对于引用类型,会导致 a b 指向同一份数据,此时如果对其中一个进行修改,就会影响到另外一个,有时候这可能不是我们想要的结果。
浅拷贝 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function copy (obj ) { if (!obj || typeof obj !== 'object' ) return ; var newObj = obj.constructor === Array ? [] : {}; for (var key in obj) { newObj[key] = obj[key]; } return newObj; } var a = { b : 'bb' , c : 'cc' , d : { e : 'ee' } };var b = copy(a);console .log(b); var a = { a : 'old' , b : { c : 'old' } };var b = Object .assign({}, a);b.a = 'new' ; b.b.c = 'new' ; console .log(a); console .log(b);
深拷贝 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 var obj1 = { a : { name : '小红' }, b : 2 };var obj2 = JSON .parse(JSON .stringify(obj1));obj1.a.name = '被修改了' ; obj2; function copy (obj ) { if (!obj || typeof obj !== 'object' ) return obj; var newObj = obj.constructor === Array ? [] : {}; for (var key in obj) { if (obj.hasOwnProperty(key)) { if (obj[key] === obj) { continue ; } newObj[key] = typeof obj[key] === 'object' ? copy(obj[key]) : obj[key]; } } return newObj; } var old = { a : 'old' , b : { c : 'old' } };var newObj = copy(old);newObj.b.c = 'new' ; console .log(old); console .log(newObj);
参考:
拓展:编程题,写个程序把 entry 转换成如下对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 var entry = { a: { b: { c: { dd: 'abcdd' } }, d: { xx: 'adxx' }, e: 'ae' } }; var output = { 'a.b.c.dd' : 'abcdd' , 'a.d.xx' : 'adxx' , 'a.e' : 'ae' };
解答:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function fn (obj, parentKey = '' , result = {} ) { for (const key in obj) { if (obj.hasOwnProperty(key)) { const ele = obj[key]; const keyName = `${parentKey} ${key} ` ; if (typeof ele === 'object' && !Array .isArray(ele)) { fn(ele, keyName + '.' , result); } else { result[keyName] = ele; } } } return result; } console .log(fn(entry));
JavaScript 中,有一个函数,执行时对象查找时,永远不会去查找原型,这个函数是? hasOwnProperty
hasOwnProperty 返回一个布尔值,指出一个对象是否具有指定名称的属性。此方法不会检查该对象的原型链中是否具有该属性;该属性必须是对象本身的一个成员。
使用方法: object.hasOwnProperty(proName) 其中参数 object 是必选项,一个对象的实例;proName 是必选项。一个属性名称的字符串值。 如果 object 具有指定名称的属性,那么 JavaScript 中 hasOwnProperty 函数方法返回 true,反之则返回 false。
instanceof 的实现原理
思路:只要右边变量的 prototype 在左边变量的原型链上即可。
思路
步骤 1:先取得当前类的原型,当前实例对象的原型链
步骤 2:一直循环(执行原型链的查找机制)
取得当前实例对象原型链的原型链(proto = proto.____proto____
,沿着原型链一直向上查找)
如果 当前实例的原型链____proto__
上找到了当前类的原型prototype
,则返回 true
如果 一直找到Object.prototype.____proto____ == null
,Object 的基类(null)上面都没找到,则返回 false
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function _instanceof (instanceObject, classFunc ) { let classFunc = classFunc.prototype; let proto = instanceObject.__proto__; while (true ) { if (proto === null ) { return false ; } if (proto === classFunc) { return true ; } proto = proto.__proto__; } }
优化版 (处理兼容问题)
Object.getPrototypeOf ( ): 用来获取某个实例对象的原型(内部**[[prototype]]属性的值,包含 proto**属性)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function _instanceof (instanceObject, classFunc ) { let classFunc = classFunc.prototype; let proto = Object .getPrototypeOf(instanceObject); while (true ) { if (proto === null ) { return false ; } if (proto === classFunc) { return true ; } proto = Object .getPrototypeOf(proto); } }
参考:浅谈 instanceof 和 typeof 的实现原理
要求设计 LazyMan 类,实现以下功能 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 LazyMan('Tony' ); LazyMan('Tony' ).sleep(10 ).eat('lunch' ); LazyMan('Tony' ).eat('lunch' ).sleep(10 ).eat('dinner' ); LazyMan('Tony' ) .eat('lunch' ) .eat('dinner' ) .sleepFirst(5 ) .sleep(10 ) .eat('junk food' );
答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class LazyManClass { constructor (name ) { this .name = name; this .task = []; console .log('Hi I am' , name); setTimeout (() => { this .next(); }, 0 ); } eat (str ) { this .task.push(() => { console .log('I am eating' , str); this .next(); }); return this ; } sleep (n ) { this .task.push(() => { setTimeout (() => { console .log('等待了' + n + 's' ); this .next(); }, n * 1000 ); }); return this ; } sleepFirst (n ) { this .task.unshift(() => { setTimeout (() => { console .log('等待了' + n + 's' ); this .next(); }, n * 1000 ); }); return this ; } next ( ) { let fn = this .task.shift(); fn && fn(); } } let LazyMan = function (name ) { return new LazyManClass(name); };
解析:
这道题的关键点有如下几个:
链式调用,通过返回 this 实现
内部需要维护一个 taskList ,根据不同逻辑,向 taskList 中 push 、shift、unshift 执行函数
每执行一个 task,需要继续执行后续 task,这通过 next 函数实现。
题目来源:Daily-Interview-Question 第 56 题
实现一个 new 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function _new (fn, ...args ) { const obj = Object .create(fn.prototype); const ret = fn.apply(obj, args); return ret instanceof Object ? ret : obj; } function Person (name, age ) { this .name = name; this .age = age; this .sayHi = function ( ) {}; } Person.prototype.run = function ( ) {}; console .log(_new(Person, 'Lance' , 19 ));console .log(new Person('Jerry' , 20 ));
实现一个单例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 function Person (name, age ) { this .name = name; this .age = age; } let Singleton = function (Fn ) { let _instance = null ; Singleton = function (...args ) { if (_instance) return _instance; return (_instance = new Fn(...args)); }; return Singleton; }; var peopleSingleton = Singleton(Person);var ming = new peopleSingleton('ming' , 18 );var fang = new peopleSingleton('fang' , 28 );console .log(ming, fang);class People { constructor (name, age ) { if (typeof People.instance === 'object' ) { return People.instance; } People.instance = this ; this .name = name; this .age = age; return this ; } } var ming = new People('ming' , 20 );var fang = new People('fang' , 19 );console .log(ming, fang);
ES6 实现一个 sleep 函数 比如 sleep(1000) 意味着等待 1000 毫秒,可从 Promise、Generator、Async/Await 等角度实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 const sleep = delay => new Promise (resolve => setTimeout (resolve, delay));sleep(1000 ).then(() => console .log('Done' )); function * sleepGenerator (delay ) { yield new Promise (resolve => setTimeout (resolve, delay)); } sleepGenerator(1000 ) .next() .value.then(() => console .log('Done' )); const sleep = delay => new Promise (resolve => setTimeout (resolve, delay));async function output ( ) { await sleep(1000 ); console .log('Done' ); } output(); function sleep (cb, delay ) { if (typeof cb === 'function' ) setTimeout (cb, delay); } function output ( ) { console .log('Done' ); } sleep(output, 1000 );
Promise 和 setTimeout 的区别? Promise 是微任务,setTimeout 是宏任务,同一个事件循环中,promise.then 总是先于 setTimeout 执行。
Promise 构造函数是同步还是异步执行?then 呢?Promise 如何实现 then 处理? Promise 的构造函数是同步执行的;then 中的方法是异步执行的。
Promise 实现 then 处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 const PENDING = 'pending' ;const FULFILLED = 'fulfilled' ;const REJECTED = 'rejected' ;function Promise (executor ) { let self = this ; self.status = PENDING; self.onFulfilled = []; self.onRejected = []; function resolve (value ) { if (self.status === PENDING) { self.status = FULFILLED; self.value = value; self.onFulfilled.forEach(fn => fn()); } } function reject (reason ) { if (self.status === PENDING) { self.status = REJECTED; self.reason = reason; self.onRejected.forEach(fn => fn()); } } try { executor(resolve, reject); } catch (e) { reject(e); } } Promise .prototype.then = function (onFulfilled, onRejected ) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; }; let self = this ; let promise2 = new Promise ((resolve, reject ) => { if (self.status === FULFILLED) { setTimeout (() => { try { let x = onFulfilled(self.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }); } else if (self.status === REJECTED) { setTimeout (() => { try { let x = onRejected(self.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }); } else if (self.status === PENDING) { self.onFulfilled.push(() => { setTimeout (() => { try { let x = onFulfilled(self.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }); }); self.onRejected.push(() => { setTimeout (() => { try { let x = onRejected(self.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }); }); } }); return promise2; };
实现一个基本的 Promise 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 class MyPromise { constructor (fn ) { this .resolvedCallbacks = []; this .rejectedCallbacks = []; this .state = 'PENDING' ; this .value = '' ; fn(this .resolve.bind(this ), this .reject.bind(this )); } resolve (value ) { if (this .state === 'PENDING' ) { this .state = 'RESOLVED' ; this .value = value; this .resolvedCallbacks.map(cb => cb(value)); } } reject (value ) { if (this .state === 'PENDING' ) { this .state = 'REJECTED' ; this .value = value; this .rejectedCallbacks.map(cb => cb(value)); } } then (onFulfilled, onRejected ) { if (this .state === 'PENDING' ) { this .resolvedCallbacks.push(onFulfilled); this .rejectedCallbacks.push(onRejected); } if (this .state === 'RESOLVED' ) { onFulfilled(this .value); } if (this .state === 'REJECTED' ) { onRejected(this .value); } } }
如何实现 Promise.all() 首先,需要知道 Promise.all() 的功能:
如果传入的参数是一个空的可迭代对象,那么此 promise 对象回调完成(resolve),只有此情况,是同步执行的,其它都是异步返回的。
如果传入的参数不包含任何 promise,则返回一个异步完成。promises 中所有的 promise 都“完成”时或参数中不包含 promise 时回调完成。
如果参数中有一个 promise 失败,那么 Promise.all 返回的 promise 对象失败。
在任何情况下,Promise.all 返回的 promise 的完成状态的结果都是一个数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 Promise .all = function (promises ) { return new Promise ((resolve, reject ) => { let index = 0 ; let result = []; if (promises.length === 0 ) { resolve(result); } else { function processValue (i, data ) { result[i] = data; if (++index === promises.length) { esolve(result); } } for (let i = 0 ; i < promises.length; i++) { Promise .resolve(promises[i]).then( data => { processValue(i, data); }, err => { reject(err); return ; } ); } } }); };
如何实现 Promise.prototype.finally() ? 不管成功还是失败,都会走到 finally 中,并且 finally 之后,还可以继续 then。并且会将值原封不动的传递给后面的 then。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Promise .prototype.finally = function (callback ) { return this .then( value => { return Promise .resolve(callback()).then(() => { return value; }); }, err => { return Promise .resolve(callback()).then(() => { throw err; }); } ); };
题目来源:2019 前端面试 | “HTML + CSS + JS”专题 「《ES6 速学:⑤ Promise 对象》[编号:es6_05]」
Event Loop & setTimeout setTimeout 的机制 等到当前脚本的同步任务和 “任务队列” 中已有的事件,全部处理完以后,才会执行 setTimeout 指定的任务。
参考:JavaScript 运行机制详解:再谈 Event Loop
使用 setTimeout 模拟 setInterval 1 2 3 4 5 setTimeout (function ( ) { setTimeout (arguments .callee, 500 ); }, 500 );
Event Loop 有关 Event Loop 相关的概念和面试题可参考我的博客:Event Loop 学习笔记
自我测验 上面两篇文章阅读完毕后可以自我测验下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 async function async1 ( ) { console .log('async1 start' ); await async2(); console .log('async1 end' ); } async function async2 ( ) { console .log('async2' ); } console .log('script start' );setTimeout (function ( ) { console .log('setTimeout' ); }, 0 ); async1(); new Promise (function (resolve ) { console .log('promise1' ); resolve(); }).then(function ( ) { console .log('promise2' ); }); console .log('script end' );
题目出处和答案参考:从一道题浅说 JavaScript 的事件循环
DOM getElementsByClassName 和 querySelectorAll 的区别
参考:
原生 JS 添加类
element.setAttribute(“class”, ‘Lance’)
element.className = “lance awesome”
追加类:element.setAttribute(“class”, element.getAttribute(“class”) + “ “ + “lance”)
编写一个可拖拽的 div
HTML
CSS
1 2 3 4 5 6 #sw { position : absolute; width : 100px ; height : 100px ; background-color : yellow; }
JS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 var flag = false ;var position = null ;var sw = document .querySelector('#sw' );sw.addEventListener('mousedown' , function (e ) { flag = true ; position = [e.clientX, e.clientY]; console .log(e.clientX, e.clientY); }); document .addEventListener('mousemove' , function (e ) { if (!flag) return ; var x = e.clientX; var y = e.clientY; var moveX = x - position[0 ]; var moveY = y - position[1 ]; var left = parseInt (sw.style.left || 0 ); var top = parseInt (sw.style.top || 0 ); sw.style.left = left + moveX + 'px' ; sw.style.top = top + moveY + 'px' ; position = [x, y]; }); document .addEventListener('mouseup' , function ( ) { flag = false ; });
预测执行结果 下面代码打印结果是什么?为什么? 1 2 3 4 5 var b = 10 ;(function b ( ) { b = 20 ; console .log(b); })();
答案:
非严格模式:[Function b]
严格模式:Uncaught TypeError: Assignment to constant variable
解析:
1 2 3 4 5 6 7 8 9 10 var b = 10 ;(function b ( ) { b = 20 ; console .log(b); console .log(window .b); })();
其他情况例子:
有window
:
1 2 3 4 5 6 var b = 10 ;(function b ( ) { window .b = 20 ; console .log(b); console .log(window .b); })();
有var
:
1 2 3 4 5 6 var b = 10 ;(function b ( ) { var b = 20 ; console .log(b); console .log(window .b); })();
解析来源 ←
输出以下代码执行的结果并解释为什么 1 2 3 4 5 6 7 8 9 10 var obj = { 2 : 3 , 3 : 4 , length: 2 , splice: Array .prototype.splice, push: Array .prototype.push }; obj.push(1 ); obj.push(2 ); console .log(obj);
结果:
解析参考:
结果最后变为一个稀疏数组[,,1,2]。 因为在对象中加入 splice 属性方法,和 length 属性后。这个对象变成一个类数组。属性名称可转换为数字时,会映射成为索引下标。所以对象实际可这样描述:[,,3,4],然后继续执行 length 属性复制,length 原来值为 4,length:2 语句将数组截断成为长度为 2 的数组。此时数组为[,,]。接着 push(1)、push (2)。数组追加两个值,[,,1,2]。小菜看法,有错望指正!!!
参考来源:
输出以下代码的执行结果并解释为什么 1 2 3 4 5 6 var a = { n : 1 };var b = a;a.x = a = { n : 2 }; console .log(a.x);console .log(b.x);
答案:
解析:
1、优先级.
的优先级高于=
,所以先执行a.x
,堆内存中的{n: 1}
就会变成{n: 1, x: undefined}
,改变之后相应的b.x
也变化了,因为指向的是同一个对象。
2、赋值操作是从右到左
,所以先执行a = {n: 2}
,a
的引用就被改变了,然后这个返回值又赋值给了a.x
,需要注意 的是这时候a.x
是第一步中的{n: 1, x: undefined}
那个对象,其实就是b.x
,相当于b.x = {n: 2}
题目来源:Daily-Interview-Question 第 53 题
输出以下代码运行结果 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var a={}, b='123' , c=123 ;a[b]='b' ; a[c]='c' ; console .log(a[b]);--------------------- var a={}, b=Symbol ('123' ), c=Symbol ('123' );a[b]='b' ; a[c]='c' ; console .log(a[b]);--------------------- var a={}, b={key :'123' }, c={key :'456' };a[b]='b' ; a[c]='c' ; console .log(a[b]);
分析:这题考察的是对象的键名的转换。
对象的键名只能是字符串和 Symbol 类型。
其他类型的键名会被转换成字符串类型。
对象转字符串默认会调用 toString 方法。
解答:
1 2 3 4 5 6 7 8 9 10 11 var a = {}, b = '123' , c = 123 ; a[b] = 'b' ; a[c] = 'c' ; console .log(a[b]);
1 2 3 4 5 6 7 8 9 10 11 12 13 var a = {}, b = Symbol ('123' ), c = Symbol ('123' ); a[b] = 'b' ; a[c] = 'c' ; console .log(a[b]);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var a = {}, b = { key : '123' }, c = { key : '456' }; a[b] = 'b' ; a[c] = 'c' ; console .log(a[b]);
题目来源:Daily-Interview-Question 第 76 题
请写出如下代码的打印结果 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function Foo ( ) { Foo.a = function ( ) { console .log(1 ); }; this .a = function ( ) { console .log(2 ); }; } Foo.prototype.a = function ( ) { console .log(3 ); }; Foo.a = function ( ) { console .log(4 ); }; Foo.a(); let obj = new Foo();obj.a(); Foo.a();
解答:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 function Foo ( ) { Foo.a = function ( ) { console .log(1 ); }; this .a = function ( ) { console .log(2 ); }; } Foo.prototype.a = function ( ) { console .log(3 ); }; Foo.a = function ( ) { console .log(4 ); }; Foo.a(); let obj = new Foo();obj.a(); Foo.a();
题目来源:Daily-Interview-Question 第 100 题
分别写出如下代码的返回值 1 2 String ('11' ) == new String ('11' );String ('11' ) === new String ('11' );
解析:
返回 true 和 falsenew String()
返回的是对象==
的时候,实际运行的是:String('11') == new String('11').toString();
输出以下代码运行结果 1 2 3 4 5 1 + '1' ;2 * '2' [(1 , 2 )] + [2 , 1 ];'a' + +'b' ;
解析:
加性操作符:如果只有一个操作数是字符串,则将另一个操作数转换为字符串,然后再将两个字符串拼接起来
所以值为:“11”
乘性操作符:如果有一个操作数不是数值,则在后台调用 Number()将其转换为数值
Javascript 中所有对象基本都是先调用valueOf
方法,如果不是数值,再调用toString
方法。
所以两个数组对象的 toString 方法相加,值为:”1,22,1”
后边的“+”将作为一元操作符,如果操作数是字符串,将调用 Number 方法将该操作数转为数值,如果操作数无法转为数值,则为 NaN。
所以值为:”aNaN”
以上均参考:《Javascript 高级程序设计》
题目来源:Daily-Interview-Question 第 116 题
下方 js 执行后的打印值为? 1 2 3 4 5 6 7 8 9 10 function demo ( ) { this .length = 10 ; var fn = function ( ) { console .log(this .length); }; arr = [fn, 'hello layui' ]; fn.length = 100 ; arr[0 ](); } demo();
答案:2
解析:this 指向问题一般看谁在调用就 OK 了,在上述函数中,fn 的拥有者是 arr 并且也是它调用的,所以 this 指向 arr,arr 中有两个成员,length 为 2。
延伸 1 2 3 function fn ( ) {}fn.length = 2 ; console .log(fn.length);
答案:0
解析:length 是 js 函数对象的一个属性值,该值是指 “该函数有多少个必须要传入的参数”,即形参的个数。形参的数量不包括剩余参数个数,仅包括 “第一个具有默认值之前的参数个数”,然而 fn 不需要传递参数,length 为 0。
拓展阅读:JS 中函数的 length 属性
{} 和 [] 的 valueOf 和 toString 的结果是什么? 1 2 {} 的 valueOf 结果为 {} ,toString 的结果为 "[object Object]" [] 的 valueOf 结果为 [] ,toString 的结果为 ""
toString 是把对象转换为字符串;
valueOf 是把对象转换 du 成一个基 zhi 本数据的值
valueOf 偏向 dao 于运算,toString 偏向于显示。
在进行对象转换时(例如:alert(a)),将优先调用 toString 方法,如若没有重写 toString 将调用 valueOf 方法,如果两方法都没有重写,按 Object 的 toString 输出。
在进行强转字符串类型时将优先调用 toString 方法,强转为数字时优先调用 valueOf。
在有运算操作符的情况下,valueOf 的优先级高于 toString。
预测下面的输出是什么 1 2 3 4 5 6 7 8 9 10 11 function Person (firstName, lastName ) { this .firstName = firstName; this .lastName = lastName; } const member = new Person('Lydia' , 'Hallie' );Person.getFullName = function ( ) { return `${this .firstName} ${this .lastName} ` ; }; console .log(member.getFullName());
A: TypeError
B: SyntaxError
C: Lydia Hallie
D: undefined
undefined
解析:
答案是 A
getFullName 函数是赋值给 Person 的,没有赋值给 Person 的原型,所以 member 并没有 getFullName 这个函数,这样运行会报错
如果这里运行的是 Person.getFullName() 那么会得到两个 undefined
如果写了 Person.prototype.getFullName = …. 此时调用 member.getFullName 函数 会得到答案 C
下面的输出是什么? 1 2 3 4 5 6 7 8 9 10 11 12 13 class Chameleon { static colorChange (newColor ) { this .newColor = newColor; return this .newColor; } constructor ({ newColor = 'green' } = {} ) { this .newColor = newColor; } } const freddie = new Chameleon({ newColor : 'purple' });freddie.colorChange('orange' );
A: orange
B: purple
C: green
D: TypeError
解析:
colorChange 是静态方法不是实例方法,需要 Chameleon.colorChange 才能调用
输出是什么? 1 2 3 4 5 6 7 8 9 10 const shape = { radius: 10 , diameter ( ) { return this .radius * 2 ; }, perimeter: () => 2 * Math .PI * this .radius }; shape.diameter(); shape.perimeter();
A: 20
and 62.83185307179586
B: 20
and NaN
C: 20
and 63
D: NaN
and 63
解析:
shape 定义所在环境,是 window 对象。
diameter 方法中的 this 指向方法调用者,即 shape。
perimeter 方法中的 this 指向方法调用者所在的环境 ,即 window 对象。
NaN 是什么,用 typeof 会输出什么? Not a Number,表示非数字,typeof NaN === ‘number’
算法题 我面的都不是什么大公司,所以很少被问到算法,不过对于前端来说,了解一些基本的算法还是很有必要的,起码最常见的排序算法得掌握,例如冒泡和快排。这部分内容可参考我的博客:
使用递归完成 1 到 100 的累加? 1 2 3 4 5 6 function fn (num ) { if (num === 1 ) return 1 ; return num + fn(num - 1 ); } console .log(fn(100 ));
正则 对正则不太熟练的同学,在这儿我分享掘金的一篇教程。这篇教程对我来说真的是受益匪浅,希望也能帮助到你:
写一个函数 isValidUsername(str),判断用户输入的是不是合法的用户名(长度 6-20 个字符,只能包括字母、数字、下划线)? 1 2 3 4 5 function isValidUsername (str ) { return /^\w{6,20}$/ .test(str); } console .log(isValidUsername('swlance' ));
写一个函数 isPhoneNum(str),判断用户输入的是不是手机号? 1 2 3 4 5 function isPhoneNum (str ) { return /^1[3578]\d{9}$/g .test(str); } console .log(isPhoneNum('13871455497' ));
1 2 3 4 5 function isEmail (str ) { return /^\w+@\w+(\.\w+){1,2}$/ .test(str); } console .log(isEmail('xxx@swlance.com.cn' ));
写一个函数 trim(str),去除字符串两边的空白字符? 1 2 3 4 5 function trim (str ) { return str.replace(/^\s+|\s+$/g , '' ); } console .log(trim(' xxxswlance.cn ' ));
上述正则题目来源:2019 前端面试 | “HTML + CSS + JS”专题 「《JS 提供的对象:② 正则表达式》[编号:js_17]」
转化为驼峰命名 1 2 var s1 = 'get-element-by-id' ;
1 2 3 4 function fn (str ) { return str.replace(/-\w/g , x => x.slice(1 ).toUpperCase()); } console .log(fn(s1));
场景题 数学逻辑 某果蔬平台商品设置与订单应付款的计算规则说明如下:
A.商品可被设置为不参与其他优惠活动,即不参与会员折扣、满减活动 B.会员价商品不参与其他优惠活动,即不参与会员折扣、满减活动 C.用户积分可抵扣,积分抵扣规则为 100 积分可抵扣 0.5 元 D.订单计算规则为: Step 1:购物车中的商品分别计算参与优惠部分的商品总价与不参与优惠的商品总价; Step 2:参与优惠的商品总价可参与满减活动 Step 3:上一步处理之后,参与优惠的商品总价可参与会员折扣 Step 4:所有商品总价,扣减掉积分可抵扣的部分得到订单应支付金额
该果蔬平台有一个商户 A,在后台创建了一个商品 fruit_a,定价为 10 元/KG。用户购买 3KG 商品,问下列各独立场景下,应付款几何?[需要写出结果与计算式,如下所示]
Eg. 商品设置了阶梯价,>=3KG,价格为 8 元/KG;应付款 24=8*3 元
2.1 用户有 500 积分,店铺积分可抵扣,抵扣规则为 100 积分可抵扣 0.5 元;店铺设置有满减活动,满 20 元减 3 元;商品设置阶梯价,》=3KG,价格为 8 元/KG,则用户应付款为____元。(5.0 分)
2.2 用户有 500 积分,店铺积分可抵扣,抵扣规则为 100 积分可抵 0.5 元;商品设置会员价 8 元/KG,使用会员价必须使用余额支付;店铺设置有满减活动,满 20 元减 3 元;用户若实用余额支付应付款为__元。若使用在线支付,则应付款为__ 元;(8.0 元)
2.3 用户有 500 积分,店铺积分不可抵扣,抵扣规则为 100 积分可抵 0.5 元;店铺有开启会员折扣,会员折扣为 8.5 折,使用会员折扣必须使用余额支付;店铺设置有满减活动,满 20 元减 3 元;用户若使用余额支付应付款为__元,若使用在线支付,则应付款为__ 元;(8.0 分)
参考答案:
2.1 8×3-3-500/100×0.5=18.5 元
2.2 8×3-500/100×0.5=21.5 元 10×3-3-500/100×0.5=24.5 元
2.3 (10×3-3)×0.85=22.95 元 10×3-3=27 元
非常规题 [‘1’, ‘2’, ‘3’].map(parseInt)的结果 正确答案:[1, NaN, NaN]
答案解析:[‘1’, ‘2’, ‘3’].map(parseInt) what & why ?
其它 修改以下 print 函数,使之输出 0 到 99,或者 99 到 0 1 2 3 4 5 6 7 8 function print (n ) { setTimeout (() => { console .log(n); }, Math .floor(Math .random() * 1000 )); } for (var i = 0 ; i < 100 ; i++) { print(i); }
解答:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 function print0 (n ) { setTimeout ( () => { console .log(n); }, 0 , Math .floor(Math .random() * 1000 ) ); } function print1 (n ) { setTimeout ( (() => { console .log(n); })(), Math .floor(Math .random() * 1000 ) ); } function print2 (n ) { setTimeout (() => { console .log(--i); }, Math .floor(Math .random() * 1000 )); } function print3 (n ) { console .log(n); }
题目来源:Daily-Interview-Question 第 101 题
写一个通用的事件侦听器函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 const EventUtils = { addEvent: function (element, type, handler ) { if (element.addEventListener) { element.addEventListener(type, handler, false ); } else if (element.attachEvent) { element.attachEvent('on' + type, handler); } else { element['on' + type] = handler; } }, removeEvent: function (element, type, handler ) { if (element.removeEventListener) { element.removeEventListener(type, handler, false ); } else if (element.detachEvent) { element.detachEvent('on' + type, handler); } else { element['on' + type] = null ; } }, getTarget: function (event ) { return event.target || event.srcElement; }, getEvent: function (event ) { return event || window .event; }, stopPropagation: function (event ) { if (event.stopPropagation) { event.stopPropagation(); } else { event.cancelBubble = true ; } }, preventDefault: function (event ) { if (event.preventDefault) { event.preventDefault(); } else { event.returnValue = false ; } } };