JavaScript
1. 数据类型有哪些?ES6新增哪些?
基本数据类型:Number,String,Boolean,Null,Undefined,Symbol(es6)
引用类型:Object,Array,Function
2. 判断类型的方式
- typeof
| 类型 | 输出 | 注释 |
|---|---|---|
| number | number | |
| string | string | |
| boolean | boolean | |
| undefined | undefined | |
| null | object | null会被当成空对象 |
| object | object | |
| array | object | typeof 无法判断 null,object, array |
| function | function |
- instanceof
判断右侧构造函数的prototype是否在左侧实例的原型链上
-
Object.prototype.toString.call()
- 终极版,可判断任何类型;输出 '[object Number/Array...]'
3. 浅拷贝/深拷贝的方法都有哪些?
- 扩展运算符
<!-- 仅对包含基本数据类型的数组有效 --> let newArr = [...oldArr] - Array.prototype.concat
let newArr = oldArr.concat() 当concat不传参时,浅拷贝数组 - 序列化
let newObj = JSON.parse(JSON.stringify(oldObj)) // 无法拷贝Function,Symbol - Object.assign()
let obj = Object.assign({}, oldObj) // 可以拷贝Function,如果是引用类型则拷贝的是地址 - 递归版实现
function deepClone (obj) { let newObj = obj instanceof Array ? [] : {} for(var i in obj){ newObj[i] = typeof obj[i] == 'object' ? deepClone(obj[i]) : obj[i] } return newObj } - 非递归版实现
// todo...
4. 什么是原型,原型链?
- 原型对象也是普通的对象,是对象一个自带隐式的__proto__属性,原型对象也有可能有自己的原型,如果一个原型对象的原型不为null的话,我们就称之为原型链。
- 原型链是由一些用来继承和共享属性的对象组成的(有限的)对象链。
__proto__是内部原型,prototype是构造器(函数)的原型prototype只有构造器(函数)才有,__proto__除null,undefined都存在- 所有构造器(函数)的
__proto__都指向Function.prototype-> ƒ () { [native code] } - 所有的构造器(函数)都来自于Function.prototype,甚至包括根构造器Object及Function自身。所有构造器都继承了Function.prototype的属性及方法。如length、call、apply、bind(ES5)。
// 例子
function Fun(){}
let f = new Fun()
f.__proto__ === Fun.prototype // true 实例对象f的__proto__指向其构造函数的prototype
f.__proto__.__proto__ === Object.prototype // true // f的原型也是原型对象,其也有__proto__并指向构造函数Object.prototype
// 原型对象上包含__proto__和constructor两个属性,constructor指回构造函数本身
Fun.__proto__ === Function.prototype // true
5. 创建对象的方式
- 字面形式
const obj = {
name: "哈哈"
}
- new操作符
const oldObj = {age: 20}
const obj = new Object(oldObj);
obj.name = "哈哈"
obj.age // 20
obj.hasOwnProperty('age') // true
// 无任何继承
- Object.create()
const oldObj = {age: 20}
const obj = Object.create(oldObj)
obj.name = "哈哈"
obj.age // 20
obj.hasOwnProperty('age') // false
obj.__proto__ === oldObj // true
// obj继承自oldObj
- Object.create() 创建一个新对象,第一个参数是对象的原型,不能为空
- 如果传入null,则创建一个没有原型的对象,也不继承Object的属性和方法( ! 不推荐 )
- 如果想创建一个空对象,则传入{}或Object.prototype
- 工厂模式
function createObj(name) {
const obj = new Object();
obj.name = name;
return obj;
}
const obj = ctreateObj("哈哈");
- 工厂模式解决了重复实例化多个对象的问题
- 构造函数模式
function CreateObj(name) {
this.name = name;
}
const obj = new CreateObj("哈哈");
- 原型模式
function CreateObj() {}
CreateObj.prototype.name="哈哈";
CreateObj.prototype.action= function() {
return "吃饭"
};
const obj = new CreateObj();
- 所有实例共享构造函数的属性及方法,原型链上的属性和方法改变,则所有继承实例都会改变
- 构造函数+原型模式
6. 遍历对象的属性
| 方法 | 解释 |
|---|---|
| Object.keys() | 遍历可枚举的属性,不包含继承属性 |
| for...in | 遍历可枚举的属性,包含继承属性 |
| Object.getOwnPropertyNames() | 获取自身所有属性,包含不可枚举属性,返回数组 |
7. 判断对象的属性
| 方法 | 解释 |
|---|---|
| Object.prototype.hasOwnProperty() | 对象自身属性中是否具有指定的属性,返回布尔值 |
| prototypeObj.isPrototypeOf(object) | 检测左侧对象是否在右侧对象的原型链上 |
| Object.getPropertyOf() | 返回指定对象的原型 |
8. 继承有哪几种实现方式
- 原型链继承
function parent(){
this.name = 'parent'
}
parent.prototype.age = '20'
function child(){}
child.prototype = new parent()
let c = new child()
c.name -> 'parent'
c.age -> 20
- 基于原型链,是父类和子类的实例,可继承父类的属性和原型链上的属性与方法,无法实现多继承
- 构造函数继承
function parent(){
this.name = 'parent'
}
parent.prototype.age = '20'
function child(){
parent.call(this)
}
c.name -> 'parent'
c.age -> undefined
- 复制父类的实例给子类,只继承父类实例的属性与方法,不继承原型上的,可实现多继承
- 组合继承(原型链继承+构造函数继承)
function parent(){
this.name = 'parent'
}
parent.prototype.age = '20'
function child(){
parent.call(this)
}
- 原型式继承
- 寄生式继承
- 寄生组合继承
- Object.create()继承
9. ==和===的区别
- == 比较时会先进行类型转换,然后比较转换值; 比如:2 == '2' // true
- === 不会进行类转换,先比较类型,然后比较值,若类型不相同则直接false;比如: 2 === '2' // false
- 如果一个操作为布尔值,则将其转换为数值类型再比较;false转换为0,true转换为1
- 如果一个是字符串,另一个是数值,则将字符串转换为数值类型
- 如果一个是对象,另一个不是,则通过valueOf()获取对象的基本数据类型的值,然后按照上述规则进行转换
- 如果双方都是对象,则对比是否来自同一个引用
- null等于undefined,NaN不等于自身
- 比较时,nulll和undefined不能被转换成其他类型值
10. 闭包
11. Event Loop 微任务/宏任务
12 数组相关
13. 实现快速排序
快速排序采用分治法的思想,将一个复杂问题分为两个或多个子问题,直到子问题简单到可以直接求解,那么子问题的解的组合便是原问题的解
function quickSort (arr) {
if (arr.length <= 1) return arr;
//取中间位置的数据作为基准值,并从原数组中删除该基准值
let jzIndex = Math.floor(arr.length/2) // 获取基准值下标
let jzNum = arr.splice(jzIndex, 1) // 删除并获取基准值
let leftArr = [], rightArr = [] // 分别保存小于和大于基准值的数据
arr.forEach(item => {
if (item < jzNum[0]) {
leftArr.push(item)
}
if (item >= jzNum[0]) {
rightArr.push(item)
}
})
//concat()连接两个数组
return quickSort(leftArr).concat(jzNum, quickSort(rightArr))
}
console.log(quickSort([10,5,15,2,4])) // [2,4,5,10,15]
14. 实现防抖与节流
防抖: 任务频繁触发情况下,只有两次任务间隔超过指定时间,才会执行。若还未超过却又一次触发,则时间重新开始计算
// 防抖函数
function debounce (fn, time) {
// 新建一个变量保存定时器
let timeout = null;
return function () {
// 每次用户点击、输入时,清空上一个定时器
clearTimeout(timeout)
timeout = setTimeout(() => {
fn(...arguments)
}, time)
}
}
// 处理函数
function handler () {
console.log('防抖成功!')
}
// 触发
debounce(handler, 1000)
节流: 频繁触发任务,任务按照一定时间间隔进行执行(稀释触发频率)
// 节流函数
function throttle (fn, time) {
// 利用闭包保存是否可执行的变量
let canRun = true
return function () {
// 如果为false,则终止函数执行
if (!canRun) return;
// 执行前将变量设为false
canRun = false
setTimeout(() => {
fn(...arguments)
canRun = true
}, time)
}
}
// 处理函数
function handler () {
console.log('节流成功!')
}
// 触发
throttle(throttle, 1000)
15. bind(),call(),apply()用法及手写实现
16. ++i 与 i++
以下代码输出什么?
let i = 3;
while(i--) {
setTimeout(() => {
console.log(i)
})
}
// result: -1 -1 -1
解析: 每次遍历,setTimeout都会产生一个宏任务,并push到宏任务队列中; i--是先赋值,后运算;每次遍历校验时,先校验当前未进行运算的i值;因此三次遍历校验的i值分别为:3,2,1,0;当i为0时,遍历结束,但i--计算后i得最终值为-1 结束后执行任务队列中的三个宏任务,得出-1,-1,-1
把i--换成--i,输入结果又是什么呢?
i--为先运算,后赋值;因而循环校验的是运算后的i值;故输出结果为: 0 ,0
- i-- 先赋值,后运算
- --i 先运算,后赋值
17. arguments是什么
- arguments实际是接收实参的一种对象,本身是一个类数组,可通过下标访问,不可使用数组的方法进行遍历
- 存在于函数中,不可被显示创建,只有在函数运行中才可用
arguments转换成数组的方法
1. 常规方法遍历
function transformsArrs() {
var arr = [];
for (var i = 0; i < arguments.length; i++) {
arr.push(arguments[i]);
}
return arr;
}
2. 扩展运算符(arguments是一个对象)
function transformsArrs() {
console.log(arguments instanceof Array); // false
var argsArray = [...arguments ];
console.log(argsArray instanceof Array); // true
}
3. Array.prototype.slice.call();
function transformsArrs() {
console.log(arguments instanceof Array); // false
var argsArray = Array.prototype.slice.call(arguments);
console.log(argsArray instanceof Array); // true
}
4. Array.from() ES6
function transformsArrs() {
console.log(arguments instanceof Array); // false
var argsArray = Array.from(arguments);
console.log(argsArray instanceof Array); // true
}
18. new原理
// 1.创建一个空对象
var obj = {}
// 2.将空对象的原型赋值为构造函数的原型
obj.__proto__ = Perons.prototype
// 3.变更构造函数内部this,将其指向刚刚创建出来的对象
Perons.call(obj)
// 4.返回对象
return obj
19. 什么是AST语法树
ES6
1. Promise解析及代码实现
什么是Promise?
- promise是异步编程的解决方案,比起传统的异步解决方案“回调函数”、“事件”更合理强大,已被ES6纳入规范
- 具有三种状态:pending 过渡态、fulfilled 完成态、rejected 失败态,状态一旦确定不可修改
Promise优缺点?
| 优点 | 缺点 |
|---|---|
| 解决回调地域 | 无法停止,一旦创建Promise立即执行 |
| 可读性高,便于维护 | 必须指定回调,否则内部抛出异常 |
| -- | 当处于pending状态时,无法确定此时进行到哪一阶段(刚开始还是即将完成) |
Promise的方法有哪些?作用都是什么?
-
Promise.prototype.then()
Promise 实例添加状态改变时的回调函数,then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
-
Promise.prototype.catch()
.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
-
Promise.prototype.finally()
无论Promise最后状态如何,都会执行finally内的函数,ES2018引入
-
Promise.all()
多个Promise同时执行,若全部成功,则以数组形式返回执行结果;若有一个是rejected,则只返回rejected的结果
-
Promise.race()
多个Promise同时执行,返回最先结束Promise执行任务的结果,无论成功或失败
-
Promise.resolve()
返回一个新的 Promise 实例,该实例的状态为resolve
-
若参数为Promise实例,则返回该实例
-
参数是一个thenable对象,(thenable为具有then方法的对象),将此对象转换为Promise并立即执行then方法
-
参数不是具有then方法的对象,或根本就不是对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved
-
若不带任何参数,直接返回一个resolved状态的 Promise 对象
-
-
Promise.reject()
返回一个新的 Promise 实例,该实例的状态为rejected
promise如何使用?
- Promise是构造函数,需要new一个Promise的实例
- Promise构造函数的参数为一个函数,函数内的参数为resolved和rejected
- 实例后用then方法分别指定resolved状态和rejected状态的回调函数
- then方法接受两个参数,rejected非必须;then返回Promise对象,则可链式调用
手写一个promise
// 定义Promise构造函数
function Promise (exector) {
let self = this;
this.value = undefined;
this.reason = undefined;
// 定义Promise的状态
this.status = 'pending'
// 存储then中成功的回调函数
this.onResolvedCallbacks = [];
// 存储then中失败的回调函数
this.onRejectedCallbacks = [];
function resolve (value) {
if (self.status === 'pending') {
self.value = value
self.status = 'resolved'
self.onResolvedCallbacks.forEach(fn => fn())
}
}
function reject (reason) {
if (self.status === 'pending') {
self.reason = reason
self.status = 'rejected'
self.onRejectedCallbacks.forEach(fn => fn())
}
}
// 异常处理
try{
exector(resolve, reject)
}catch (e) {
reject(e)
}
}
// 在Promise原型上定义then方法,参数为成功和失败的回调
Promise.prototype.then = function (onFulfilled, onRejected) {
let self = this;
if (this.status === 'resolved') {
onFulfilled(self.value)
}
if (this.status === 'rejected') {
onRejected(self.reason);
}
if (this.status === 'pending') {
this.onResolvedCallbacks.push(() => {
onFulfilled(self.value)
})
this.onRejectedCallbacks.push(() => {
onRejected(self.reason)
})
}
}
let promise = new Promise((resolve, reject)=> {
setTimeout(() => {
resolve('success')
}, 1000)
})
promise.then(data => {
console.log(data)
}, err => {
console.log(err)
})