1.for in和for of的区别
for in
- 遍历对象可枚举的属性,包括原型链上面的属性
for of
- 适用可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)实现了[symbol.iterator]接口
- 但是不能遍历对象,因为没有迭代器对象
- 只会输出当前对象的值,不会去找原型链
const arr = [1,2,3,4]
// for ... in 输出索引
for (const key in arr){
console.log(key) // 输出 0,1,2,3
}
// for ... of 输出value
for (const key of arr){
console.log(key) // 输出 1,2,3,4
}
2.数组和伪数组的区别
伪数组的关联对象不是数组的prototype属性指向的对象,所以没有数组的方法
3.浅拷贝和深拷贝的区别
- 浅拷贝---浅拷贝只拷贝一层,更深层次对象级别的只拷贝引用
- 深拷贝---深拷贝拷贝多层,每一级别的数据都会拷贝
// ES5浅拷贝
let obj = {
id: 1,
name: 'andy',
msg: { title: '标题' },
color: ['red', 'green']
};
let o = {};
for(let k in obj){
o[k] = obj[k];
}
// ES6 浅拷贝方法1
// Object.assign()方法用于对象的合并,将源对象(source)的所有可枚举属性,不包括原型链,复制到目标对象(target)
let m = {};
Object.assign(m, obj);
// ES6 浅拷贝方法2
let n = { ...obj };
// ES5深拷贝方法1
function deepCopy(oldObj, newObj){
for(let k in oldObj){
const value = oldObj[k]
if(Array.isArray(value)){
newObj[k] = []
deepCopy(value, newObj[k])
}else if(typeof value === 'object'){
newObj[k] = {}
deepCopy(value, newObj[k])
}else {
newObj[k] = value
}
}
}
// ES5深拷贝方法2
const json = JSON.stringify(obj)
const obj2 = JSON.parse(json)
4.ES6中Symbol的应用
- 作为对象的键,对象的键可以是字符串和symbol值
- Symbol.iterator
- Symbol.toPrimitive -> 对象转数字Number([val]) 对象转字符串String([val])的第一部都是看看有没有这个属性,有的话执行该方法,看看获得的是原始值就转换成功了
- 使用Symbol来替代常量
Symbol.toPrimitive
// 一个没有提供 Symbol.toPrimitive 属性的对象,参与运算时的输出结果
const obj1 = {};
console.log(+obj1); // NaN
console.log(`${obj1}`); // "[object Object]"
console.log(obj1 + ""); // "[object Object]"
// 手动赋予了 Symbol.toPrimitive 属性,再来查看输出结果
const object1 = {
[Symbol.toPrimitive](hint) {
console.log(hint);
if (hint === 'number') {
return 42;
}else if(hint === 'string'){
return 'hi';
}
return null;
}
};
// 左边一个+号就表示执行Number([val])
console.log(+object1); // 10 -- hint 参数值是 "number"
console.log(`${object1}`); // "hi" -- hint 参数值是 "string"
console.log(object1 + ""); // null -- hint 参数值是 "default"
使用Symbol来替代常量
const tabTypes = {
basic: Symbol(),
super: Symbol(),
}
if (type === tabTypes.basic) {
return <div>basic tab</div>
}
if (type === tabTypes.super) {
return <div>super tab</div>
}
5.对象的遍历方法
- for in:遍历对象的key,所有可枚举,包含原型链
- Object.keys():返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。
- Object.values():返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。
- Object.entries():返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。
// 会过滤属性名为 Symbol 值的属性
const obj1 = { [Symbol()]: 123, foo: 'abc' }
for(let k in obj1){
console.log(k); // 'foo'
}
console.log(Object.keys(obj1)); // ['foo']
6.比较两个值是否相等
- == / === / Object.is()
- ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。JavaScript 缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。
- ES6 提出“Same-value equality”(同值相等)算法,用来解决这个问题。Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。不同之处只有两个:一是+0不等于-0,二是NaN等于自身。
7.手写防抖和节流
- 占位
8.commonJS和es6 module的区别
- 占位
9.进程和线程
- 进程:计算机已经运行的程序
- 线程:操作系统能够运行运算调度的最小单位
- 说人话:运行一个程序,就默认启动一个进程(可以多进程),每个进程都会启动一个线程来执行程序的代码,这个就叫主线程,所以进程是线程的容器
- 浏览器多数是多进程的,每开启一个页面就开一个新的进程,避免页面卡死造成全部页面无法响应,需要退出浏览器,而每个进程有多个线程,其中一个线程来负责执行js代码
- js是单线程的,setTimeout/ajax/dom监听/ui redering是调用浏览器内置api(web api),等浏览器执行完毕后,会将回调函数放入宏任务事件队列中
10.promise 和 async/await
- async、await是Promise的一个语法糖
- async修饰函数,会根据函数的返回值把它包装成Promise对象,返回出去,这样调用函数之后就可以链式调用了
- 我们可以将await关键字后面执行的代码,看做是包裹在(resolve, reject) => {函数执行}中的代码,这个在源码中叫做excutor函数,是同步函数,立即执行的,目的是将promise实例的resovle方法和reject方法传递给用户,传递的时候用bind显示绑定了this为实例对象
- await的下一条语句,可以看做是then(res => {函数执行})中的代码,也就是成功的回调,如果状态是rejected则执行不到这里,需要catch里面处理(在源码中then的unfufilled方法和unrejected方法为微任务)
- 要会手写Promise.all(),Promise源码的then()这个方法要理解
// 测试async,会将函数的返回值包装成Promise实例后返回
const test = async () => {
// return Promise.resolve('success')
return Promise.reject('fail')
// return 123
}
test().then(value => {
console.log(value);}, // success/123
reason => {
console.log(reason); // fail
})
Promise.myAll = args => {
const result = []
let fullCount = 0
let iteratorIndex = 0
const promise2 = new Promise((resolve, reject) => {
for(let item of args){
iteratorIndex++
// 包装一层,避免item不是promise实例
Promise.resolve(item).then(value => {
result.push(value)
fullCount++
// 对于数组是length,map之类是size,所以得自己定义一个iteratorIndex
if(fullCount === iteratorIndex){
resolve(result)
}
}, reason => {
reject(reason)
})
}
if(iteratorIndex === 0){
resolve(result)
}
})
return promise2
}
11.宏任务和微任务
事件循环中维护着以下两个队列:
- 宏任务队列(macrotask queue):ajax、setTimeout、setInterval、DOM监听、UI Rendering等
- 微任务队列(microtask queue):Promise的then回调(new Promise传入的函数为excutor函数,是在主线程立即执行的,建议看下源码)、 Mutation Observer API、queueMicrotask()等
- 宏任务执行之前,必须保证微任务队列是空的,如果不为空,那么优先执行微任务队列中的任务(回调)
12.事件循环
- 浏览器的事件循环是一个我们编写的JavaScript代码和浏览器API调用(setTimeout/AJAX/监听事件等)的一个桥梁, 桥梁之间他们通过回调函数进行沟通。
- 具体参考coderwhy大神的这篇文章https://juejin.cn/post/6978019623316750349,把今日头条面试题做出来就说明你理解了大部分
13.forEach和map的区别
- forEach无返回值
- map返回一个新的数组
- 当数组的元素的基本类型数据,则在循环中改变item并不会影响源数组,如果元素是引用类型数据,则循环中改变元素,会改变源数组
14.set和map的区别
- set存储值,或者说键值相同,不能重复,没有get方法
- map存储键值对,有get方法
网友评论