一、JS的数据类型以及它们的区别
JS的数据类型分为:基础数据类型和引用数据类型。
- 基础数据类型:Number、Boolean、String、Undefined、Null,以及ES6新增的Symbol、BigInt。
- 引用数据类型:Object
其中,Symbol
和BigInt
是ES6新增的数据类型:
- Symbol:代表创建后独一无二且不可改变的数据类型,它主要是为了解决可能出现的全局变量冲突的问题。
- BigInt:一种数字类型的数据。它可以表示任何精度格式的整数,使用BigInt可以安全存储和操作大整数,即使这个数已经超出了Number能够表示的安全整数范围。使用BigInt可以避免精度丢失。
二、数据类型检测的方式有哪些
- typeof
js
console.log(typeof 1) // number
console.log(typeof 'a') // string
console.log(typeof true) // boolean
console.log(typeof undefiend) // undefiend
console.log(typeof null) // object
console.log(typeof {}) // object
console.log(typeof []) // object
其中,null、对象、数组的数据类型判断为object,其他判断正确。
typeof null === 'object'
原因:
- JavaScript早期设计失误,null底层二进制存储为000,与object相似,导致
typeof null === 'object'
。
- instanceof
instanceof
可以正确判断对象数据类型,其内部运行机制是判断在其原型链中能否找到该类型的原型。
js
console.log(1 instanceof Number) // false
console.log('a' instanceof String) // false
console.log(true instanceof Boolean) // false
console.log({} instanceof Object) // true
console.log([] instanceof Array) // true
console.log(function() {} instanceof Function) // true
instanceof只能判断引用数据类型,不能判断基本数据类型。
- constructor
js
console.log((1).constructor === Number) // true
console.log(('a').constructor === String) // true
console.log((true).constructor === Boolean) // true
console.log(({}).constructor === Object) // true
console.log(([]).constructor === Array) // true
console.log((function(){}).constructor === Function) // true
constructor有两个作用,一是判断数据的类型,二是对象实例通过constructor
对象访问对象的构造函数。需要注意,如果创建一个对象来改变它的原型,constructor
就不能用来判断数据类型了
- Object.prototype.toString.call()
js
const a = Object.prototype.toString
console.log(a.call(1))
console.log(a.call(true))
console.log(a.call('a'))
console.log(a.call([]))
console.log(a.call(function(){}))
console.log(a.call({}))
console.log(a.call(undefined))
console.log(a.call(null))
Object.prototype.toString.call()可以检测所有数据类型。
同样是检测对象obj调用toString方法,obj.toString()的结果和Object.prototype.toString.call(obj)的结果不一样,这是为什么?
因为toString是Object原型上的方法,向Array、Function(){}等类型作为Object上的实例,都重写了toString方法。不同的对象调用toString方法,调用的都是重写的toString方法,不会去调用Object原型上的方法。
三、判断数组的方法有哪些
- 通过instanceof判断
[] instanceof Array
Object.prototype.toString.call([])
- 通过ES6的
Array.isArray([])
判断 - 通过原型链判断
[].__proto__ === Array.prototype
- 通过
Array.prototype.isPrototypeOf([])
判断
四、请简述js中的this
this是JavaScript中的上下文对象,它指向最后一次调用这个方法的对象。
常见的this使用情况:
- 在普通函数中,this指向全局对象(在浏览器中指向window,在nodejs中指向global)
- 在函数作为对象的方法使用时,this指向该对象
- 箭头函数中的this,箭头函数没有自己的this,它的this永远指向它的作用域父级,且this指向不可变
- 构造函数中的this,指向实例对象
call
、apply
和bind
方法,可以改变this指向
五、箭头函数与普通函数的区别
- 箭头函数更加简洁
js
const fn = () => {} //箭头函数
function fn() {} //普通函数
- 箭头函数没有自己的this,它的this继承于它的上级作用域
- 箭头函数继承来的this指向永远不会被改变
- call、apply、bind不会改变箭头函数的this指向
- 箭头函数不能作为构造函数使用
- 箭头函数没有自己的
arguments
- 箭头函数没有
prototype
- 箭头函数不能作为
Generator
生成器函数,不能使用yeild
关键字
六、js的模块化
js的模块化概念 :JS 模块化是为了解决作用域污染
、依赖混乱
和可维护性差
的问题。随着项目变大,把代码拆成模块
能让我们更好地管理依赖关系
,减少全局变量冲突
,也方便团队协作。
模块化
也为构建工具提供了基础,比如 tree-shaking
、按需加载
等优化手段。
js的模块化分为:AMD、CommonJS、ES Module
AMD概念 :AMD(Asynchronous Module Definition)是一种浏览器端的模块化规范
,由 RequireJS 推广,强调异步加载
模块。
CommonJS概念 :CommonJS 是一种模块化规范,最初是为服务器端(Node.js)设计的
,强调同步加载
模块。
AMD和CommonJS的区别
- AMD是异步加载,适用于浏览器端
- CommonJS是同步加载,适用于nodejs
七、let、const、var区别
- 变量提升:var具有变量提升,let、const不存在变量提升
- 暂时性死区:let、const具有暂时性死区,var不存在暂时性死区
- 块级作用域 :块级作用域是由
{}
包裹,let、const具有块级作用域,var没有块级作用域。 - 给全局添加属性 :浏览器的全局对象是
window
,node的全局对象是global
。var声明的变量为全局变量,并会将该变量添加到全局对象的属性上。但是let、const不会。 - 重复声明:var声明变量时,可以重复声明变量,后面声明的同名变量会覆盖前面的。let、const不允许重复声明。
- 初始值设置:const声明必须赋值,let、var可以不用设置初始值。
- 指针指向:var、let创建的变量可以改变指针指向(重新赋值),const声明的变量不可以改变指针指向。
八、块级作用域解决了ES5中的什么问题
-
解决了变量提升的问题:内层变量可以覆盖外层变量
jsvar x = 10 function fn() { console.log(x) // undefined var x = 20 console.log(x) // 20 } fn() console.log(x) // 10 // fn()函数经过变量提升之后 function fn() { var x console.log(x) x = 20 console.log(x) }
-
解决了变量覆盖问题
jsvar x = 10 if (true) { var x = 20 // 重新赋值 } console.log(x) // 20
-
for循环变量共享的问题
var
没有块级作用域
,i 变成了全局变量。
jsfor(var i = 0; i < 3; i++){ setTimeOut(function() { console.log(i) }, 0) } // 预期结果: 0,1,2 // 实际结果: 3,3,3
let
具有块级作用域
,每次循环都会创建一个新的i,setTimeout
中的i互不影响。
jsfor(let i = 0; i < 3; i++){ setTimeOut(function() { console.log(i) }, 0) } // 实际结果: 0,1,2
-
变量重复声明的问题:var创建的变量可以重复声明,容易引发问题。let、const禁止重复声明变量。
jsvar x = 10 var x = 20 // 重复声明不会报错 console.log(x) // 20
九、new操作符的实现原理
- 新建一个空对象
- 将空对象的原型挂载到构造函数的原型上
- 将空对象的this指向构造函数
- 返回这个对象
js
// 实现
function myNew(constructor, ...args) {
if (typeof constructor !== 'function') return
let obj = {}
obj.__proto__ = constructor.prototype
const result = constructor.apply(obj, args)
return result instanceof Object ? result : obj
}
// 使用实例 - 构造函数没有显式返回值
function Person(name, age) {
this.name = name;
this.age = age;
}
const person1 = new Person('lili', 24)
console.log(person1) // 打印结果 {name: 'lili', age: 24}
const person2 = myNew(Person, 'qiqi', 21)
console.log(person2) // 打印结果 {name: 'qiqi', age: 21}
console.log(person1 instanceof Person); // 打印结果 true
console.log(person2 instanceof Person); // 打印结果 true
// 使用实例 - 构造函数有显式返回值
function Fn(name, age, sex) {
this.name = name
this.age = age
return { sex }
}
const fn1 = new Fn('lili', 24, '女')
console.log(fn1.name) // 打印结果 undefined
console.log(fn1) // 打印结果 {sex: '女'}
console.log(fn1.sex) // 打印结果 '女'
const fn2 = myNew(Fn, 'qiqi', 21, '男')
console.log(fn2) // 打印结果 {sex: '男'}
console.log(fn2 instanceof Fn) // 打印结果 false
十、for in和for of的区别
- for in 循环的是键值
- for of 循环的是键名
十一、原型和原型链
当访问对象上的一个属性时,如果在该对象身上没有找到该属性,通常会去该对象的原型上找,该原型对象又会有自己的原型,这样一层一层的向上寻找,形成了原型链。原型链的尽头一般是Object.prototype
。
十二、事件循环(EventLoop)
事件循环是js执行异步代码的机制,它保证了js是单线程的,也能执行异步任务不阻塞主任务。
- 首先会先执行
同步任务
,再执行异步任务
,进入到事件循环过程中。 - 执行
异步任务
的时候又会区分微任务
和宏任务
。先执行微任务
。 - 后执行
宏任务
,如果执行宏任务
的过程中产生微任务
的话,会把微任务
放到微任务队列
中。 - 执行完
当前宏任务
,立刻执行微任务
,再执行下一轮宏任务
。
- 同步任务:console.log()、变量声明、函数调用、for循环
- 异步任务 :
setTimeout
、fetch
、事件绑定(click
)、Promise、DOM 渲染 - 微任务 :
Promise.then/catch/finally
、Async/Await
MutationObserver
监听 DOM 变动(浏览器原生)queueMicrotask(fn)
手动添加微任务(浏览器和 Node 支持)process.nextTick
Node.js 中的微任务,比 Promise 更早
- 宏任务 :
setTimeout(fn, 0)
定时器setInterval(fn, 1000)
定时重复任务setImmediate(fn)
Node.js 特有,立即执行axios
网络请求
十三、闭包
- 闭包:指在函数内部可以访问到函数外部的变量。
- 在哪里使用过闭包:防抖、节流函数。
十四、Promise
Promise的概念:Promise是ES6新引入的异步编程解决方案。
Promise的状态:
- pending 进行中
- fulfilled 已成功
- rejected 已失败
状态只能从 pending 变为 fulfilled或 rejected,一旦变更不可逆。
Promise的方法:
Promise.all
: 可以放多个promise对象,会等所有promise对象都执行完成后一起返回。如果其中有一个promise对象执行失败,promise.all返回失败结果。Promise.race
: race的意思是"赛跑"。它里面可以放多个promise对象,哪个promise对象先执行完成,先返回哪个promise对象。
十五、异步编程的实现方式
- Promise
- Async/Await
- 回调函数
- generator(生成器)函数
十六、内存泄露
- 意外的全局变量
- 被遗忘的计时器和回调函数
- 不正确的使用闭包
- 脱离DOM的引用
十七、ES6的新特性
1.箭头函数 2. 新的变量声明方式let、const 3. 解构赋值 const [a, b] = [1, 2]
4. promise 5. 模版字符串: 在代码层面可换行
js
const a = 'str'
console.log(`<p>${a}</p>
<div>${a}</div>`)
- 扩展运算符
js
const a = [1, 2, 3]
const b = [5, 6, 7]
const c = [...a, ...b] // [1, 2, 3, 5, 6, 7]
- Symbol数据类型,代表独一无二的值
- 模块化
- for...of 遍历对象的键值
- Map 和 Set复杂数据类型:
- Proxy代理
- 类(Class):面向对象编程的概念
- 默认参数(Default Parameter),在定义函数时可以给参数设置默认值
十八、数组的原生方法
七个可以改变原数组的数组方法 unshift()、shift()、push()、pop()、sort()、reverse()、splice()
数组和字符串的转换
- toString() 默认按逗号分隔
["hello", "world"].toString()
→"hello,world"
- join() 将数组元素用指定分隔符连接成字符串
["a", "b", "c"].join("-")
→"a-b-c"