Bootstrap

Js常用面试题目知识整理

Js代码题

1. 千分位

题目:要求返回参数数字的千分位分隔符字符串。

思路:
在字符串长度不确定的情况下,可以使用递归。
_comma(number % 1000) 是获取数字最后三位,将其放在返回值的最后面,并且在前面加一个逗号
_comma(Math.floor(number / 1000)) 是将剩下的部分传入函数本身
不断重复,直到入参的number小于1000,返回 number.toString(),函数最后返回一个完整的千位分隔符的字符串。

 function _comma(number) {
                // 补全代码
                if(number<1000){
                    return number.toString();
                }else{
                    return _comma(Math.floor(number/1000))+","+_comma(number%1000)
                }
    
            }

PS: 有个bug:如果number为123456789012,会输出123,456,789,12

完善版:


function _comma(number){
    // 补全代码
    let zf=false;
    if(number<0){
        zf = true;
    }
    number = Math.abs(number)
    number = number.toString().split('');
    let n = number.length;
    for(let i=n-3;i>0;i-=3){
        number.splice(i,0,',');
    }
    if(zf) number.unshift('-');
    return number.join('');
}
console.log(f(123456789012));

知识扩展:

Math.floor()、Math.round()、Math.ceil()

下取整: Math.floor()
四舍五入:Math.round()
上取整: Math.ceil()

reverse join split splice 用法

参考这位博主总结

2. 创建数组

返回一个长度为参数值并且每一项值都为参数值的数组

通过new Array()函数构造实例的时候带入参数,可以生成该参数长度的空数组
通过Array.fill()函数可以将数组的每一项都改编为参数值

    <script type="text/javascript">
         const _createArray = (number) => {
             // 补全代码
             return Array(number).fill(number);
             
         }
     </script>

3. JS获取扩展名

  • String 中split()方法
function getFileExtension(filename) {
  return filename.split(".").pop()
}
function getFileExtension(filename) {
  return filename.slice((filename.lastIndexOf(".") - 1 >>> 0) + 2)
}
  • 正则
function getFileExtension1(filename) {
  return /[.]/.exec(filename) ? /[^.]+$/.exec(filename)[0] : undefined
}

这里的/[.]/.exec(filename)是用来判断.是否存在,如果不存在的话,其值为null;
/[^.]+$/.exec(filename)[0]拿到从.开始匹配的字符串,也就是扩展名。如果不存在则返回undefined。

4. 最长公共前缀

给你一个大小为 n 的字符串数组 strs ,其中包含n个字符串 , 编写一个函数来查找字符串数组中的最长公共前缀,返回这个公共前缀。

数据范围: 0≤n≤50000 \le n \le 5000 0≤n≤5000, 0≤len(strsi)≤50000 \le len(strs_i) \le 5000 0≤len(strsi​)≤5000
进阶:空间复杂度 O(n)O(n)O(n),时间复杂度 O(n)O(n)O(n)

/**
  *
  * @param strs string字符串一维数组
  * @return string字符串
  */
function longestCommonPrefix( strs ) {
    // write code here
    if(strs.length==0) return "";
    let s = "";//标准
    for(let i=0;i<strs[0].length;i++){
        let j=0;
        while(j<strs.length-1){
            if(strs[j][i]===strs[j+1][i]){
                j++;
            }else{
                return s;
            }
        }
         s += strs[0][i];
    }
    return strs[0];
}

5.ip地址

编写一个函数来验证输入的字符串是否是有效的 IPv4 或 IPv6 地址
IPv4 地址由十进制数和点来表示,每个地址包含4个十进制数,其范围为 0 - 255, 用(“.”)分割。比如,172.16.254.1;
同时,IPv4 地址内的数不会以 0 开头。比如,地址 172.16.254.01 是不合法的。
IPv6 地址由8组16进制的数字来表示,每组表示 16 比特。这些组数字通过 (“:”)分割。比如, 2001:0db8:85a3:0000:0000:8a2e:0370:7334 是一个有效的地址。而且,我们可以加入一些以 0 开头的数字,字母可以使用大写,也可以是小写。所以, 2001:db8:85a3:0:0:8A2E:0370:7334 也是一个有效的 IPv6 address地址 (即,忽略 0 开头,忽略大小写)。

然而,我们不能因为某个组的值为 0,而使用一个空的组,以至于出现 (:😃 的情况。 比如, 2001:0db8:85a3::8A2E:0370:7334 是无效的 IPv6 地址。
同时,在 IPv6 地址中,多余的 0 也是不被允许的。比如, 02001:0db8:85a3:0000:0000:8a2e:0370:7334 是无效的。

说明: 你可以认为给定的字符串里没有空格或者其他特殊字符。

数据范围:字符串长度满足 5≤n≤505 \le n \le 505≤n≤50
进阶:空间复杂度 O(n)O(n)O(n),时间复杂度 O(n)O(n)O(n)

function solve( IP ) {
    // write code here
    let res="";
    if(IP.length<16){
        //IPV4判断
        res = "IPv4";
        let ip4 = IP.split(".");
        if(ip4.length!=4) res = "Neither";
        for(let i=0;i<ip4.length;i++){
            if(Number(ip4[i])>255 || Number(ip4[i])<0 || ip4[i][0]=='0' || ip4[i]==""|| isNaN(Number(ip4[i]))) res = "Neither";  
        }
    }else{
            //IPV6判断
            res = "IPv6";
            let ip6 = IP.split(":");
            if(ip6.length!=8) res = "Neither";
            //正则 匹配16进制数字
            let regex="^[A-Fa-f0-9]+$"
            for(let i=0;i<ip6.length;i++){
                if(ip6[i].length>4 || ip6[i]=="" || !ip6[i].match(regex)) {
                    res = "Neither";
                    break;
                }
            }     
    }
    return res;
}

6. 最长无重复数组

给定一个长度为n的数组arr,返回arr的最长无重复元素子数组的长度,无重复指的是所有数字都不相同。
子数组是连续的,比如[1,3,5,7,9]的子数组有[1,3],[3,5,7]等等,但是[1,3,7]不是子数组
在这里插入图片描述

思路:

  1. 用set进行去重和查重的操作
  2. 定义双指针l,r控制左右边界
  3. 遍历数组判断元素是否在set中出现过,未出现就加入set,r指针向后移动
  4. 重复的元素就把set中的重复元素删除,l++直到没有重复元素,挨个删除
  5. 返回最大的无重复子串
function maxLength( arr ) {
    // write code here
    let set = new Set();//用set集合判断有无重复数字
    let l=0,r=0;
    let cnt=1;
    while(l<arr.length && r<arr.length){
        if(set.has(arr[r])==false){//不存在 插入
            set.add(arr[r]);
            cnt = Math.max(r-l+1,cnt);//返回最大的无重复子串
            r++;
        }
        else{//有重复的就删除到没有重复的
            set.delete(arr[l++]);
        }
    }
    return cnt;
}

总结原生JS数组的操作

添加:
push() 在数组的末尾添加一个或多个元素,数组push之后返回的是length,而不是新的数组
unshift() 在数组的开头添加一个或多个元素,返回值:数组的长度

删除:
pop() 删除数组的最后一个元素,返回该元素的值
shift() 删除数组的第一个元素 并返回该元素的值。

sort()排序

 sort排序
        var arr = [12, 85, 98, 56];
        arr.sort(function(a, b) {
            return a - b;
        })
        console.log(arr); //[12,56,85,98]
        var arr1 = [{age: 48}, {age: 12}, {age: 89}];
        arr1.sort(function(a, b) {
            return a.age - b.age
        })
        console.log(arr1);//  [{age: 12}, {age: 48}, {age: 89}]

reverse() 反转数组 arrayObject.reverse()

splice() 从数组中添加/删除元素,然后返回被删除的项目。(会改变原数组)

arrayObject.splice(index,howmany,item1,.....,itemX)
	index	必需。整数,规定添加/删除项目的位置,
			使用负数可从数组结尾处规定位置。
	howmany	必需。要删除的项目数量。如果设置为 0,则不会删除项目。
	item1, ..., itemX	可选。向数组添加的新项目。
	返回值:删除元素的数组
	

concat(item1,item2,......,item3) 连接两个或多个数组,会返回被连接数组的一个副本
item可以是元素也可以是数组(如果是数组,只连接数组内的元素)
concat不改变原有数组

join()用于把数组中的所有元素放入一个字符串。元素是通过指定的分隔符进行分隔的。

参数可选
不写;默认“,”为分隔符
写“”;则为直接连接
写“-”;则用短划线连接
不影响原有数组

slice(start,end) 截取数组

start 必须,负值为倒着数
end 可选 负值为倒着数
返回值:被截取的子数组,包头不包尾
对原数组没有影响

reduce: arr.reduce((prev,current,index,arr)=>{},initValue)
是一个累加方法,是对数组累积执行回调函数,返回最终计算结果。 对数组里每个元素执行一次回调函数。

  • prev:上一次回调的调用值,默认是initValue,如果不传initValue,则默认是数组的第一项
  • currentValue 必需。当前元素
  • index 可选。当前元素的索引值。
  • arr 可选。当前元素所属的数组对象。
  • thisValue 可选。传递给函数的值一般用 “this” 值。如果这个参数为空, “undefined” 会传递给 “this” 值

求数组的和

const arr = [1,2,3,4,5]
arr.reduce((prev, current)=>{
	return prev + current
})
// 15

数组去重

const arr = [1,1,2,2,3,3]
arr.reduce((prev,current)=>{
	!prev.includes(current) && prev.push(current)
	return prev
},[])
// [1,2,3]

统计数组元素出现次数

let arr = ['name','age','long','short','long','name','name'] 
let arrResult = arr.reduce((pre,cur) =>{
     console.log(pre,cur)
     if(cur in pre){
         pre[cur]++
     }else{
         pre[cur] = 1
     }
     return pre
 },{})

 console.log(arrResult)
 //结果:{name: 3, age: 1, long: 2, short: 1}

every() 检测数值元素的每个元素是否都符合条件。布尔值。如果所有元素都通过检测返回 true,否则返回 false。原始值不变。
some() 检测数组元素中是否有元素符合指定条件。布尔值。如果数组中有元素满足条件返回 true,否则返回 false。原始值不变。

filter 检测数值元素,并返回符合条件所有元素的数组。返回数组,包含了符合条件的所有元素。如果没有符合条件的元素则返回空数组。

fill(target, start, end) 使用一个固定值来填充数组。并返回一个新数组。(start、end可选)

indexOf()
includes()
from() 将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。

flat(num) 用于将嵌套的数组“拉平”,变成 一维的数组。该方法返回一个新数组,对原数据没有影响。
num: 拉平数组嵌套的层数,默认值是 1,Infinity 代表无限拉平.
原数组有空位,flat()方法会跳过空位

[1, [2, [3]]].flat(Infinity)
// [1, 2, 3]

toString() 把数组转换为字符串,并返回结果。

总结字符串操作

split() 用于把一个字符串分割成字符串数组.
stringObject.split(a,b)

  • a是必须的,决定个从a这分割
  • b可选。该参数可指定返回的数组的最大长度 。如果设置了该参数,返回的子串不会多于这个参数指定的数组。如果没有设置该参数,整个字符串都会被分割,不考虑它的长度。
    如果a为空字符串 (“”) ,那么 stringObject 中的每个字符之间都会被分割。
    String.split() 执行的操作与 Array.join 执行的操作是相反的。

chatAt() 返回下标位置的字符串。原始值不变。

let str = 'dengZiYao';

str.charAt(param)
 // charAt()接受一个number类型的必须参数
 // 当 charAt() 的 参数为空 或者 为 NaN 时 将返回字符串中的第一个字符

console.log(str.charAt(3))  //  g
console.log(str.charAt())  //  d
console.log(str.charAt(NaN))  //  d

charCodeAt() 返回字符串指定位置的Unicode编码
fromCharCode()

String.fromCharCode(unicode1,unicode2,...,nuicodeX) 2 3
 // 该方法是 String 的静态方法,语法应该是 String.fromCharCode()。 

indexOf() 返回字符串中某个字符 首次出现 的位置,如果參數為字符串就返回字符串首位出现的位置

lastIndexOf() 返回字符串中某个字符 最后出现 的位置(从后往前搜索)
concat() 拼接字符串 / 组合字符串 在字符串末尾处追加字符串

let str = 'dengZiYao';
console.log(str.concat('ab')  // dengZiYaoab
console.log(str.concat('ab','cd'))  // dengZiYaoabcd

match() 在字符串内检索指定的值,找到一个或多个正则表达式的匹配的字符。匹配不到返回Null;

string.match(regexp) // regexp是一个正则表达式 不区分大小写  其返回值是一个数组Array
console.log(str2.match(/zi/g));  //  ["zi", "zi", "zi", "zi"]

replace() 在字符串中用 某种字符去替换指定的字符 或者 用正则表达式去替换某些字符

// 语法
stringObject.replace(regexp/substr,replacement)
// regexp/substr 要在字符串中匹配的字符(必须参数)
// replacement 字符串要替换的值(必须参数)

string.replaceAll(searchValue, replacement) :在字符串中查找匹配的子串,并替换与正则表达式匹配的子串,只能全部替换。返回新字符串。

search() 用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串。无匹配返回-1

str.search(regexp/substr)  
//返回值:str中第一个与正则或字符串相匹配的子串的起始位置。

slice() 用于截取字符串指定位置的字符,接收两个参数 slice(start,end) 字符串开始位置的索引号 和 字符串结束位置的索引号

  • 自动将传入的负值与字符串的长度相加
  • end参数小于start参数的话,返回空字符串
let str1 = 'asdfghjkl';

console.log( str1.slice( 2, 4 ) );// df

console.log( str1.slice( -2, 4 ) );// 

console.log( str1.slice( 2, -4 ) );// dfg

string.startsWith() : 查看字符串是否以指定的子字符串开头,返回布尔值。原始值不变。
string.endsWith() : 判断当前字符串是否是以指定的子字符串结尾的(区分大小写),返回布尔值。原始值不变。
substring(开始索引值,结束索引值) 用于提取字符串中介于两个指定下标之间的字符。

  • 包含开始位置,不包含结束位置
  • 如果两个参数都是负数或者两个参数都大于str长度,截取的内容都是空
  • 如果一个正数一个负数就是整个字符串

string.substr( start, num ) : 从起始索引号提取字符串中指定数目的字符。

  • start开始位置,num参数指定的则是返回的字符个数。如果没有给方法传递num参数,则将字符串的长度作为结束位置
  • 自动将负数的start参数加上字符串的长度,将负数的第num参数转换为0
 let str1 = 'asdfgh';
 console.log( str1.substr( 2, 4 ) );// dfgh 
 console.log( str1.substr( -2, 4 ) );// gh 
 console.log( str1.substr( 2, -4 ) );// 将负数的第num参数转换为0
 console.log( str1.substr( 2 ) ); // dfgh

toUpperCase() toLowerCase() 用于字符串转换大小写
trim():用于去除字符串两端的空白

 let str = "   faf a fa fas dfa  s f sdaf   ";
 let re = str.trim();

js判断字符串是否是数字——Number(num)+ isNaN(num)

var str = "37";
var n = Number(str);
if (!isNaN(n))
{
    alert("是数字");
}

注意: 在 JavaScript 中,对于省略写法(如:“.3”、“-.3”)、科学计数法(如:“3e7”、“3e-7”)、十六进制数(如:“0xFF”、“0x3e7”)均被认定为数字格式,这类字符串都可以用 Number 转化成数字。

js判断字符串是否为16进制数字

let s="123bf";
let regex="^[A-Fa-f0-9]+$";
  if(s.match(regex)){
  alet('是16进制')
}

总结 Number对象常用方法

toString()

var string = number.toString(radix) 

adix为2,表示以二进制值显示;
radix为8,表示以八进制值显示;
radix为16,表示以十六进制值显示;

toFixed()
toFixed() 方法可把 Number 四舍五入为指定小数位数的数字。

var num = 1653.325;
var str = num.toFixed(6);
str;//"1653.325000"
var str1 = num.toFixed();
str1;//"1653"

toExponential() toExponential() 方法可把对象的值转换成指数计数法。
toPrecision()
可在对象的值超出指定位数时将其转换为指数计数法。
isFinite()
用于检测指定参数是否为无穷大。如果是有限数字返回 true,否则返回 false。

总结Object对象常用方法:

1、Object.freeze() 将对象冻结,阻止修改对象现有属性的特性和值,并阻止添加新的属性

let obj={}
obj.age=10
Object.freeze(obj)
obj.age=20
console.log(obj.age)  //10  修改属性无效

Object.seal就是封闭某个对象,
1)阻止添加新属性;
2)现有属性变得不可配置(non-configurable)
然后会起到如下作用:
1、不可以通过delete去删除该属性从而重新定义属性;
2、不可以转化为访问器属性;
3、configurable和enumerable不可被修改;
4、writable可单向修改为false,但不可以由false改为true;
5、value是否可修改根据writable而定。

2、Object.assign(target,sourse1,sourse2) 用于对象合并(可用于浅拷贝),将源对象source的所有可枚举属性合并到目标对象上,方法只拷贝源对象的自身属性,不拷贝继承属性。
Object.assign 方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的这个对象的引用。同名属性会替换

  • Object.assign只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。
  • Object.assign可以用来处理数组,但是会把数组视为对象。
  • 拷贝之后同名的属性会被覆盖。
  • 如果只有一个参数,Object.assign()会直接返回该参数。 参数不是对象,会先转化为对象再返回,但是null 和 undefined作为参数会报错,因为他们无法转化为对象。
    但如果undefined和null不在首参数,就不会报错,会跳过他们执行。

3、Object.is() 判断两个值是否相等
与严格等于“===” 基本一致,但修复了NaN ===NaN true 和 -0===+0 false的失误判断;

+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

4、Object.keys() 返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名
5、Object.values() 返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值
6、Object.entries() 返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组
7、Object.fromEntries() 返回一个对象,Object.fromEntries()方法是Object.entries()的逆操作,用于将一个键值对数组转为对象。 因此特别适合将 Map 结构转为对象.

 Object.fromEntries([
 ['foo', 'bar'],
 ['baz', 42]
])
// { foo: "bar", baz: 42 }
 
const entries = new Map([
   ['foo', 'bar'],
   ['baz', 42]
]);

Object.fromEntries(entries)
// { foo: "bar", baz: 42 }

https://www.cnblogs.com/jiaoshou/p/15915201.html

总结Set操作

Set 对象类似于数组,且成员的值都是唯一的

//初始化一个Set ,需要一个Array数组,要么空Set
var set = new Set([1,2,3,5,6]) 
console.log(set)  // {1, 2, 3, 5, 6}

//添加元素到Set中
set.add(7) //{1, 2, 3, 5, 6, 7}

//删除Set中的元素
set.delete(3) // {1, 2, 5, 6, 7}

//检测是否含有此元素,有为true,没有则为false
set.has(2) //true

Set的forEach遍历
forEach()方法会传入一个回调函数,回调函数可任意传入三个参数:

  1. Set 中下个位置的值;
  2. 与第一个参数相同的值;
  3. 目标 Set 自身。

传给回调函数的第一个与第二个 参数是相同的。(因为数组第二个参数参数的是索引值,但是Set没有键,所以就把自己的值当作键传入)除了参数差异外其他使用和数组基本相同。

  let mySet = new Set([1,2,1,1,2,3,4,5,5,5]);
    mySet.forEach((item, key, self) => {
        console.log(key + " " + item);
        console.log(self === mySet);//目标 Set 自身,true
    });

set 数组去重

let arr = [1,2,3,3,4,4,4,5];
let set = new Set(arr);
console.log([...set]);//Array.from(set) 也可以转化为数组

set求数组交集

let arr1 = [1,2,3,1];
let arr2 = [3,4,5,4];
let s1 = new Set(arr1);//先去除arr1数组自身的重复项
let s2 = new Set(arr2);//去除arr2数组自身的重复项
//数组的filter方法,它的参数是一个函数,如果这个函数返回的是true,表示把数组的这一项留下
//如果是false的话,会把数组的这一项删除掉
let arr = [...s1].filter((item)=>{
     return s2.has(item) //has方法看s2里有没有item这一项
})
console.log(arr)//[3]复制代码

set 求数组并集

let arr1 = [1,2,3];
let arr2 = [3,4,5];
let s1 = new Set([...arr1,...arr2]) //这样就把重复的3去掉了
console.log([...s1]);//这就是并集的结果了复制代码

总结Map操作

Map的数据结构是一个键值对的结构, 一个key只能对应一个value,多次对一个key放入value,后面的值会把前面的值覆盖掉;key 不仅可以是字符串还可以是对象。

//初始化`Map`需要一个二维数组(请看 Map 数据结构),或者直接初始化一个空`Map` 
let map = new Map();

//添加key和value值
map.set('Amy','女')
map.set('liuQi','男')

//是否存在key,存在返回true,反之为false
map.has('Amy') //true
map.has('amy') //false

//根据key获取value
map.get('Amy') //女

//删除 key为Amy的value
map.delete('Amy')
map.get('Amy') //undefined  删除成功

Map 的forEach方法:
Map 的 forEach() 方法类似于 Set 与数组的同名方法,它接受一个能接收三个参数的回调函 数:

  1. Map 中下个位置的值;
  2. 该值所对应的键;
  3. 目标 Map 自身。
  let myMap = new Map([["name", "Amy"], ["age", 20]]);
    myMap.forEach((value, key, self) => {
        console.log(key + " " + value);
        console.log(self === myMap);//true
    });

Map.prototype.keys():键名的遍历器。
Map.prototype.values():键值的遍历器。
Map.prototype.entries():所有成员的遍历器。

Map转换

  • Map 转为数组 => 使用扩展运算符 或者使用 Array.from()
var map = new Map();
map.set("id1",1).set("id2",2);
console.log([...map]);
//输出:[["id1",1],["id2",2]]

// 使用 Array.from() 方法将迭代器对象转换为数组类型
const myArray1 = Array.from(myMap.entries());
console.log(myArray1); // [ ['key1', 'value1'], ['key2', 'value2'] ]

  • 数组 转为 Map => 数组传入 Map 构造函数

  • Map 转为对象 =>如果所有 Map 的键都是字符串,它可以无损地转为对象,如果有非字符串的键名,那么这个键名会被转成字符串,再作为对象的键名。

  • 对象转为 Map => 对象转为 Map 可以通过Object.entries()

  • Map 转为 JSON,Map 的键名都是字符串,这时可以选择转为对象 JSON,Map 的键名有非字符串,这时可以选择转为数组 JSON。

事件委托

概念:

把原本需要绑定在子元素的响应事件(click、keydown…)委托给父元素,让父元素担当事件监听的职务。通过事件对象的target属性来控制子级对象的改变。事件委托就是在冒泡阶段完成

原理:

DOM元素的事件冒泡。

优点:

  1. 节省内存占用,减少事件注册
  2. 可以实现动态绑定事件,当新增子对象时无需再次对其绑定。

适合事件委托 的事件有: click , mousedown , mouseup , keydown , keyup , keypress

数组去重

  • 在ES6中可用set方法去重
 let arr = [1,1,1,2,2,3,3,3,4,4]
 let set = new Set(arr)
 console.log(set);
 let arr2 = Array.from(set)  //将类数组转化为真数组
 console.log(arr2);
  • 使用新数组对不重复的数组进行保存
    indexOf 对NaN型
  let array= [1,1,1,2,2,3,3,3,4,4]
  let newArr = []
  array.forEach(item => {
       if(newArr.indexOf(item) === -1){
           newArr.push(item)
       }
   });
    return newArr

如果数组元素有多个NaN,用indexOf方法和===运算符都不太合适,可以采用includes方法;

 let array= [1,1,1,2,2,3,3,3,4,4]
 var newArr=[];//空数组+array.forEach的方法 
 array.forEach(item=>{
    !newArr.includes(item)&&newArr.push(item);
  })
 return newArr; 
  • 利用filterindexOf
const arr1=[1,3,4,5,6,7,5,4,3]
const arr2 = arr1.filter((item,index)=>{
	return arr1.indexOf(item) === index;
})
console.log(arr2);

快速排序

思想: 选一个数作为基准数,大于基准数放在右边新数组,小于基准数放在左边新数组,不断递归左右两边数组,然后拼接每个数组;

cosnt _quicksort = array =>{
	if(array.length<=1) return array;
	//找基准并删除基准
	var mid = Math.floor(array.length/2);
	var p = array.splice(mid,1)[0];
	var l = [];
	var r = [];
	for(var i=0;i<array.length;i++){
		if(array[i]<p) l.push(array[i]);
		else r.push(array[i]);
	}
	return _quicksort(l).concat([p],_quicksort(r));
}

全排列

要求以数组的形式返回字符串参数的所有排列组合。
注意:

  1. 字符串参数中的字符无重复且仅包含小写字母
  2. 返回的排列组合数组不区分顺序

思路:
1、因为是要获取全排列 用数组来存储最适合
2、判断输入的字符串的长度
3、当字符串长度为1时候 返回 只有自身的数据
4、当字符串的长度大于1时候 就类似于类似于’abc’ 的排列 就等于 a + bc的全排列 和 b + ac的全排列 c + ab的全排列

const _permute = string => {
                // 补全代码
                var res=[];
                if(string.length<=1) return [string];
                else{
                    for(var i=0;i < string.length; i++){
                        var c = string[i];
                        //递归选择每一位字符串
                        var newStr = string.slice(0,i) + string.slice(i+1,string.length)
                         //上一个返回全排的结果
                        var l = _permute(newStr);
                        //遍历插入组合结果
                        for(var j=0;j<l.length;j++){
                            var t = c+l[j];
                             // 把所有的排列保存到res数组中
                            res.push(t);
                        }
                    }
                }
               return res;
                
            }

this指向问题

腾讯题目:

var x = 20;
var a = {
 x: 15,
 fn: function() {
 var x = 30;
 return function() {
  return this.x
 }
 }
}
console.log(a.fn());
console.log((a.fn())());
console.log(a.fn()());
console.log(a.fn()() == (a.fn())());
console.log(a.fn().call(this));
console.log(a.fn().call(a));
  1. a.fn() 调用函数,返回值为一个function:

function() {return this.x}

  1. (a.fn())() 相当于返回函数的执行:()()这是自执行表达式。this->window

20

  1. a.fn()() a.fn()相当于在全局定义了一个函数,然后再自己调用执行。this -> window

20

  1. a.fn()() == (a.fn())()

true

  1. a.fn().call(this) 这段代码在全局环境中执行,this -> window

20

  1. console.log(a.fn().call(a)); this->a

15

生成范围内随机数数组

题目:

  1. 根据输入的数字范围[start,end]和随机数个数"n"生成随机数
  2. 生成的随机数存储到数组中,返回该数组
  3. 返回的数组不能有相同元素
    注意:
  4. 不需要考虑"n"大于数字范围的情况
const _getUniqueNums = (start,end,n) => {
    // 补全代码
   let arr = [];
     while(n){
         let num = Math.floor(Math.random()*(end-start+1))+start;
         if(arr.indexOf(num)===-1){//不重复
             arr.push(num);
             n--;
         }else{
             continue;
         }
     }
     return arr;
}

知识点:生成随机数Math.random()

  • 生成[min,max]范围的随机数
return Math.floor(Math.random()*(max-min+1))+min;
  • 生成[min,max) 范围的随机数
return Math.floor(Math.random()*(max-min))+min;

JS计数器(闭包的使用)

请补全JavaScript代码,要求每次调用函数"closure"时会返回一个新计数器。每当调用某个计数器时会返回一个数字且该数字会累加1。
注意:

  1. 初次调用返回值为1
  2. 每个计数器所统计的数字是独立的

考点:闭包、匿名函数

<script type="text/javascript">
     const closure = () => {
         // 补全代码
         var count=1;
         return ()=>{
             return count++;
         }
     }
 </script>

原型链查找

/**
 * @param {Object} target 
 * @param {Function} Fn 
 * @returns {boolean}
 */
const _instanceof = (target, Fn) => {
  // 补全代码
  //获取到对象的原型
  let proto = target.__proto__;
  while (proto) {
    if (proto === Fn.prototype) return true;
    proto = proto.__proto__;
  }
  return false;
};

补充:js检查对象是否在同一个原型链中

instanceof 检测一个对象是否是某个构造函数的实例(new)

isPrototypeOf() 检查一个对象是否存在于另一个对象的原型链上

//instanceof
var str = new String('zhangsan');
console.log(str instanceof String);//true

function Animal(){}
var dog1 = new Animal();//用new来调用的函数一般是构造函数
console.log(dog1 instanceof Animal)//true
 
//isPrototypeOf
console.log(Animal.prototype.isPrototypeOf(dog1))//true检测对象dog1是否在animal的原型链上
var obj = {};
console.log(Object.prototype.isPrototypeOf(obj));//true

function Dog(){}
var dog2 = new Dog();
console.log(Animal.prototype.isPrototypeOf(dog2));//false dog2不在animal的原型链上

手写promise

 function Promise(excutor) {
            let self = this
            self.status = 'pending'
            self.value = null
            self.reason = null
            self.onFulfilledCallbacks = []
            self.onRejectedCallbacks = []

            function resolve(value) {
                if (self.status === 'pending') {
                    self.value = value
                    self.status = 'fulfilled'
                    self.onFulfilledCallbacks.forEach(item => item())
                }
            }

            function reject(reason) {
                if (self.status === 'pending') {
                    self.reason = reason
                    self.status = 'rejected'
                    self.onRejectedCallbacks.forEach(item => item())
                }
            }
            try {
                excutor(resolve, reject)
            } catch (err) {
                reject(err)
            }
        }


        Promise.prototype.then = function (onFulfilled, onRejected) {
            onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (data) {
                resolve(data)
            }
            onRejected = typeof onRejected === 'function' ? onRejected : function (err) {
                throw err
            }
            let self = this
            if (self.status === 'fulfilled') {
                return new Promise((resolve, reject) => {
                    try {
                        let x = onFulfilled(self.value)
                        if (x instanceof Promise) {
                            x.then(resolve, reject)
                        } else {
                            resolve(x)
                        }
                    } catch (err) {
                        reject(err)
                    }
                })
            }
            if (self.status === 'rejected') {
                return new Promise((resolve, reject) => {
                    try {
                        let x = onRejected(self.reason)
                        if (x instanceof Promise) {
                            x.then(resolve, reject)
                        } else {
                            resolve(x)
                        }
                    } catch (err) {
                        reject(err)
                    }
                })
            }
            if (self.status === 'pending') {
                return new Promise((resolve, reject) => {
                    self.onFulfilledCallbacks.push(() => {
                        let x = onFulfilled(self.value)
                        if (x instanceof Promise) {
                            x.then(resolve, reject)
                        } else {
                            resolve(x)
                        }
                    })
                    self.onRejectedCallbacks.push(() => {
                        let x = onRejected(self.reason)
                        if (x instanceof Promise) {
                            x.then(resolve, reject)
                        } else {
                            resolve(x)
                        }
                    })
                })
            }
        }

        Promise.prototype.catch = function (fn) {
            return this.then(null, fn)
        }
promise.all
 /*
1. Promise.all() 方法接收一个promise的iterable类型(注:Array,Map,Set都属于ES6的iterable类型)的输入。
 —— 说明所传参数都具有Iterable,也就是可遍历
2. 并且只返回一个Promise实例。—— 说明最终返回是一个Promise对象。
3. 那个输入的所有promise的resolve回调的结果是一个数组。
—— 说明最终返回的结果是个数组,且数组内数据要与传参数据对应
4. 这个Promise的resolve回调执行是在所有输入的promise的resolve回调都结束,或者输入的iterable里没有promise了的时候。
—— 说明最终返回时,要包含所有的结果的返回。
5. 它的reject回调执行是,只要任何一个输入的promise的reject回调执行或者输入不合法的promise就会立即抛出错误,并且reject的是第一个抛出的错误信息。
—— 说明只要一个报错,立马调用reject返回错误信息。
*/
const promiseAll = (iterator) => {
    const promises = Array.from(iterator);//对传入的数据进行浅拷贝//也可以不转化,直接使用for..of 进行遍历
    const len = promises.length;
    let count = 0;//每次执行成功+1,当count等于长度时,说明所有数据都返回,则可以reslove
    let data = [];//保存返回的数组
    return new Promise((resolve,reject)=>{
        promises.forEach((p,index)=>{
            Promise.resolve(p).then((res)=>{
                count++;
                data[index] = res;
                if(count==len){
                    resolve(data);
                }
            }).catch(err=>{
                reject(err);
            })
        })
    })

}

const promise1 = Promise.resolve('promise1');
const promise2 = new Promise(function (resolve,reject){
    setTimeout(resolve,2000,'promise2');
})
const promise3 = new Promise(function(resolve,reject) {
    setTimeout(resolve,1000,'promise3')

})

promiseAll([promise1,promise2,promise3]).then(function(values){
    console.log(values);
})
promise.race
Promise.prototype.race = function(iterator){
            return new Promise((resolve,reject)=>{
                for(const p of iterator){
                    Promise.resolve(p).then(res=>{
                        resolve(res);
                    }).catch(err=>{
                        reject(err)
                    })
                }
            })
        }
promise.resolve
 function myPromiseReslove(value){
            //如果原本就是 promise 类型的,那么直接返回
            if(value instanceof Promise){
                return value;
            }
            //如果是有 then 属性的对象,且then属性是个函数的,就返回一个 promise,这个 promise 中的方法用这个 then 属性
            if(Object.prototype.toString.call(value) ==="[object Object]" &&
                typeof value.then === "function"){
                return new Promise(value.then);
            }
            //如果是一个值的话,就直接作为 promise,且在 resolve 中传入这个值。
            return new Promise((resolve)=>{
                reslove(value)
            })

        }
promise.allsettled

在 Promise 中,resolve或者reject不会阻止Promise内的其余代码的执行

防抖

防抖:当持续触发事件,一定时间内若没有再次触发事件,就执行一次该事件处理函数;若在设定时间内再次触发, 那就重新开始延时 ,防抖执行的是最后一次触发的时间事件

// 防抖函数
function debounce(fn,delay){
    let timer = null
    return function(...args){
        //如果定时器以开启 就清除
        if(timer) clearTimeout(timer)
        // 重新计时 计时结束后才执行fn
        timer=setTimeout(() => {
            fn.apply(this, args)
        }, delay);
    }
}

节流

当持续触发事件时,保证一段时间内,只调用一次处理函数
应用场景: 点击事件(提交表单),高频监听事件

function throttle(fn, delay){
    let timer=null
    return function(...args){
        // 如果定时器存在 直接返回 
        if(timer) return
        // 设置定时任务 到时间后执行fn
        timer = setTimeout(()=>{
            fn.apply(this, args)
            timer = null
        },delay)
    }
}

手写call apply bind

call
Function.prototype.myCall = function(context,...args){
	if(typeof this != 'function') throw new Error('error')
	context = context || window
	context.fn = this
	let res = context.fn(...args)
	delete context.fn
	return res
}
apply
Function.prototype.myApply = function(context){
	if(typeof this != 'function') throw new Error('error')
	context = context || window
	context.fn = this
	let res=null
	if(arguments[1]){
		res = constext.fn(...arguments[1])
	}else{
		res = context.fn()
	}
	delete context.fn
	return res
}
bind

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

Function.prototype.myBind = function(context,...args){
            if(typeof this != 'function') throw new Error('error')
            context = context || window
            let fn = this
            return function Fn(){
                return fn.apply(
                    this instanceof Fn ? this : context,...args.concat(...arguments)
                )
            }
        }

较为完整的一版

function fn() {}
Function.prototype.myBind = function(context) {
    if (typeof this !== 'function') {
        throw new Error('The bind method is not available');
    }
    var context = context || window;
    var self = this;
    // myBind函数传进来的参数,从第2个开始取
    var args = (arguments.length && Array.prototype.slice.call(1, arguments.length)) || [];

    var returnFn = function() {
        var arr = (arguments.length && Array.prototype.slice.call(1, arguments.length)) || [];
        return self.apply(this instanceof returnFn ? this : context, arr.concat(args));
    };

    var templateFn = function() {};
    // 修正prototype之后,templateFn可以继承原函数原型的属性和方法
    templateFn.prototype = this.prototype;
    // 创建空函数实例,然后给returnFn的原型继承,returnFn创建的实例可以继承templateFn方法。
    // 这样修改了returnFn原型方法,都影响不了 原函数myBind原型对象,只会影响空函数templateFn。妙妙妙啊。
    returnFn.prototype = new templateFn();
    return returnFn;
};

var getFn = fn.myBind(obj);
var newObj = new getFn();
getFn.prototype.text = '我是被bind方法返回的函数'
console.log(fn.prototype.text) // => undefined

三者区别:

  • 三者都可以改变函数的this对象指向。

  • 三者第一个参数都是this要指向的对象,如果没有这个参数或参数为undefined或null,则默认指向全局window。

  • 三者都可以传参,但是apply是数组,而call是参数列表,且apply和call是一次性传入参数,而bind可以分为多次传入,bind是返回绑定this之后的函数,apply、call 则是立即执行。

水平垂直居中

结构: HTML+CSS 实现一个定宽定高元素在容器中的水平和垂直居中。

//结构
<div class="wrap">
  <div class="inner"></div>
</div>
//样式
.wrap {
  width: 500px;
  height: 500px;
  background-color: blue;
  
}
.inner {
  width: 300px;
  height: 300px;
  background-color: pink;
}

//第一种 定位:
.warp{
	position: relative;
}
inner{
	position: absolute;
	top:0;
	left:0;
	right:0;
	bottom: 0;
	margin: auto;
}

//第二种 transform
.warp{
	position: relative;
}
.inner{
	position: absolute;
	left: 50%;
	top: 50%;
	transform: translate(-50%, -50%);
}


//第三种 弹性布局
.warp{
	display: flex;
	justify-content: center;//主轴的对齐方式
  	align-items: center;//侧轴的对齐方式
}

对象深浅拷贝

概念:
浅拷贝: 只拷贝对象时仅拷贝对象本身,包括对象的基本变量,而不拷贝对象包含的引用指向的对象,即引用类型数据,拷贝后也是会发生引用;
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。

深拷贝:
在堆中重新分配内存,并且把源对象所有属性进行新建拷贝,保证拷贝后的对象与原来的对象时完全隔离,互不影响

举例:

浅拷贝

  1. 直接赋值
var obj = {
	name: 'aaa',
	age: 18
}
var copyObj = obj
copyObj.name = 'bbb'
console.log(obj.name);//bbb
console.log(copyObj.name);//bbb

在这里插入图片描述
2. Object.assign()
可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。
但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。

<script>
 var obj = {
            name: 'aaa',
            age: 30,
            son: {
                name: 'ccc',
                age: 8
            }
        }
        var copyObj = Object.assign({}, obj)
        obj.son.name = 'ddd';
        console.log(obj);//
        console.log(copyObj);
</script>

在这里插入图片描述
当object只有一层的时候,是深拷贝

var obj = {
            name: 'aaa',
            age: 30
        }
        var copyObj = Object.assign({}, obj)
        obj.name = 'ddd';
        console.log(obj);
        console.log(copyObj);

在这里插入图片描述
3. ES6扩展运算符(…)

<script>
  var obj = {
    name:'wade',
    age:37,
    friend:{
      name:'james',
      age:34
    }
  }
  var cloneObj = {...obj};
  obj.friend.name = 'curry'
  console.log(obj);
  console.log(cloneObj);
</script>

在这里插入图片描述
注意: Object只有一层时是深拷贝

//对象的深拷贝
 var obj = { name: '明明', age: 20 };
// console.log(...obj); //不能直接打印对象,会报错
 var obj1 = { ...obj };
// console.log(obj1);
 obj1.age = 22;
 console.log(obj); //age:20 不改变
  1. Array的slice和concat方法
    不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。
    原数组的元素会按照下述规则拷贝:
  • 如果该元素是个对象引用(不是实际的对象),slice 会拷贝这个对象引用到新的数组里。两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变。

  • 对于字符串、数字及布尔值来说(不是 String、Number 或者 Boolean 对象),slice 会拷贝这些值到新的数组里。在别的数组里修改这些字符串或数字或是布尔值,将不会影响另一个数组

<script>
  var arr = ['wade',37,{
    name:'james',
    age:34
  }]
  var cloneArr = arr.concat();
  arr[2].name = 'curry';
  console.log(arr);
  console.log(cloneArr);
</script>

在这里插入图片描述
5. jQuery中的 . e x t e n d ‘ .extend ` .extend.extend(deep,target,object1,objectN)`
deep: 如过设为true为深拷贝,默认是false浅拷贝
target:要拷贝的目标对象
object1:待拷贝到第一个对象的对象
objectN:待拷贝到第N个对象的对象

<script>
var obj = {
    name:'wade',
    age:37,
    friend:{
      name:'james',
      age:34
    }
  }
  var cloneObj = {};
  // deep 默认为 false 为浅拷贝
  $.extend(cloneObj,obj);
  obj.friend.name = 'rose';
  console.log(obj);
  console.log(cloneObj);
</script>

深拷贝:

1.$.extend(deep,target,object1,objectN)

 // deep 设为 true 为深拷贝
  $.extend(true,cloneObj,obj);
  obj.friend.name = 'rose';
  console.log(obj);
  console.log(cloneObj);
  1. JSON.parse(JSON.stringify())
    用JSON.stringify 将对象转换成JSON字符串,再用JSON.prase()解析成对象,对象会开辟新的栈,实现深拷贝。
    注意:这种方法虽然可以实现数组或对象深拷贝,但不能处理函数

这是因为 JSON.stringify() 方法是将一个JavaScript值(对象或者数组)转换为一个
JSON字符串,不能接受函数。(同样,正则对象也一样,在JSON.parse解析时会发生错误)。

<script>
  var obj = {
    name:'wade',
    age:37,
    friend:{
      name:'james',
      age:34
    }
  }
  var cloneObj = JSON.parse(JSON.stringify(obj))
  obj.name = 'rose';
  obj.friend.name = 'curry';
  console.log(obj);//rose curry
  console.log(cloneObj);//james wade
</script>

3.通过for-in遍历循环(递归)

 <script type="text/javascript">
    const _sampleDeepClone = target => {
    //判断原对象的层数
        if(typeof target === 'object' && target !== null){
        	//判断是否是数组对象
            let res = Array.isArray(target) ? [] : {};
            for(let i in target){
            	//属性为对象 递归处理
                if(typeof target[i] ==='object')
                    res[i] = _sampleDeepClone(target[i]);
                else{
                //直接赋值
                    res[i] = target[i];
                }
            }
            return res;
        }
        else
            return target;
    }
</script>

4.(…)三点运算符(Object只有一层)
5. Object.assign() Object只有一层

深拷贝的循环引用问题

解决方法: 用一个 map来存储引用类型,只需要判断一个对象的字段是否引用了这个对象或这个对象的任意父级即可
在map对象中获取当前参数的对象,如果能获取到,则说明这里为循环引用并返回Map对象中该参数的值
如果Map没有获取到对应的值,则将该参数保存在Map中作为标记。

<script type="text/javascript">
    const _completeDeepClone = (target, map = new Map()) => {
        // 补全代码
        if(typeof target === 'object' && target!=null){
            if(/^(Function|RegExp|Date|Set|Map)$/i.test(target.constructor.name)){
                return new target.constructor(target) //进行深拷贝
            }
            if(map.get(target)) return map.get(target) //已经遍历到 直接返回结果
            map.set(target,true) //还未遍历过 设置为true
            let obj = Array.isArray(target) ? [] : {}
            for(let i in target){ //开始遍历
                if(target.hasOwnProperty(i)){
                    obj[i] = _completeDeepClone(target[i],map)
                }else{
                    obj[i] = target[i]
                }
            }
            return obj
        }else{
            return target
        }
        
    }
</script>

数组扁平化

请补全JavaScript代码,要求将数组参数中的多维数组扩展为一维数组并返回该数组。 注意:
数组参数中仅包含数组类型和数字类型
输入:
[1,[2,[3,[4]]]]

输出:

[1,2,3,4]

1、使用for循环 递归

<script>
     var newarr = [];
    const _flatten = arr => {
        // 补全代码
        arr.forEach((item)=>{
            if(item instanceof Array === true) _flatten(item);
            else newarr.push(item);
        })
        return newarr;
    }
</script>

2、Es6 新特性 arr.flat(Infinity)

function _flatten1(arr) {
    return arr.flat(Infinity)
}

3、扩展运算符

//只要有一个元素有数组,那么循环继续
while(array.some(Array.isArray())){
	array = [].conact(...array)
}

对象扁平化

1.简单版 ,只考虑属性为对象或其他

//简单版 只考虑对象元素
function fn(obj){
    let res = {}//存储结果
    function isObj(curObj){
        for(let key in curObj){
            if(Object.prototype.toString.call(curObj[key]) == '[object Object]'){
                isObj(curObj[key]);
            }else{
                res[key] = curObj[key];
            }
        }
    }
    isObj(obj);
    return res;
}

2、考虑层级关系,扁平化后属性带有层级关系

//保留了父层节点关系的版本 但未考虑数组
function flattenObj(obj){
    let res = {}
    function isObj(curObj, prev){
        for(let key in curObj){
            if(Object.prototype.toString.call(curObj[key])==='[object Object]'){
                if(prev===null){
                    isObj(curObj[key], key);
                }else{
                    isObj(curObj[key], prev+'.'+key);
                }
            }else{
                if(prev===null){
                    res[key] = curObj[key];
                }else{
                    res[prev+'.'+key] = curObj[key]
                }
            }
        }
    }
    isObj(obj,null)//初始为父节点null
    return res;
}

3、详细版,考虑层级节点 ,并且考虑属性有数组的情况

//考虑有数组元素的版本
function flatten2(obj){
    let res={}
    function recurse(cur, prop) {
        // 如果输入进来的是不是对象,就将其放在数组中,返回
        if (Object(cur) !== cur) {
            res[prop] = cur;
        // 如果输入进来的是数组,长度不为0就递归数组,得出结果
        } else if (Array.isArray(cur)) {
            for (var i = 0, len = cur.length; i < len; i++)
                recurse(cur[i], prop + "[" + i + "]");
            if (len == 0)  res[prop] = [];
        } else {
            var isEmpty = true;
            for (var p in cur) {
                isEmpty = false;
                // recurse(cur[p], p); //不要层级节点 就用这个
                recurse(cur[p], prop ? prop + "." + p : p);
            }
            if (isEmpty && prop)
                res[prop] = {};
        }
    }
    recurse(obj,"");
    return res;
}

测试:

let obj={
    a:[1,2],
    b:2,
    c:{
        d:3,
        e:4,
        f:{
            g:5,
            h:{
                i:6
            }
        }
    },
    y:[
        {a: 1, b:2},
        {a: 3, b:4},
        {a: 5, b:{
            c: 7
        }},
    ]
}
console.log("直接扁平化:");
console.log(fn(obj));
console.log("考虑层级关系:");
console.log(flattenObj(obj));
console.log("考虑有数组元素的版本");
console.log(fnn(obj));

在这里插入图片描述

对象判空方法

1、将json对象转化为json字符串,再判断该字符串是否为"{}"

var data = {}
console.log(JSON.stringfy(data)=="{}")// true

2、for …in 循环判断,设置一个标记,进了循环说明对象不为空,return false

var data={}
var b = function(){
for(let key in data){
	return false;
}
return true;
}
alert(b) //true;

3、用es6的新特性 Object.keys(),判断返回的数组长度是否为0

var data = {};
var arr = Object.keys(data);
alert(arr.length == 0);//true

函数柯里化

function curring(fn){
            const args = [];
            return function reslut(...rest){
                if(rest.length===0) return fn(...args);
                else{
                    args.push(...rest);
                    return reslut;
                }
            }
        }

手写数组转树状结构数据

// 例如将 input 转成output的形式
let input = [
    {
        id: 1, val: '学校', parentId: null
    }, {
        id: 2, val: '班级1', parentId: 1
    }, {
        id: 3, val: '班级2', parentId: 1
    }, {
        id: 4, val: '学生1', parentId: 2
    }, {
        id: 5, val: '学生2', parentId: 2
    }, {
        id: 6, val: '学生3', parentId: 3
    },
]

let output = {
    id: 1,
    val: '学校',
    children: [{
        id: 2,
        val: '班级1',
        children: [
            {
                id: 4,
                val: '学生1',
                children: []
            },
            {
                id: 5,
                val: '学生2',
                children: []
            }
        ]
    }, {
        id: 3,
        val: '班级2',
        children: [{
            id: 6,
            val: '学生3',
            children: []
        }]
    }]
}

// 代码实现
function arrayToTree(array) {
    let root = array[0]
    array.shift()
    let tree = {
        id: root.id,
        val: root.val,
        children: array.length > 0 ? toTree(root.id, array) : []
    }
    return tree;
}

function toTree(parenId, array) {
    let children = []
    let len = array.length
    for (let i = 0; i < len; i++) {
        let node = array[i]
        if (node.parentId === parenId) {
            children.push({
                id: node.id,
                val: node.val,
                children: toTree(node.id, array)
            })
        }
    }
    return children
}

console.log(arrayToTree(input))

max和min大小比较 解释原因

var min = Math.min();
max = Math.max();
console.log(min < max);
// 写出执行结果,并解释原因

结果: false
原因:Math.min 的参数是 0 个或者多个,如果多个参数很容易理解,返回参数中最小的。如果没有参数,则返回 Infinity,无穷大
Math.max 在没有参数的返回 -Infinfity

delete 删除属性 解释

例题:

var company = {
    address: 'beijing'
}
var yideng = Object.create(company);
delete yideng.address
console.log(yideng.address);
// 写出执行结果,并解释原因

结果:beijing
解释:这里的 yideng 通过 prototype 继承了 company的 address。yideng自己并没有address属性。所以delete操作符的作用是无效的。

扩展: delete操作符的使用

delete使用原则:delete 操作符用来删除一个对象的属性。
1.可以删除隐式全局变量,但不可已删除显示全局变量。
全局变量其实是global对象(window)的属性。

x = 10;
var y = 20;
delete x; //true;
delete y; //false

2.内置对象的内置属性不能被删除,用户自定义的属性可以被删除。

obj = {
    h : 10
}
var obj1 = {
    h: 10
}

delete Math.PI; // false
delte obj.h; //true
delete obj; //ture ,obj 是全局变量的属性,而不是变量。

delete obj1.h;//true
delete obj1; //false 全局显示变量不能被删除

function fn(){
    var z = 10;
    delete z; //false
    //z是局部变量,不能被删除,delete只能删除对象的属性。
}
delete fn; //false
//fn 相当于是一个匿名变量,所以也不能被删除。

3.不能删除一个对象从原型继承而来的属性,但是可以直接从原型上删掉它;


function foo(){}
foo.prototype.name = 'zhangsan';
var f = new foo();

//delete只能删除自己的属性,不能删除继承来的属性
delete f.name; // false 
console.log(f.name);//zhangsan
delete foo.prototype.anme; // true
console.log(f.name); // undefined

4.当删除数组元素时,数组的长度并不会变小,数组元素变成undefined。


var arr = [1,2,4,6,7,8];
delete arr[2];

console.log(arr.length); // 6
console.log(arr[2]); //undefiend
consoel.log(arr); //[ 1, 2, , 6, 7, 8]

删除数组元素方法:delete+filter

delete在forEach 循环中删除元素,不会影响循环结果

var arr = [1,3,5,21,3,4,53,21,5,2];
arr.forEach(function(val,index){
    if(val < 10){
        delete arr[index];
    }
})
console.log(arr); //[ , , , 21, , , 53, 21, ,  ]
//可以使用filter过滤掉空值 [ 21, 53, 21 ]
arr = arr.filter(function(val){return val});

如果用splice删除元素会改变数组的长度。
所以说删除一个值后,其后的那个值占据了它的位置,在判断的时候就会漏掉。

总结:

  • delete能删除的:
    (1)可配置对象的属性
    (2)隐式声明的全局变量
    (3)用户定义的属性
    (4)在ECMAScript 6中,通过 const 或 let 声明指定的 “temporal dead zone” (TDZ) 对 delete 操作符也会起作用
  • delete不能删除的:
    (1)显式声明的全局变量
    (2)内置对象的内置属性
    (3)一个对象从原型继承而来的属性

[‘1’,‘2’,‘3’].map(parseInt)输出

let arr = ['1','2','3'];
let result = arr.map(parseInt);
console.log(result); // expected: [1,NaN, NaN]

原理:
map里面会自动传入3个参数(currentValue,index, array),但是这里,parseInt函数最多接受两个参数,也就是,currentValue 和 index

console.log(['1','2','3'].map(parseInt));  //[1, NaN, NaN]
// 等同于
['1','2','3'].map((item,index)=>{
    return parseInt(item,index)
}

parseInt(‘1’,0)返回的是0,parseInt(‘2’,1)返回的是NaN,因为第二个参数必须是2~64之间的数,parseInt(‘3’,2)返回的是NaN,因为二进制不包括3这个数字,因此会得到1,NaN,NaN的结果。
parseInt(string [, radix]) radix范围:2-36
parseInt() 函数可解析一个字符串,并返回一个整数。

用法:
当参数 radix 的值为 0,或没有设置该参数时,parseInt() 会根据 string 来判断数字的基数。当忽略参数 radix , JavaScript 默认数字的基数如下:

  • 如果 string 以 “0x” 开头,parseInt() 会把 string 的其余部分解析为十六进制的整数。
  • 如果 string 以 0 开头,那么 ECMAScript v3 允许 parseInt() 的一个实现把其后的字符解析为八进制或十六进制的数字。
  • 如果 string 以 1 ~ 9 的数字开头,parseInt() 将把它解析为十进制的整数。

模板字符串``

  • 模板字符串的空格和换行,都是被保留的
$('#list').html(`
<ul>
  <li>first</li>
  <li>second</li>
</ul>
`);
  • 模板字符串中嵌入变量,需要将变量名写在${}之中。
let x = 1;
let y = 2;

`${x} + ${y} = ${x + y}`
// "1 + 2 = 3"
  • 模板字符串之中还能调用函数
function fn() {
  return "Hello World";
}

`foo ${fn()} bar`
// foo Hello World bar
  • 模板字符串甚至还能嵌套。
  • 可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。这被称为==“标签模板==”功能(tagged template)。
alert`hello`
// 等同于
alert(['hello'])

标签模板其实不是模板,而是函数调用的一种特殊形式。“标签”指的就是函数,紧跟在后面的模板字符串就是它的参数。
如果模板字符里面有变量,就不是简单的调用了,而是会将模板字符串先处理成多个参数,再调用函数

let a = 5;
let b = 10;

tag`Hello ${ a + b } world ${ a * b }`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);

第一个参数是一个数组,该数组的成员是模板字符串中那些没有变量替换的部分,其他参数,都是模板字符串各个变量被替换后的值

  • “标签模板”的一个重要应用,就是过滤 HTML 字符串,防止用户输入恶意内容。

source map

sourse map 就是把压缩后的代码映映射到原始源的文件。
问题:

1、source map 那么大,不会影响网页性能吗?

不会影响,source map 只有在打开 dev tools 的情况下才会开始下载。

2、浏览器是怎么知道哪个文件对应哪个source map的?

当我们开启 source map 选项以后,产物应该为两个文件,分别为 bundle.js 以及 bundle.js.map。
sourceMappingURL 就是标记了该文件的 source map 地址。

3、那么多打包器和浏览器是存在什么标准才能让他们通用的吗?

source map 是存在一个标准的,为 Google 及 Mozilla 的工程师制定,文档地址:https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit。正是因为存在这份标准,各个打包器及浏览器才能生成及使用 source map,否则就乱套了。

4、source map是如何一对一对应到代码的?

关键就是map文件的mappings属性,内容其实是 Base64 VLQ 的编码表示,这是一个很长的字符串,它分成三层。
1、英文,表示源码及压缩代码的位置关联
2、逗号,分隔一行代码中的内容。比如说 console.log(a) 就由 console 、log 及 a 三部分组成,所以存在两个逗号。
3、分号,代表换行

JSON.parse() 用法及参数说明

JSON.parse() 将数据转换为JS对象
JSON.parse(text[, reviver]) 参数:

  • text:必需, 一个有效的 JSON 字符串。
  • reviver: 可选,一个转换结果的函数, 将为对象的每个成员调用此函数。
    用法:
var text = '{ "name":"Amy", "initDate":"2022-09-15","Datas":"09-15"}';
var obj = JSON.parse(text, function (key, value) {
    if (key == "Datas") {//日期对象转换
        return new Date(value);
    } else {
        return value;
}});

数组也可以:

let arrJSON = '["kelly",18]'
console.log(JSON.parse(arrJSON)); //  ['kelly', 18]

注意:

  • JSON 不能存储 Date 对象。
    如果需要存储 Date 对象,需要将其转换为字符串。之后再将字符串转换为 Date 对象。
    例子:
    var text = '{ "name":"Amy", "age":10, "initDate":"2022-09-15"}';
    var obj = JSON.parse(text);
    obj.initDate = new Date(obj.initDate);
    
  • JSON 不允许包含函数,但可以将函数作为字符串存储,之后再将字符串转换为函数。

JSON.stringify() 将对象、数组转换为JSON格式字符串

let obj = {name:'Amy',age:18,hobby:'food'}
let arr = [1,2,'a']
let objStr = JSON.stringify(obj);
console.log(objStr); // {"name":"Amy","age":18,"hobby":"food"}

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;