一. js中的垃圾回收机制
- 定义:防止内存泄漏,不定期的寻找不再使用的变量,释放他们的内存
- 回收方式:
- 标记清除:当一个变量再声明的时候,垃圾回收机制将其标记为‘进入状态’,当它离开执行环境(在函数执行之后),垃圾回收机制将其标记为‘离开状态’。然后根据标记的状态类型来决定是否回收。
- 引用计数:当声明一个变量并且将一个引用类型赋值给这个变量的时候,这个变量的引用次数加一,当这个变量指向其他引用类型的时候,这个变量的引用次数减一。当引用次数为零的时候,这个变量就会被回收
二. 为什么说函数是第一类对象
- 函数可以作为参数传递给其他函数。
- 可以作为其他函数的返回值分配到变量当中。
- 可以存在于数据结构之中。
三. js数据类型的判断方法
- typeof:返回具体的数据类型多用于基本数据类型的判断
- A instanceof B (A 一定要是一个 object 类型例如数组和对象类型) 返回布尔值。 其实就是判断 B 的 prototype 是否在 a 的原型链上。
- 原型链:(不能用于undefined和null)
- xxx.constructor 返回一个对应类型的 function,例如:ƒ String(),ƒ Array()
- xxx.constructor===数据类型 返回布尔值,是的话返回 true,否则返回 false
- 通用办法:
- Object.prototype.toString.call(xxx) 返回’[object 对应的数据类型]’ 例如:‘[object String]’
- Object.prototype.toString.call(xxx)===‘[object 对应的数据类型]’ 返回布尔值
- typeof()和 instanceof 的区别
- 返回的结果不同,typeof 返回具体的数据类型,instanceof 返回布尔值
- 结构不同:typeof(xxx) A instanceof B 判断 A 的数据类型是不是 B,前后数据的关联性那个
- 注意:
- typeof arr =>‘object’
- typeof obj =>‘object’
typeof null =>'object'
- typeof undefined =>‘undefined’
typeof NaN =>'number'
typeof isNaN =>'function'
typeof 123 // number
typeof '123' // string
typeof true // boolean
typeof undefined // undefined
const arr = []
const obj = {}
typeof {} // object
typeof [] // object
arr instanceof Array // true
obj instanceof Object //true
// 需要注意的几个:
typeof null // object
typeof NaN // number
typeof isNaN // function
// 原型链方法
const str = '123'
const num = 123
str.constructor // ƒ String()
num.constructor // ƒ Number()
arr.constructor // ƒ Function()
obj.constructor // ƒ Object()
str.constructor === String // true
num.constructor === Number // true
arr.constructor === Function // true
obj.constructor === Object // true
// 通用办法:
Object.prototype.toString.call(str) // [object String]
Object.prototype.toString.call(num) // [object Number]
Object.prototype.toString.call(arr) // [object Array]
Object.prototype.toString.call(obj) // [object Object]
Object.prototype.toString.call(true) // [object Boolean]
Object.prototype.toString.call(null) // [object Null]
Object.prototype.toString.call(undefined) // [object Undefined]
Object.prototype.toString.call(num) === '[object Number]' // true
//... 以此类推
四. 判断是否为数组的方法
- A instanceof Array
- Object.prototype.toString.call(xxx)===‘[object Array]’
- Array.isArray(xxx) 返回布尔值
const arr = []
arr.instanceof Array // true
Object.prototype.toString.call(arr)==='[object Array]' // true
Array.isArray(arr) // true
五. for in 和 for of的区别
- for…in…:一般用来遍历对象,返回键;如果用来遍历数组和字符串则返回索引。
- for…of…:一般用来遍历字符串和数组,返回的是数组和字符串中的每一个值。
六. 字符串和数字之间的转换
- 字符串转数字:
- Number(‘123’): 将一个字符串转换成整型或者浮点型,只能用于十进制,不能出现除了小数点之外的非数字字符否则返回NaN。
- parseInt(‘123’):将一个字符串转换成整型,只能解析整数部分
- parseFloat(‘123.45’):将一个字符串转换成整型,可以解析小数。
- +‘123’:在前面添加“+”,建议用这种方法。
- 数字转字符串:
- String(123)
- number.toString(radix):radix 在 2-36 之间,默认是 10,代表十进制,还可以将一个数组直接展开成字符串
- number.toFixed(a):小数点后精确到 a 位,会四舍五入
- number.toExponential(a):用科学计数法,小数点后精确到 a 位
- number.toPrecision(a):a 表示指定的有效数字位数
// 1.字符串转数字
Number('123') // 123
Number('123.45') //123.45
Number('123a') // NaN
parseInt('123') // 123
parseInt('123.45') // 123
parseFloat('123.45') // 123.45
+ '123.45' // 123.45
// 1. 数字转字符串
const num = 123
const num2 = 123.456
const num3 = 123456.789
String(num) // '123'
String(num2) // '123.456'
num.toString() // '123'
num2.toString() // '123.456'
num2.toFixed(2) // '123.46' 小数点后面保留两个两位 会四舍五入
num3.toExponential(3) // '1.235e+5' 会四舍五入
num2.toPrecision(4) // '123.5' 会四舍五入
七. 事件委托
- 概念:将本来绑定在子元素上面的事件现在绑定到了它的父元素上面,通过在父元素上面触发事件来实现对子元素的监听。
- 原理:事件冒泡
- 优点:减少事件数量,提高程序性能。
- 事件:当用户或者浏览器与页面进行交互的时候,产生可以被 javascript 侦测到的行为称之为事件。
八. 构造函数普通函数
- 构造函数:主要用来创建类,通常和new一起使用。
- 区别:
- 构造函数只能由 new 关键字调用
- 构造函数可以创建实例化对象
- 构造函数是类的标志
九. 自执行函数
- 概念:声明一个匿名函数,可以立即发起调用。
- 应用:创建一个独立的作用域。
- 优点:
- 隔离作用域,避免污染
- 避免由闭包造成引用变量无法释放
- 利用立即执行的特点,返回需要的业务函数或者对象
十. 函数节流
- 概念:当一个函数执行一次之后,只有大于设定的执行周期才会执行下一次,保证在一段的时间之内函数只会被执行一次
- 例子:
function throttle(func,wait){
let timer=null;
return function(value){
if(!timer){
timer=setTimeout(()=>{
fun(value)
timer=null;
},wait)
}
}
}
function callback(value){
console.log(value)
}
throttle(callback,1000)
十一. 函数防抖
- 概念:当一个函数在被频繁调用之后,只执行最后一次或者第一次,其余均不生效。
- 例子:
function debounce(callback,wait){
let timer=null;
return function(value){
if(timer){
clearTimeout(timer)
}
timer=setTimeout(()=>{
callback(value)
},wait)
}
}
function callback(value){
console.log(value)
}![请添加图片描述](https://img-blog.csdnimg.cn/e68333ba07d34832b15fe8fb065996c7.png)
debounce(callback,1000)
ps:关于函数防抖和函数节流后面会单独出一篇详细说明。
十二. 赋值、深拷贝和浅拷贝
- 赋值:
赋值的是该对象在栈中的地址,而不是堆中的数据。
如果原对象的属性值是基础类型,那么就拷贝基础类型,赋值之后两个变量互不影响。如果是引用类型,则拷贝的是指针,赋值之后两个变量相互影响。 - 浅拷贝:
会开辟一个栈内存
。一种引用层面上的拷贝,拷贝的是指针,两者所指向的内存并没有发生变化,改变一个另一个也会随之改变。
- 深拷贝:
会开辟一个新的空间(堆)用于存储新的对象
,通过递归的方式复制所有的属性,是一种完全意义上的拷贝,两者所指向的内存发生了变化,改变一个另一个不会随之改变。
- 实现浅拷贝的方法
- 对象的Object.assign()
- 一层(深拷贝)
- 多层(浅拷贝)
- 数组的concat(),slice()
- 手写循环
- 对象的Object.assign()
// 1.Object.assign()
// 一层
const obj1 = {
name: 'wangjiajia',
age: 18
}
const shallowObj = Object.assign({},obj1)
shallowObj.name = 'wangtongtong'
obj // {name: 'wangjiajia', age: 18},可以看到name的值并没有改变,是深拷贝。
// 多层
const obj2 = {
person: {
name: 'wangjiajia',
age: 18
}
}
const shallowObj = Object.assign({},obj2)
shallowObj.person.name = 'wangtongtong'
obj2 // {person: {name: 'wangtongtong',age: 18}} 可以看到name的值改变了,是浅拷贝。
// 2.数组的concat()方法
const arr1 = [1,2,{
name: 'wangjiajia'
}]
const shallowArr = arr1.concat()
shallowArr[2].name = 'wangtongtong'
arr1[2].name // 'wangtongtong' 可以看到name的值改变了,是浅拷贝
// 3.数组的slice()方法
const arr1 = [1,2,{
name: 'wangjiajia'
}]
const shallowArr = arr1.slice()
shallowArr[2].name = 'wangtongtong'
arr1[2].name // 'wangtongtong' 可以看到name的值改变了,是浅拷贝
// 4.手写循环(一层是深拷贝,多层是浅拷贝)
const obj2 = {
name: 'wangjiajia',
}
const obj3 = {
person:{
name: 'wangjiajia'
}
}
const shallowCopy = (obj)=>{
const targetObj = {}
for(let i in obj){
targetObj[i] = obj[i]
}
return targetObj
}
const deepObj = shallowCopy(obj2)
const shallowObj = shallowCopy(obj3)
deepObj.name = 'wangtongtong'
shallowObj.person.name = 'wangtongtong'
obj2.name // 'wangjiajia' 一层是深拷贝,原来的值不会变化
obj3.person.name // 'wangtongtong' 多层浅拷贝,原来的值会受到影响,改变了
- 实现深拷贝的方法
- JSON.parse(JSON.stringify())
- 手写递归方法
- 原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。
- 函数库lodash中的cloneDeep方法
// 1. JSON.parse(JSON.stringify())方法
const obj = {
person: {
name: 'wangjiajia'
}
}
const deepObj = JSON.parse(JSON.stringify(obj))
deepObj.person.name = 'wangtongtong'
obj.person.name // 'wangjiajia' 深拷贝,没有影响到原来的值
// 2.手写递归
const obj1 = {
name: 'wangjiajia'
}
const obj2 = {
person:{
name: 'wangjiajia'
}
}
// 定义检测数据类型的方法
function checkType(target){
return Object.prototype.toString.call(target).slice(8,-1) // 返回数据类型
}
// 实现深拷贝
function deepCopy(target){
// 判断数据类型,初始化result存放拷贝后的数据
let result,targetType = checkType(target)
if(targetType === 'Object'){
result = {}
}else if(targetType === 'Array'){
result = []
}else {
return target
}
// 遍历target
for(let i in target){
// 判断每一项值得数据类型,如果是Object或Array则继续遍历,否则直接返回
let value = target[i]
if(checkType(value) === 'Object' || checkType(value) === 'Array') {
result[i] = deepCopy(value)
}else{
result[i] = value
}
}
return result
}
const deepObj1 = deepCopy(obj1)
const deepObj2 = deepCopy(obj2)
deepObj1.name = 'wangtongtong'
deepObj2.person.name = 'wangtongtong'
obj1.name // 'wangjiajia'
obj2.person.name // 'wangjiajia'
// 3. lodash函数库
const _ = require('lodash')
const obj2 = _.cloneDeep(obj1)
obj2 === obj1 // false
十三. 怎么样提高前端性能优化
- 尽可能减少http的请求次数,可以将请求的结果存放在一个变量中,便于后续使用。(vue中可能会将结果存进state中)
- 减少外部资源的引用,外部资源可以使用 cdn 托管,开启 gzip 压缩文件
- css 代码放在头部,js 代码放在尾部
- 减少 DOM 操作
- 预加载图片,可以将图片提前存放在 sessionStorage 中,然后根据实际需求从中获取
- 合并精灵图(一张图片上面有很多样式,通过 css 的办法呈现出需要的图片)
- 减少 cookie 头信息(头信息越大,资源传输的越慢)
- 对于资源可以按需加载,异步加载
十四. 异步执行的原理
- 在 js 中分为同步任务和异步任务,同步任务会被分配到主线程中,异步任务会被分配到任务队列中
- 先执行主线程中的同步任务,执行完了之后在执行任务队列里面的异步任务
- 通过循环遍历任务队列的方式
十五.宏任务和微任务
- 宏任务:script,setTimeout,setInterval,setImmediate
- 宏任务队列所处的任务
- 一个宏任务队列里面只能有一个宏任务
- 可以有多个宏任务队列
- 微任务:promise.then,process.nextTick,object.observe
- 微任务队列所处的任务
- 一个微任务队列里面可以有多个微任务
- 执行顺序
- 先执行主线程,遇到 new promise 立即执行
- 执行所有的微任务
- 执行下一个宏任务
关于js任务执行顺序看一个例子:
// 例1:
setTimeout(function () {
console.log("AAA");
}, 0);
console.log('BBB');
new Promise(function (resolve) {
console.log("CCC");
resolve();
}).then(function () {
console.log("DDD");
});
console.log("EEE");
// 执行顺序为:BBB CCC EEE DDD AAA
// 例2:
async function async1() {
console.log("FFF");
async2();
console.log("GGG");
}
async function async2() {
console.log("HHH");
}
setTimeout(function () {
console.log("AAA");
}, 0);
async1();
console.log('BBB');
new Promise(function (resolve) {
console.log("CCC");
resolve();
}).then(function () {
console.log("DDD");
});
console.log("EEE");
// 执行顺序为:FFF HHH GGG BBB CCC EEE DDD AAA
// 注意:async函数内部没有await的时候就是一个纯同步函数
// 例3:
async function async1() {
console.log("FFF");
await async2();
console.log("GGG");
}
async function async2() {
console.log("HHH");
}
setTimeout(function () {
console.log("AAA");
}, 0);
async1();
console.log('BBB');
new Promise(function (resolve) {
console.log("CCC");
resolve();
}).then(function () {
console.log("DDD");
});
console.log("EEE");
// FFF HHH BBB CCC EEE GGG DDD AAA
// 注意:await async2(); 后面的会放进微任务中
十六. 栈、堆、队列
- 队列
一种先进先出的数据结构
- 栈
一种先进后出的数据结构
- 自动分配相对固定大小的内存空间,并且由系统自动释放
- js 的基本数据类型就是由栈存放
- 堆
程序在运行的时候动态的分配一块大小不一内存空间,不会被系统自动释放
- js 中的数组,对象,函数都是由堆存放的
十七. new的作用
- 作用:
开辟一个新的空间来存储构造函数中初始化的数据,并将地址作为返回值返回,
构造函数中的 this 指向全局变量,如果没有返回值就会显示 undefined - 实现步骤:
- new 创建一个新的对象
- new 会让 this 指向这个新的对象
- 执行构造函数里面的代码,目的是给新对象加属性和方法
- 返回这个对象(因此在构造函数中不需要 return)
十八. if 判断类型总结
- 定义了但是没有赋值认为是假
- 定义了赋值为空字符串认为是假,赋值非空字符串认为是真
- 赋值为 true 为真,赋值为 false 为假
- 赋值为 0 是假,赋值非零数字是真
- 赋值 null、undefined 都是假
- 赋值函数
- 不带括号 if(test){} 如果 test 定义了那就为真,没定义会报错
- 带括号 if(test()){} 根据 test 函数的返回值从而判定是真还是假
十九. 为什么 0.1+0.2 不等于 0.3
0.1 和 0.2 再转化为二进制之后都是一个近似值,因此相加之后大约等于 0.30000000000000004(15 个 0)不等于 0.3
二十. js里面怎么比较两个对象是否相同
JSON.stringify(obj) 将对象转译成json字符串在进行比较
,但是可能会存在问题,用这种方法两个对象的键值顺序必须完全相同。Object.keys():
列举出所有的键,先判断数组长度是否相等,若不相等直接返回false,若相等在判断每一个键对应的值是否相等。- ES6方法:Object.entries(obj1).toString() === Object.entries(obj2).toString(),键值的排列顺序也要相同
- lodash 函数库中的 isEqual: isEqual(obj1,obj2) 返回 true or false
const obj1 = {
name: 'wangjiajia',
age: 18
}
const obj2 = {
name: 'wangjiajia',
age: 18
}
const obj3 = {
age: 18,
name: 'wangjiajia',
}
const obj4 = {
name: 'wangtongtong',
age: 18
}
// 方法1:JSON.stringify(obj)
JSON.stringify(obj1) === JSON.stringify(obj2) // true
JSON.stringify(obj1) === JSON.stringify(obj3) // false obj1和obj3实际上一样只不过键值顺序不同,但还是返回false。这就是这种方法存在的缺陷。
JSON.stringify(obj1) === JSON.stringify(obj4) // false
// 方法2:Object.keys()
function compare(obj1,obj2){
const obj1Keys = Object.keys(obj1)
const obj2Keys = Object.keys(obj2)
if(obj1Keys.length !== obj2Keys.length){
return false
}else{
for(let i of obj1Keys){
if(obj1[i] != obj2[i]){
return false
}
}
return true
}
}
compare(obj1,obj2) // true
compare(obj1,obj3) // true
compare(obj1,obj4) // false
// 方法3:es6里面的Object.entries(obj1).toString()
Object.entries(obj1) // [['name','wangjiajia'],['age',18]] 这种方法会返回有键和值构成的数组,每个数组中都是两个元素,一个是键,一个是值。
Object.entries(obj1).toString() // 'name,wangjiajia,age,18',将所有的键值组成一个字符串。
Object.entries(obj1).toString() === Object.entries(obj2).toString() // true
Object.entries(obj1).toString() === Object.entries(obj3).toString() // false
Object.entries(obj1).toString() === Object.entries(obj4).toString() // false
// 方法4:lodash函数库里面的isEqual(obj1,obj2)方法
isEqual(obj1,obj2) // 相同返回true,不相同返回false,和键值的排列顺序无关