js|易错,易混淆的各种误区总结🎈🎈

221 阅读13分钟

一、转换

1、split

字符串转数组

str = '1234567';
console.log(str.split('')); // ['1', '2', '3','4', '5', '6','7']

2、join

数组转字符串

arr  = [1,2,3];
console.log(arr.join('')); //123
console.log(arr.join('-')); // 1-2-3
console.log(arr.join('*')); // 1*2*3

3、splice

删除:splice(删除元素的位置,删除元素数量)

arr  = [1,2,3];
console.log(arr.splice(1,1)); // [ 2 ]

插入:splice(开始位置,0,要插入元素)

arr  = [1,2,3];
arr.splice(1,0,4);
console.log(arr); // [ 1, 4, 2, 3 ]

替换:splice(开始位置,删除元素数量,插入的任意多个元素)

arr  = [1,2,3];
arr.splice(1,1,3456);
console.log(arr); // [ 1, 3456, 3 ]

splice()总是返回这样一个数组,他包含从数组中被删除的元素,如果没有元素,返回空数组

4、slice

字符串和数组的截取方法

str = '1234567';
console.log(str.slice(0,3)); //123
arr = [1,2,3,4,5];
console.log(arr.slice(0,3)); // [ 1, 2, 3 ]

二、深拷贝

1、slice

const _delete = (array, index) => {
    // 补全代码
    var newArr = array.slice();
    newArr.splice(index, 1); 
    console.log(array); // [ 1, 2, 3 ] 刪除之後,原数组不变
    return newArr;
}
console.log(_delete([1, 2, 3], 1)); // [ 1, 3 ]

2、concat

const _delete = (array, index) => {
    // 补全代码
    var newArr = array.concat();
    newArr.splice(index, 1); 
    console.log(array); // [ 1, 2, 3 ] 刪除之後,原数组不变
    return newArr;
}
console.log(_delete([1, 2, 3], 1)); // [ 1, 3 ]

3、for循环依次赋值

const _delete = (array, index) => {
    // 补全代码
    var newArr = [];
    for(let i of array){
        newArr.push(i);
    }
    newArr.splice(index, 1); 
    console.log(array); // [ 1, 2, 3 ] 刪除之後,原数组不变
    return newArr;
}
console.log(_delete([1, 2, 3], 1)); // [ 1, 3 ]

4、JSON.parse(JSON.stringify)

const _delete = (array, index) => {
    // 补全代码
    var newArr = JSON.parse(JSON.stringify(array))
    newArr.splice(index, 1); 
    console.log(array); // [ 1, 2, 3 ] 刪除之後,原数组不变
    return newArr;
}
console.log(_delete([1, 2, 3], 1)); // [ 1, 3 ]

三、相等问题

NaN == NaN // false
NaN === NaN // false
{} === {} // false
[] === [] // false
{} == {} // false
[] == [] // false

    + 0 == -0; // true
+0 === -0; // true

typeof null === 'object' // true

'8' == 8 // true
!'' // true
![] // false
!{} // false

Symbol(1) === Symbol(1) // false
Symbol.for(1) === Symbol.for(1) // true

![3][2] // true
!-1 // false

null == undefined // true
null === undefined  // false

console.log(Object instanceof Function); // true
console.log(Function instanceof Object); // true

四、假值

js的6个假值 0 null undefined NaN '' false

五、set去重

set会去重重复的NaN、null、undefined,但是不会去重重复的{}、[]、function f
let set = new Set([null, null, NaN, NaN, {}, {}, [], [], undefined, undefined, function f() { }, function f() { }]);
console.log(set); // Set(9) { null,NaN,{},{},[],[],undefined,[Function: f],[Function: f]}

六、0 的问题

image (3).png

七、json和js

js对象转json 字符串:json = JSON.stringify(js);

let obj = {
    a: 'hello',
    b: 'world'
}
let json = JSON.stringify(obj);
console.log(json); // {"a":"hello","b":"world"}
console.log(typeof json); // string 

json字符串转成js对象js = JSON.parse(json);

let json = '{"a":"hello","b":"world"}';
let obj = JSON.parse(json);
console.log(obj); // { a: 'hello', b: 'world' }

八、类数组对象

一、类数组对象

1、arguments

2、DOM方法的返回值NodeList

二、类数组对象转数组的方法

1、通过call调用数组的slice方法

Array.prototype.slice.call(arrayLike);

2、通过call调用数组的splice方法

Array.prototype.splice.call(arrayLike, 0);

3、通过apply调用数组的concat方法

Array.prototype.concat.apply([], arrayLike);

4、通过Array.from()方法

Array.from(arrayLike);

5、扩展运算符

[...arrayLike]

九、类数组对象

1、会造成不同程度的数据丢失

var obj = {
        name: 'zhangsan',
        date: new Date(),
        regExp: new RegExp("\w+"),
        fun: function () { return true;},
        err: new Error('error'),
        symbol: Symbol(233),
        undefined: undefined,
        null: null,
        nan: NaN,
        infinity: Infinity // 无穷大
};
console.log(obj); // 正常
console.log(JOSN.parse(JSON.stringify(obj))); // 部分数据丢失或转义

image.png

2、如果对象的属性使用了new 一个构造函数,会丢失构造函数

image (1).png

3、深拷贝循环引用的对象,会引起报错

 var a = {};
 a.b = a;
 console.log(a); // 内存指向相互引用 无尽头 但不会内存溢出
 console.log(JOSN.parse(JSON.stringify(a))); // 直接报错 

image (2).png

4、JSON.parse(JSON.stringify)

JSON.parse(JSON.stringify)为什么可以实现深拷贝?

使用JSON.stringify将对象序列化,转成字符串后,存储在硬盘上,在通过JSON.parse()反序列化,将字符串转成对象

实现深拷贝的缺点?

会将JS对象中的时间对象转化为字符串 会将JS对象中的RegExp、Error对象转化为空对象 会将JS对象中的function、undefined、Symbol丢失 会将JS对象中的NaN、Infinity和-Infinity转化为null 会将JS对象中有构造函数生成的对象的 constructor 丢失 如果JS对象存在循环引入将无法实现深拷贝

十、 数据劫持or数据代理

一、object.defineProperty

1、object.defineProperty存在的问题

不能监听数组的变化

//无法监听到数组
let arr = [1, 2, 3];
let obj = {};
Object.defineProperty(obj, 'arr', {
    get() {
        console.log("get err");
        return arr;
    },
    set(newVal) {
        console.log('set', newVal);
        arr = newVal;
    }
})
obj.arr.push(4); // 只调用了get方法,没有调用set方法
obj.arr = [1, 2, 3, 4]; //这种方式可以检测到数据的变化

2、Vue的解决方案,把方法重写

// Vue的解决方案,把方法重写实现数据劫持
const arrMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
const arrayAugmentations = [];
arrMethods.forEach((method) => {
    let original = Array.prototype[method]; //原生的Array的原型方法
    arrayAugmentations[method] = function () {
        console.log("我被改变了");
        console.log(this); //this指向的是list数组[ 'a', 'b', 'c' ]
        return original.apply(this, arguments);  // 调用对应的原生方法并返回结果
    }
})
let list = ['a', 'b', 'c'];
// 将我们监听的数组原型指针指向上面定义的空数组对象
list.__proto__ = arrayAugmentations;
list.push('d'); // 我被改变了
let list2 = ['a', 'b', 'c']; // list2是原生的数组,没有指向我们定义的方法
list2.push('d'); //没有输出

3、必须遍历对象的每一个属性

//必须遍历对象的每一个属性
Object.keys(obj).forEach(key=>{
    Object.defineProperty(obj,key,{
        
    })
})

4、如果是嵌套对象,必须进行深层遍历

//必须进行深层遍历
let obj = {
    info:{
        name:'person',
        age:18
    }
}

二、Proxy

1、针对对象

let obj = {
    name: 'Eason',
    age: 30
}
let handler = {
    get(target, key, receiver) { // get方法返回获取的值
        // console.log("target:",target); //{ name: 'yom', age: 18 } 目标对象
        // // console.log("receiver:",receiver); // { name: 'yom', age: 18 } 目标对象
        console.log('get', key);
        return Reflect.get(target, key, receiver);  // 返回值
    },
    set(target, key, value, receiver) { //set方法设置属性成功返回true,失败返回false
        console.log('set', key, value);
        return Reflect.set(target, key, value, receiver);
    }
}
let proxy = new Proxy(obj, handler);
proxy.name = 'yom'; // set name yom
proxy.age = 18; // set age 18
console.log(proxy.name);
// get name
// yom
console.log(proxy.age);
// get age
// 18

2、支持数组

let array = [1, 2, 3];
let proxy = new Proxy(array, {
    get(target, key, receiver) {
        console.log('get', key);
        return Reflect.get(target, key, receiver);
    },
    set(target, key, value, receiver) {
        console.log('set', key, value);
        return Reflect.set(target, key, value, receiver);
    }
})
proxy.push(4);
// get push 得到方法名是push
// get length // 得到数组的长度属性
// set 3 4 // 设置下标 3 值是4
// set length 4 // 改变长度是4

3、不支持嵌套,需要在get方法里递归调用proxy

let obj = {
    info: {
        name: 'tom',
        blogs: ['webpack', 'babel', 'cahce']
    }
}
let handler = {
    get(target, key, receiver) {
        console.log('get', key)
        if (typeof target[key] === 'object' && target[key] !== null) {
            return new Proxy(target[key], handler); //递归调用
        }
        return Reflect.get(target, key, receiver);
    },
    set(target, key, value, receiver) {
        console.log('set', key, value);
        return Reflect.set(target, key, value, receiver);
    }
}
let proxy = new Proxy(obj,handler);
proxy.info.name = 'tom';
proxy.info.blogs.push('proxy')
// get info
// set name tom
// get info
// get blogs
// get push
// get length
// set 3 proxy
// set length 4

proxy的第二个参数,支持13种拦截方法,比Object.defineProperty()更加丰富

proxy做为新标准,受到了关注和性能优化

proxy的兼容性不如Object.defineProperty()

4、相关面试题

a 可以满足 (a === 1 && a === 2 && a === 3) === true 呢?

let current = 0;
Object.defineProperty(window, 'a', {
    get() {
        current++;
        return current;
    }
})
console.log(a === 1 && a === 2 && a === 3); // true

三、多继承

let foo = {
    foo() {
        console.log('foo');
    }
}
let bar = {
    bar() {
        console.log('bar');
    }
}
// 正常情况下,对象只能继承一个对象
let sonOfFoo = Object.create(foo);
sonOfFoo.foo(); //foo
let sonOfBar = Object.create(bar);
sonOfBar.bar(); //bar
let sonOfFooBar = new Proxy({}, {
    get(target, key) {
        return target[key] || foo[key] || bar[key];
    }
})
sonOfFooBar.foo(); //既有foo
sonOfFooBar.bar(); //还有bar

十一、立即执行函数

1、声明式函数

/**
 * 函数的声明式写法,会导致变量提升
 */
function foo(a, b) {
    console.log(a + b); // 3
}
foo(1, 2)

2、表达式函数

/**
 * 函数的表达式写法,不会导致变量提升
 */
let foo1 = function (a, b) {
    console.log(a + b);
}

3、立即执行函数

/**
 * 立即执行函数
 */
(function foo2(a, b) {
    console.log(a + b); //3 
}(1, 2))

4、立即执行函数

/**
 * 立即执行函数
 */
(function foo3(a,b){
    console.log(a+b); // 5
})(1,4)

立即执行函数创建了一个独立的作用域,这个作用域里的变量外部访问不到,防止变量污染

十二、ajax-axios-fetch的区别

一、ajax

1、优点:

传统的Ajax指的是XMLHttpRequest,最早出现的发送后端请求技术,核心使用的是XMLHttpRequest对象 JQuery ajax是对原生XHR的封装,除此之外还提供了对JSONP的支持

2、缺点:

本身是针对MVC编程,不符合现在前端MVVM的浪潮 基于原生的XHR开发, XHR本身的架构不清晰 JQuery整个项目太大,为了使用Ajax就引入JQuery非常的不合理 不关注分离原则 配置和调用非常混乱

3、用法:

$.ajax({
    type:'POST',
    url:url,
    data:data,
    dataType:dataType,
    success:function(){},
    error:function(){}
})

二、axios

1、优点:

基于promise用于浏览器和nodejs的HTTP客户端 本质也是对原生XHR的封装,但是是Promise的实现, 从浏览器创建XMLHttpRequest 支持promise API 客户端支持防止CSRF 提供一些并发请求的接口 从nodejs创建http请求 转换请求数据和响应数据 取消请求 自动转换json数据

2、用法:

axios({
method:'post',
url:'/user/1245',
data:{
firstname:'tom',
lastname:'jack'
},
}).then(function (response){

}).catch(function(error){

})

三、fetch

1、优点:

语法简单,更加语义化 基于标准的promise实现,支持async/await 同构放便 更加底层,提供丰富的API 脱离了XHR,是ES中新的规范 fetch不是ajax的封装,是原生的js,没有使用XHR

2、缺点:

fetch只对网络请求报错,对400,500 都认为请求成功,需要封装去处理

fetch默认不会携带cookie,需要添加配置项

fetch不支持abort,不支持超时控制,使用setTimeout和Promise.reject实现的超时控制不能阻止代码在后端运行,造成量的浪费

fetch没有原生检测请求的进度,而XHR可以

3、用法:

try {
  let response = await fetch(url);
  let data = response.json();
  console.log(data);
} catch(e) {
  console.log("Oops, error", e);
}

十三、for...in 和for...of区别

1、返回值

for...in遍历对象,返回对象的键名

for...of 遍历对象,返回对象的键值

for...of 适合遍历数组、类数组对象、Map、Set、字符串等

for...in 适合遍历对象

2、是否遍历原型链

for...in 会遍历对象的原型链上的属性

for...of 只会遍历对象自己的属性

for...in 会遍历数组的所有属性,包括了原型链上的

for..of 只会遍历数组本身的下标的值

3、有Symbol.iterator属性的数据结构

Map、Set、arguments、数组、String、NodeList

数组扩展符[...]和解构赋值和for of 都针对以上的Symbol.iterator属性的数据结构

4、for of 遍历普通对象会报错

类数组对象使用Array.from()转成数组

普通对象可以尝试添加Symbol.iterator属性

十四、Map和WeakMap的区别

一、Map

1、 本质

Map的本质也是键值对的组合,但是Map的键可以是任意数据类型,但是对象的键只能是字符串

const map = new Map([
     ["foo",1],
     ["bar",2],
])

2、操作方法

size: Map.size 返回Map结构的大小。

set(key,value):设置键名key对应的键值value,然后返回整个Map结构,如果key已经有值,则键值会被更新,否则就新生成该键。(因为返回的是当前Map对象,所以可以链式调用)

get(key):该方法读取key对应的键值,如果找不到key,返回undefined。

has(key):该方法返回一个布尔值,表示某个键是否在当前Map对象中。

delete(key):该方法删除某个键,返回true,如果删除失败,返回false。

clear():map.clear()清除所有成员,没有返回值。

3、遍历方法

forEach()

keys()

values()

entires()

二、WeakMap

1、本质

WeakMap本质也是键值对的组合,但是键必须是对象

const key1 = {id: 1},
      key2 = {id: 2},
      key3 = {id: 3};
// 使用嵌套数组初始化弱映射
const wm1 = new WeakMap([
  [key1, "val1"],
  [key2, "val2"],
  [key3, "val3"]
]);

2、操作方法

set(key,value) :设置键名key对应的键值value,然后返回整个Map结构,如果key已经有值,则键值会被更新,否则就新生成该键。(因为返回的是当前Map对象,所以可以链式调用)

get(key) :该方法读取key对应的键值,如果找不到key,返回undefined。

has(key) :该方法返回一个布尔值,表示某个键是否在当前Map对象中。

delete(key) :该方法删除某个键,返回true,如果删除失败,返回false。

3、弱引用

WeakMap产生的原因是,有时候只想使用对象存放一些数据,但是会形成对这个对象的引用,一旦不需要引用这个对象,就需要手动的去删除引用,否则垃圾回收机制就不会释放对象占用的内存。 但是WeakMap的对象的引用是弱引用,只要所引用对象的其他引用都被清除,垃圾回收机制就会自动释放这个对象占用的内存,WeakMap中的键名和键值都会自动消失,不需要手动清除。

三、总结

1、键

Map的键是任意数据类型、WeakMap的键只能是对象

2、弱引用

WeakMap的键是弱引用

十五、如何判断一个对象是空对象?

1.将对象转换成字符串,再判断是否等于“{}”

let obj={};
console.log(JSON.stringify(obj)==="{}")

2.Object.keys()方法

console.log(Object.keys(obj).length==0);// true

3.Object.getOwnPropertyNames

方法获取对象的属性名,存到数组中,若长度为0,则为空对象

console.log(Object.getOwnPropertyNames(obj).length==0);//true

4.for in循环

let result=function(obj){
    for(let key in obj){
        return false;//若不为空,可遍历,返回false
    }
    return true;
}
console.log(result(obj));//返回true

十六、三种等于方式

1."==":

会先进行类型判断,类型如果不一致,会进行类型转换,然后比较大小

2."===":

类型不一致,直接返回false

3.Object.is

进行了优化,在Object.is的前提下:+0 ≠ -0; NaN= NaN

如果是在"===" 情况下:+0 === -0 ; NaN≠NaN

4.类型转换的规则:

  1. 先判断是否在对比null和undefined,是的话就返回true
  2. 判断两者的类型是否为string和number,是的话,就将string转成number
  3. 判断两者的类型是否为boolean和number,是的话,就将boolean转成number
  4. 判断其中一方是否为 object 且另一方为 stringnumber 或者 symbol,是的话就会把 object 转为原始类型再进行判断

十七、object.assign和...的区别?

1.扩展运算符:

let outObj = {
  inObj: {a: 1, b: 2}
}
let newObj = {...outObj}
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}

2.Object.assign()

let outObj = {
  inObj: {a: 1, b: 2}
}
let newObj = Object.assign({}, outObj)
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}

Object.assign()和扩展运算符第一层实现的是深拷贝

3.区别

Object.assign()第一个参数是目标对象,后面的参数都是源对象,然后把所有的源对象合并到目标对象中,会修改一个对象,会触发Es6 setter()方法

扩展运算符(...) 数组和对象的每一个值都会被拷贝到一个新的数组或者对象中,不复制继承的属性或者类属性,但是会复制Es6的Symbol属性。

十八、NaN

1. NaN的类型

typeof NaN // number
NaN == NaN // false
NaN === NaN // false
NaN !== NaN  // true

2.isNaN()和Number.isNaN()的区别?

isNaN()会先进行类型转换,如果转换之后是number,返回false,否则返回true

console.log(isNaN(12))     //false
console.log(isNaN(true))   //false
console.log(isNaN("12"))   //false
console.log(isNaN("hhh"))  //true

Number.isNaN()不会进行类型转换,只有NaN返回true,其他情况都返回false

console.log(Number.isNaN(121))     //false
console.log(Number.isNaN("121"))   //false
console.log(Number.isNaN(NaN))     //true
console.log(Number.isNaN("hhh"))   //false

十九、小数计算

思考💡: 0.1+0.2 = 0.3?

console.log(0.1 + 0.2 === 0.3);// false

1. 原因分析

十进制转换成2进制的时候,会损失精度 0.1 转化成2进制数的时候是0.00011.....是一个最接近于0.1的数

2. 如何使得相等

toFixed

使用 toFixed() 方法可以四舍五入为指定小数位数的数字,得到的返回值是 String(字符串) 类型;这一点,官方也有介绍到; 该方法调用的是 NumberObject.toString(), 返回采用指数计数法 表示的 字符串

console.log(Number((0.1+0.2).toFixed(1)) === 0.3);
console.log(parseFloat((0.1+0.2).toFixed(1)) === 0.3);

极小数

Number.EPSILON ES6提供的极小数

Number.EPSILON 属性表示 1 与Number可表示的大于 1 的最小的浮点数之间的差值

function isXiaoShu(arg1, arg2) {
    return Math.abs(arg1 - arg2) < Number.EPSILON
}
console.log(isXiaoShu(0.1 + 0.2, 0.3)); // true

二十、null和undefined

typeofnullundefined
含义空对象未定义
解释可能返回对象的变量,用null做初始化一个变量声明了但是还没有定义时会返回undefined
null == undefinedtruetrue
null === undefinedfalsefalse
typeofobjectundefined

思考💡:为什么typeof null === 'object'?

用底层的问题解释是:object的底层表示是000;null的底层表示是null指针,null指针全是0

所以会出现typeof null === 'object'

参考👀

www.cnblogs.com/jdWu-d/p/11…