一、转换
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 的问题
七、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))); // 部分数据丢失或转义
2、如果对象的属性使用了new 一个构造函数,会丢失构造函数
3、深拷贝循环引用的对象,会引起报错
var a = {};
a.b = a;
console.log(a); // 内存指向相互引用 无尽头 但不会内存溢出
console.log(JOSN.parse(JSON.stringify(a))); // 直接报错
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.类型转换的规则:
- 先判断是否在对比null和undefined,是的话就返回true
- 判断两者的类型是否为string和number,是的话,就将string转成number
- 判断两者的类型是否为boolean和number,是的话,就将boolean转成number
- 判断其中一方是否为
object且另一方为string、number或者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
| typeof | null | undefined |
|---|---|---|
| 含义 | 空对象 | 未定义 |
| 解释 | 可能返回对象的变量,用null做初始化 | 一个变量声明了但是还没有定义时会返回undefined |
| null == undefined | true | true |
| null === undefined | false | false |
| typeof | object | undefined |
思考💡:为什么typeof null === 'object'?
用底层的问题解释是:object的底层表示是000;null的底层表示是null指针,null指针全是0
所以会出现typeof null === 'object'