文章目录
- 1、闭包,内存泄露
- 2、作用域链,变量提升
- 3、let,var,const
- 4、常用数组(reduce),字符串方法
- 5、map和foreach区别
- 6、js事件循环机制,宏任务微任务,async/await,读代码运行顺序
- 7、原型与原型链
- 8、对象继承方法
- 9、new做了什么操作
- 10、bind,call,apply的区别,手撕源码
- 11、Ajax原理,手撕源码(xmlhttprequest)
- 12、this指向
- 13、设计模式,应用场景,手撕源码
- 14、promise,promise.all,promise.race源码手撕(让你写一个请求,5秒内执行完就返回执行结果,否则返回超时)
- 15、JS垃圾回收机制
- 16、基本数据类型
- 17、== 和 === 的区别
- 18、隐式转换(坑1)var a=?;console.log(a1&&a2&&a3);
- 19、如何判断数组
- 20、js动画和css动画区别
- 21、dom0级时间dom2级事件
- 22、模块化
- 23、防抖节流
- 24、深浅拷贝
- 25、事件流
- 26、浏览器事件冒泡、事件捕获事件冒泡:
- 27、typeof和instance of(判断数据类型的5种方法)
- 28、变量提升
- 29、ES6和common.js
- 30、怎么判断两个对象是否相等
- 31、利用Promise知识,用原生JS封装AJAX
- 32、用setTimeout实现setInterval
- 33、拦截器(Axios)
- 34、箭头函数与普通函数的区别
- 35、JQuery相关面试题
- 36、一般的函数调用和链式调用的区别
- 37、Ajax和Fetch的异同
- 38、undefined和null的区别:
- 39、js中0.1+0.2不等于0.3问题,以及解决方法
- 40、JS中的for of和for in的区别?
- 41、为什么JS是单线程
- 42、使用过的DOM操作有什么
- 43、前端如何捕获错误
- 44、js实现一个轮播图
- 45、JavaScript中显式原型和隐式原型的联系
- 46、实现准确搜索的方法
- 47、类型转换问题
- 48、export和export default有什么区别?在导入上有什么区别
- 49、generator和promise的区别
- 50、事件委托
- 51、Tree - shaking 是什么
- 52、AMD规范与CommonJS规范的兼容性
1、闭包,内存泄露
特性
- 函数嵌套函数
- 函数内部可以引用函数外部的参数和变量
- 参数和变量不会被垃圾回收机制回收
优点
- 保护函数内的变量安全,实现封装,防止变量流入其他环境发生命名冲突
- 在内存中维持一个变量,可以做缓存(但使用多了同时也是一种缺点,消耗内存
- 匿名执行函数可以减少内存消耗
缺点
- 其中一点上面已经有体现了 ,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄露,解决方法是可以在使用完变量后 手动为他赋值为null;
- 其次由于闭包涉及跨域访问,所以会导致性能有所流失,我们可以把跨域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的
2、作用域链,变量提升
在js中,作用域分为全局作用域和函数作用域,ES6新增块级作用域
作用域链
一般情况下,变量取值到创建这个变量的函数的作用域中取值。
但是如果在当前作用域中取不到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。
作用域链这么形成
作用域和执行上下文:许多人误认为作用域和执行上下文的概念,误认为他们是相同的概念,事实并非如此。
我们知道js属于解释性语言,js的执行分为:解释和执行两个阶段 。这两个阶段所做的事情并不一样。
解释阶段
- 词法分析
- 语法分析
- 作用域规则确定
执行阶段
- 创建执行上下文
- 执行函数代码
- 垃圾回收
JavaScript 解释阶段便会确定作用域规则,因此作用域在函数定义时就已经确定了,而不是在函数调用时确定,但是执行上下文是函数执行之前创建的。执行上下文最明显的就是 this 的指向是执行时确定的。而作用域访问的变量是编写代码的结构确定的。
作用域和执行上下文之间最大的区别是: 执行上下文在运行时确定,随时可能改变;作用域在定义时就确定,并且不会改变。
一个作用域下可能包含若干个上下文环境。有可能从来没有过上下文环境(函数从来就没有被调用过);有可能有过,现在函数被调用完毕后,上下文环境被销毁了;有可能同时存在一个或多个(闭包)。同一个作用域下,不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值。
EC:函数的执行环境AO:(Actived Object)活动对象: 保存函数调用时的局部变量 其实AO对象就是函数作用域对象
变量提升
var 声明的变量会提升到当前作用域的最顶端
function 声明的函数也会提升到当前作用域的最顶端
3、let,var,const
- let没有变量提升
- let变量不能重复声明
- ES6可以用let定义块级作用域变量
不同
- var定义的变量,没有块的概念,可以跨块访问,不能跨函数访问。
- let定义的变量,有块的概念,不能跨块访问,也不能跨函数访问。
- const用来定义常量,有块的概念,不能跨块访问,而且不能修改。
什么是暂时性死区
如果区块(花括号)中存在let命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域,凡是在声明之前就使用这些变量,就会报错,所以在代码块内,使用let命令声明变量之前,该变量都是不可用的
这被称为暂时性死区
4、常用数组(reduce),字符串方法
数组
会改变原有数组
会改变原有数组,不会返回新的数组或值:
pop()—删除数组的最后一个元素并返回删除的元素。
push()—向数组的末尾添加一个或更多元素,并返回新的长度。
shift()—删除并返回数组的第一个元素。
unshift()—向数组的开头添加一个或更多元素,并返回新的长度。
reverse()—反转数组的元素顺序。
sort()—对数组的元素进行排序。
splice()—用于插入、删除或替换数组的元素。
不会改变数组,并会返回新的数组
concat()—连接两个或更多的数组,并返回结果。
every()—检测数组元素的每个元素是否都符合条件。
some()—检测数组元素中是否有元素符合指定条件。
filter()—检测数组元素,并返回符合条件所有元素的数组。
indexOf()—搜索数组中的元素,并返回它所在的位置。
join()—把数组的所有元素放入一个字符串。
toString()—把数组转换为字符串,并返回结果。
lastIndexOf()—返回一个指定的字符串值最后出现的位置,在一个字符串中的指定位置从后向前搜索。
map()—通过指定函数处理数组的每个元素,并返回处理后的数组。
slice()—选取数组的的一部分,并返回一个新数组。
valueOf()—返回数组对象的原始值。
其他
reduce 对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。
reducer 函数接收4个参数:
Accumulator (acc) (累计器)
Current Value (cur) (当前值)
Current Index (idx) (当前索引)
Source Array (src) (源数组)
您的 reducer 函数的返回值分配给累计器,该返回值在数组的每个迭代中被记住,并最后成为最终的单个结果值。
字符串
split、slice、substr、indexOf、charAt、trim、toLocalUpperCase、toLocaleLowerCase
5、map和foreach区别
可以通过抛出异常来强制终止迭代,但这种方法会导致代码难以维护:
6、js事件循环机制,宏任务微任务,async/await,读代码运行顺序
js异步任务安装准确的划分,应该将任务分为:
宏任务:setTimeout、setInterval( 按照指定的周期(以毫秒计)来调用函数或计算表达式。)
微任务:列如promise.then方法。注意new Promise()的时候是同步,立即执行。
所以针对这种机制,js的事件循环机制应该是这样的
注意:现在有三个队列:同步队列(也称执行栈)、宏任务队列、微任务队列
- 遇到同步代码,依次推入同步队列并执行
- 当遇到setTimeout、setInterval,会被推到宏任务队列
- 如果遇到.then,会被当做微任务,被推入微任务队列
- 同步队列执行完毕,然后去微任务队列取任务,直到微任务队列清空。然后去检查宏任务队列,去宏队列取任务,并且每一个宏任务执行完毕都会去微任务队列跑一遍,看看有没有新的微任务,有的话先把微任务清空,这样依次循环。
setTimeout(function(){
console.log(1)
},0)
new Promise(function(resolve,reject){
console.log(2)
resolve()
}).then((res)=>{
console.log(3)
})
console.log(4)
//执行结果是 2 4 3 1
async
当我们在函数前使用async的时候,使得该函数返回的是一个Promise对象
async function test(){
return 1 //async的函数会帮我们隐式的使用Promise。resolve(1)
}
//等价于下面的代码
function test(){
return new Promise(function(resolve,reject){
resolve(1)
})
}
可见async只是帮助我们返回一个Promise而已
await
await表示等待,是右侧【表达式】的结果,这个表达式的结果结算可以是Promise对象的值或者一个函数的值。并且只能在带有async的内部使用
使用await时,会从右往左执行,当遇到await时候,会阻塞函数内部处于他后面的代码。去执行该函数外部的同步代码,当外部同步代码执行完毕,再回到该函数内部执行剩余代码,并且当await执行完毕之后,会先处理微任务队列的代码。
async function async1() {
console.log( 'async1 start' )
await async2()
console.log( 'async1 end' )
}
async function async2() {
console.log( 'async2' )
}
console.log( 'script start' )
setTimeout( function () {
console.log( 'setTimeout' )
}, 0 )
async1();
new Promise( function ( resolve ) {
console.log( 'promise1' )
resolve();
} ).then( function () {
console.log( 'promise2' )
} )
console.log( 'script end' )
7、原型与原型链
原型:每一个对象都与另一个对象相关联,那个关联的对象就称为原型
原型链:每一个对象,都有一个原型对象与之关联,这个原型对象它也是一个普通对象,这个普通对象也有自己的原型对象,这样层层递进,就形成了一个链条,这个链条就是原型链。通过原型链可以实现JS的继承,把父类的原型对象赋值给子类的原型,这样子类实例就可以访问父类原型上的方法了
原型链的终点是null
8、对象继承方法
- 原型链继承
function Animal() {
this.colors = ['black', 'white']
}
Animal.prototype.getColor = function() {
return this.colors
}
function Dog() {
}
Dog.prototype = new Animal()
let dog1 = new Dog()
dog1.colors.push('brown')
let dog2 = new Dog()
console.log(dog2.colors) // ['black', 'white', 'brown']
原型链继承存在的问题:
问题1:原型中包含的引用类型属性将被所有实例共享;
问题2:子类在实例化的时候不能给父类构造函数传参;
- 构造继承
function Animal(name) {
this.name = name
this.getName = function() {
return this.name
}
}
function Dog(name) {
Animal.call(this, name)
}
Dog.prototype = new Animal()
借用构造函数实现继承解决了原型链继承的 2 个问题:引用类型共享问题以及传参问题。但是由于方法必须定义在构造函数中,所以会导致每次创建子类实例都会创建一遍方法。
- class实现继承
class Animal {
constructor(name) {
this.name = name
}
getName() {
return this.name
}
}
class Dog extends Animal {
constructor(name, age) {
super(name)
this.age = age
}
}
- 组合继承
组合继承结合了原型链和盗用构造函数,将两者的优点集中了起来。基本的思路是使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。
function Animal(name) {
this.name = name
this.colors = ['black', 'white']
}
Animal.prototype.getName = function() {
return this.name
}
function Dog(name, age) {
Animal.call(this, name)
this.age = age
}
Dog.prototype = new Animal()
Dog.prototype.constructor = Dog
let dog1 = new Dog('奶昔', 2)
dog1.colors.push('brown')
let dog2 = new Dog('哈赤', 1)
console.log(dog2)
// { name: "哈赤", colors: ["black", "white"], age: 1 }
缺点:两次调用父类构造函数,数据冗余
寄生式组合继承
function Animal(name) {
this.name = name
this.colors = ['black', 'white']
}
Animal.prototype.getName = function