这是我参与8月更文挑战的第1天,活动详情查看:8月更文挑战
最近在准备秋招面试,关注了不少前辈们和大佬们的公众号,也拜读了掘金里许多大佬的前端方面的文章,对我而言十分有用,我在下面把他们汇总下来,让自己能够再深入学习。
吃水不忘挖井人,再次感谢各位大佬,各位大佬的文章我会在下面一一做汇总
1.JS的数据类型以及存储方式
答:
Js一共有7种原始数据类型
以及1种引用数据类型
//原始数据类型
null
undefined
Boolean
Number
String
Symbol(ES6新增)
BigInt
//引用数据类型
Object
/*
Object包含:
普通对象Object
数组对象Array
正则对象RegExp
日期对象Date
数学函数Math
函数对象Function
*/
存储方式:原始数据类型
存储在栈内存
中,引用数据类型
同时存储在栈内存
与堆内存
中(引用类型指针存在栈中,所存入的指针指向堆内存中的数据的起始地址)
2.JS数据类型的判断
1.使用typeof
判断
不足:原始数据类型null
无法正确判断、引用数据类型除Function
均检测为Object
typeof 1 //'number'
typeof '1' //'string'
typeof undefined //'undefined'
typeof true //'boolean'
typeof Symbol() //'symbol'
typeof [] //'object'
typeof {} //'object'
typeof console.log() //'function'
2.使用instanceof
判断
不足:无法精确判断原始数据类型,只能够判断引用数据类型
原理:instanceof
通过判断对象的原型链
上是否能够查找到被判断类型的prototype
1 instanceof Number //false
true instanceof Boolean //false
'string' instanceof String //false
[] instanceof Array //true
console.log() instanceof Function //true
{} instanceof Object//true
3.使用Object.prototype.toString.call()
判断
Object.prototype.toString.call(1) //Number
Object.prototype.toString.call(true) //Boolean
Object.prototype.toString.call('string') //String
Object.prototype.toString.call(Symbol()) //Symbol
Object.prototype.toString.call(null) //null
Object.prototype.toString.call(undefined) //undefined
Object.prototype.toString.call([]) //Array
Object.prototype.toString.call({}) //Object
Object.prototype.toString.call(console.log()) //Function
3.null与undefined的区别
null
与undefined
均为Javascript的基本数据类型,undefined
表示为该变量应有值但并未定义,而null
则表示不应该有值(阮一峰博客中的部分解答)
4.==与===的区别
===
为严格运算符,==
为相等运算符
1. 严格运算符比较规则
- 不同类型相比较:类型不相同直接返回
false
- 相同类型(原始数据类型):值相等则返回
true
,不想等则返回false
- 相同类型(引用数据类型):指向的是同一个对象则返回
true
,否则返回false
2.相等运算符比较规则
-
原始类型比较:均将数据转换为数值类型后进行比较
-
对象与原始类型比较:对象转换为原始数据类型的值后进行比较
-
相等运算符中存在隐式转换
其中值得一提的是
null
与undefined
之间的比较(也可能出笔试题)
null === undefined //false
null == undefined //true
5.原型链
这里推荐一篇文章,简单易懂:《面不面试的,你都得懂原型和原型链》 ——尼克陈
,顺便贴一张尼克陈大佬上述文章的一张图和一张经典的原型链图。顺带写下我个人的记忆方法(可能有误,我后面再仔细学习):对象的__proto__
均指向上一级对象的.prototype
,直到为null
,对象的.prototype
的构造函数constructor
指向同级对象
6.Js的继承
ECMAScript只支持实现继承
,主要依靠原型链
来实现。Js的继承有原型链继承
、借用构造函数继承
、组合继承
、原型式继承
、寄生式继承
、寄生式组合继承
六种。本文写出两种继承方式:组合继承
以及寄生式组合继承
//定义构造函数
function Parent(age){
this.age = age
}
//设置Parent方法
Parent.prototype.sayAge = function(){
console.log(this.age)
}
//定义Child并使用构造函数继承Parent的age属性
function Child(age){
Parent.call(this,age)
}
//通过原型链继承Parent的sayAge方法
Child.prototype = new Parent()//此处调用了一次构造函数
//创建Child实例
const childInstance = new Child(18)
console.log(childInstance.age)//18
console.log(childInstance instanceof Parent)//true
- 优点:父类方法可复用、构造函数可传参、继承的属性不共享
- 缺点:多出了父类中不必要的属性,造成内存浪费,调用了两次构造函数
- 寄生式组合继承对组合继承进行了优化,弥补了继承父类时调用了父类的构造函数这一缺点,避免了不必要的属性
- 原理:在继承时,并没有继承父类的
实例对象
,从而没有调用父类的构造函数
,而是继承了父类对象原型的实例对象
,避免了继承时产生了不必要的属性 - 方法:在修改原型
//定义构造函数
function Parent(age){
this.age = age
}
//设置Parent方法
Parent.prototype.sayAge = function(){
console.log(this.age)
}
//定义Child并使用构造函数继承Parent的age属性
function Child(age){
Parent.call(this,age)
}
//修改原型链
const prototype = object(Parent.prototype)
prototype.constructor = Child
Child.prototype = prototype
//创建Child实例
const childInstance = new Child(18)
console.log(childInstance.age)//18
console.log(childInstance instanceof Parent)//true
7.事件传播
当某个事件发生在DOM元素上时,该事件并不仅仅在那个元素上发生,而是由window开始一层一层往内递进触发,直到指定的元素。事件传播有以下三个阶段:
- 捕获阶段:事件从
window
开始层层向内递进触发直到指定元素 - 目标阶段:事件已到达目标元素
- 冒泡阶段:事件从目标元素向外层冒泡,逐层递进直到
window
8.addEventListener的useCapture参数
addEventListener
方法第三个可选参数为useCapture
,其值默认为false
,当值为false
时,监听事件将在事件冒泡阶段
触发,值为true
时,监听事件将在捕获阶段
触发
9.事件捕获
当某个事件发生在DOM元素上时,该事件并不仅仅在那个元素上发生,而是由window开始一层一层往内递进触发,直到指定的元素。事件捕获阶段,发生事件有:window
→document
→html
→body
→指定元素
10.事件委托
事件委托
本质上利用了事件冒泡
的机制。事件在冒泡的过程中,会上传到父节点
,同时父节点
也可以通过事件对象定位到目标子节点
,因此可以将子节点
的监听事件定义在父节点
之上,这样就可以由一个父节点
监听父节点下所有子节点
的事件而不需要为每一个子节点绑定监听事件,也称事件代理
。事件代理减少了内存的消耗,还可以实现事件的动态绑定。
11.ES6的新特性
- 块作用域
- 类
- 模板字符串
- 对象解构
- 箭头函数
- 模块
- 加强的字面量对象
- 函数默认参数
- Promise
- Symbol
- set
- proxy
- rest
12.箭头函数特点
- 没有自己的定义上下文
this
永远指向外层的this
而非箭头函数内- 无法作为构造函数(不能使用
new
) - 没有
arguments
13.Set、WeakSet、Map、WeakMap
1.Set
特点:
元素顺序 | 元素重复性 | 可存储类型 | 无序 | 不可重复 | 原始数据类型+引用数据类型 |
---|
Set常用方法:
let set = new Set() //创建Set
set.add(0)
set.add(1)
set.add(2) //添加元素
set.delete(2) //删除元素
set.forEach(item=>{
console.log(item) //1,2
}) //遍历
set.has(1) //检查集合是否包含某元素,true
set.clear() //清空集合
此外,Set
对象中的引用对象都已强类型化,且不能够自动回收无用的引用对象
的,这样会造成内存浪费,且回收代价高,WeakSet
解决了这一个问题
2.WeakSet
特点:
元素顺序 | 元素重复性 | 可存储类型 | 无序 | 不可重复 | 引用数据类型 |
---|
WeakSet
只能够存储引用对象,否则会抛出TypeError
错误WeakSet
不能够包含无引用的对象(null
),否则会将该对象自动进行回收(移出集合)
WeakSet
中存储的对象不可枚举
,即无法获取WeakSet
的大小
和其中的元素
相关代码:
let weakset = new WeakSet()
let foo = {}
weakset.add(a)
console.log(...weakset) //Exception is thrown
console.log(weakset.size) //undefined
weakset.clear() //没有这个方法
//移出对象以及检查对象中元素是否存在的方法
console.log(weakset.has(a)) //true
foo = null //自动回收无引用的对象
console.log(weakset.has(a)) //false
3.Map
特点:
Map
类似于对象,也是键值对的集合,但不同的是,传统对象仅可以Number
、String
、Symbol
类型值作为键名,而Map
可以任何类型值作为键名
使用Map
的优点:
- 相同大小内存,
Map
存储量更大 - 可通过
size
获取Map
的长度
相关方法:
//基础
let map = new Map()
map.set("tony",21)
map.set("steve",22) //键值对添加
console.log(map.get("tony")) //通过key查找value,21
console.log(map.has("tony")) //通过key检查是否包含元素,true
console.log(map.delete("steve")) //通过key删除元素,true
console.log(map.has("steve")) //false
//遍历方法
let m = new Map([
["tony",21],
["steve",22]
])
for(let key of m.keys()){
//keys()获取Map所有key的遍历器
console.log(key)
// "tony"
// "steve"
}
for(let value of m.values()){
//values()获取Map所有value的遍历器
console.log(value)
// 21
// 22
}
//entries()获取Map所有键值对遍历器(两种写法:普通写法+解构写法)
for(let item of m.entries()){
//普通写法
console.log(item)
// ["tony",21]
// ["steve",22]
}
for(let [key,value] of m.entries()){
//解构写法
console.log(key,value)
// "tony" 21
// "steve" 22
}
4.WeakMap
特点:
WeakMap
的特点
- 只接收对象作为
key
(null
除外) key
是弱引用,所指的value
可被回收,回收后key
是无效的,成员随时会消失- 不支持
Map
中获取遍历器的方法
14.Proxy
概念:Proxy
可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
15.this指向
这里借用一张Jake大佬博文《由浅入深,66条JavaScript面试知识点》中的一张图
图片中表示地十分详尽、清晰:
- 在全局作用域中,
this
指向window
- 在函数中,this永远指向最后调用该函数的对象
- 构造函数中,this指向被new出来的实例
- call、apply、bind中,this指向被绑定的对象上
- 箭头函数中的this永远指向父作用域中的this
16.new是怎么实现的
个人理解:new
就是创建了一个空对象,将这一空对象的__proto__
指向了构造函数的prototype
即其原型构成一条原型链,之后借用构造函数继承
对象的属性再赋值给指定的变量
new
使用后,会进行以下几个步骤:
- 创建一个空对象
Object
({}
) - 将该空对象链接到另一个对象(需要
new
的对象)(构造函数的链接) - 将新创建的空对象作为
this
上下文 - 返回
这里贴上一段Jake大佬博文《由浅入深,66条JavaScript面试知识点》中的代码
function Dog(name, color, age) {
this.name = name;
this.color = color;
this.age = age;
}
Dog.prototype={
getName: function() {
return this.name
}
}
var dog = new Dog('大黄', 'yellow', 3)
一部分一部分分析代码如下:
//第一步:创建一个简单空对象
var obj = {}
//第二步:链接该对象到另一个对象(原型链)
obj.__proto__ = Dog.prototype
//第三步:将步骤1新创建的对象作为 `this` 的上下文,this指向obj对象
Dog.apply(obj, ['大黄', 'yellow', 3])
//第四步:如果该函数没有返回对象,则返回this
// 因为 Dog() 没有返回值,所以返回obj
var dog = obj
dog.getName() // '大黄'
//如果 Dog() 有 return 则返回 return的值
var rtnObj = {}
function Dog(name, color, age) {
// ...
//返回一个对象
return rtnObj
}
var dog = new Dog('大黄', 'yellow', 3)
console.log(dog === rtnObj) // true
17.作用域与作用域链
- 作用域:定义变量的区域,它有一套访问变量的规则,这套规则管理着浏览器引擎在当前作用域或嵌套作用域内寻找变量
- 作用域链:保证浏览器引擎在作用域内的有序访问,简单点说,就是内层的作用域找不到的变量,就向外寻找,就像一个链条
- 作用域本质:实际上是一个指向变量的指针列表,变量对象包含了所执行环境中的所有变量以及函数,也就是说这个指针列表里所含的是每一个上下文、由内到外的变量对象,全局作用域的变量对象就是指针列表中的最后一个对象
当查找一个变量时,若在当前执行环境中没有查找到,则会沿着作用域链向后查找
18.var、let、const的区别
var
存在变量提升而let
和const
不存在let
和const
声明形成块作用域,作用域外找不到变量- 同一作用域下
var
能够重复声明变量,而let
和const
不能 const
必须在声明时就进行赋值,若赋值的是基本数据类型
,则不能修改,否则会报错,若复制的是引用数据类型
(对象
),则不能修改const
对象的指针,但可以修改对象中的属性
19.暂时性死区
在一个块作用域中,若一个变量使用let
和const
声明的情况下,则在只会在该块作用域内查找该变量,即便块作用域外有相同的变量,因此有以下代码:
var a = 100;
if(1){
a = 10;
//在当前块作用域中存在a使用let/const声明的情况下,给a赋值10时,只会在当前作用域找变量a,
// 而这时,还未到声明时候,所以控制台Error:a is not defined
let a = 1;
}
20.闭包
简单的一句话:闭包就是拥有权限能够读取其他函数作用域内部变量的函数
常用用途:
- 创建私有变量
- 将运行结束的上下文中变量保存在内存中
示例代码:
//普通用法
function a(){
var b = 0;
function c(){
console.log(b)
}
return c
}
const testc = a()
testc()
//私有变量
function person(name,age){
var name = name
this.age = age
this.getName = function(){
return name
}
}
const tony = new person('tony',21)
console.log(tony.name) //undefined
console.log(tony.age) //21
console.log(tony.getName())//tony
可以认为,闭包
是在一个函数声明创建的时候就生成了的,你可以理解成游戏角色创建以后就有了一个背包,里面装的就是这个函数执行上下文
所有变量的词法作用域
。更详细的内容,可以参考 《medium 五万赞好文-《我永远不懂 JS 闭包》》——掘金安东尼 。
21.JS运行机制
Javascript
是单线程
的,与其用途有关。Javacript
主要用途是与用户进行互动和操作DOM
,因此只能是单线程
。例如,如果Javascript
有两个线程,一个线程进行了DOM
添加操作,另一个线程进行了DOM
删除操作,则会造成矛盾。
22.宏任务与微任务
- 宏任务:
整体script
、setTimeout
、setInterval
、setImmediate
- 微任务:
Promise
、process.nextTick
、MutationObserver
23.事件循环
先说一下个人对事件循环的理解:在程序开始执行时,先进行宏任务
(一般先是script
)的执行,在该宏任务
中,若发现同步任务
,则立即执行
,若发现异步任务
,则进行挂载(宏任务
则挂载到宏任务
的Event Queue
上,微任务
则挂载到微任务
的Event Queue
上),完成这些步骤(宏任务
的完成后)后,就算是进行了一轮事件循环
,进行了这一轮事件循环后,就进入Event Queue
查看是否有挂载的微任务
,有则执行,没有则直接进入下一轮的事件循环,在Event Queue
寻找宏任务继续进行执行代码、挂载宏任务
、微任务
的工作,不断循环往复。
继续捋一下:宏任务→上一个宏任务挂载的微任务→上一个宏任务挂载的宏任务→不断循环
个人浅见,学习得可能不是很深刻,如果有错误,十分感激各位的斧正
下面贴一些代码:
/*
输出结果:
1.script start
2.script end
3.promise1
4.promise2
5.setTimeout
*/
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
24.Promise
- 三种状态:
pending
、fulfilled
、rejected
- 特点:状态一旦从
pending
转变为其他状态,则不可改变 - 作用:解决
回调地狱
问题、以同步操作
方式表达异步操作
- 缺点:无法取消、无法获取进度
故事:Promise相当于一个承诺,这个承诺本身是一个异步的任务(比如两天后我请你吃饭),这就进入了pending
状态,到了两天后,我们都有时间,就一起去吃了顿饭,吃完后,你说下周请我,进入了fulfilled
状态,另外一种情况:领导临时说有新项目,我必须加班,没法请你了,根据这一个状态,我们又有了新的计划,打算下周再聚,进入rejected
状态。
我的理解:在上一个故事中,这个约定就是一个Promise
,我的承诺让这个Promise
进入了pending
状态,在履行承诺的时候,请客和加班分别让这个承诺进入了不同的状态(fulfilled
和rejected
),一旦状态进入了fulfilled
或rejected
,则这个Promise
就定型了(resolve
),无法改变了。根据resolve
,我们就有了新的计划,就相当于使用了回调函数
得到结果并进行了相应操作
再捋一遍:Promise
→pending
→fulfilled or rejected
→返回fulfilled or rejected
的回调函数→进行下一步操作
25.Promise.all与Promise.race
- 应用:合并请求
- 参数:
Promise
对象组成的数组
- 特点:传入数组,多个
Promise
,全部执行成功则成功,返回成功回调函数
,若其中有执行失败则算失败 - 作用:并行执行多个异步操作,且在一个
回调函数
中处理所有结果 - 理解:完成传入的所有
Promise
,全部成功返回成功的回调函数
,若有失败则返回失败的回调函数
- 以执行慢的
Promise
为主,最后一个Promise
结束后才进行回调
let Promise1 = new Promise(function(resolve, reject){})
let Promise2 = new Promise(function(resolve, reject){})
let Promise3 = new Promise(function(resolve, reject){})
let p = Promise.all([Promise1, Promise2, Promise3])
p.then(funciton(){
// 三个都成功则成功
}, function(){
// 只要有失败,则失败
})
- 应用:请求超时处理
- 参数:
Promise
对象组成的数组
- 特点:传入数组,多个
Promise
,若其中某一个Promise
率先改变状态,则Promise.race
返回该Promise
的回调函数
- 作用:解决超时问题
- 以执行快的
Promise
为主,返回该Prmise
的操作回调
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
p
.then(console.log)
.catch(console.error);
Promise.race
执行参数内两个异步操作,当超过5s后,定时器异步操作触发,p
返回reject回调函数
,提出请求超时错误,否则正常请求到数据
26.async与await
- 优点:可读性更高,代码更加简洁
- 特点:一个函数如果在前面加上
async
,则会返回一个Promise
,若在async
函数内写下return
,则该函数直接返回Promise.resolve()
- 注意:
await
只能在async
函数下使用
下面贴出一段代码,分别用Promise
和async/await
实现,来源:《由浅入深,66条JavaScript面试知识点》——JakeZhang
//初始代码
function takeLongTime(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n);
});
}
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(n) {
console.log(`step2 with ${n}`);
return takeLongTime(n);
}
function step3(n) {
console.log(`step3 with ${n}`);
return takeLongTime(n);
}
//Promise实现
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then(time2 => step2(time2))
.then(time3 => step3(time3))
.then(result => {
console.log(`result is ${result}`);
});
}
doIt();
//async/await实现
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time2);
const result = await step3(time3);
console.log(`result is ${result}`);
}
doIt();
27.script标签中的async与defer
常见使用:
<script src='xxx'></script>
<script src='xxx' async></script>
<script src='xxx' defer></script>
- async:
异步请求
脚本,若请求到,则暂停HTML
的解析,执行完获取到的Javascript
代码后才继续解析HTML
- defer:
异步请求
脚本,若请求到,则先等待HTML
解析完成,再执行获取到的Javascript
代码
async
的缺点:获取到脚本的时机是不确定的,获取到脚本时,部分DOM
元素还未被解析,可能无法被脚本获取到。而且若有多个async
,那么脚本的获取也是不可控的
更加详细的可以看:《图解 script 标签中的 async 和 defer 属性》——乔珂力
28.JS预编译
顺序:全局预编译
→函数体预编译
- 函数体内预编译
- 创建一个
AO
对象(activation object
) - 寻找
形参
以及变量声明
,将形参
以及变量声明
作为新建的AO
对象的属性
- 将
形参
与实参
统一 - 寻找
函数声明
,将函数名
作为AO
对象的属性
,这一属性
的值为函数体
- 函数逐行执行(若某行代码存在
变量提升
则忽略,赋值
不忽略)
步骤总结:创建空AO
对象→添加变量声明
、形参
至AO
对象→形参
、实参
统一→函数声明
添加至AO
对象→逐行执行函数
function fn(a) {
console.log(a); //function a() {}
var a = 123; //赋值
console.log(a); //123
function a() {} //变量已提升,忽略
console.log(a); //123
var b = function() {}
console.log(b); //function() {}
function d() {}
var d = a //赋值
console.log(d); //123
}
fn(1)
/*
解析:
1.创建空AO对象
AO:{
}
2.寻找函数内形参和变量声明
AO:{
a:undefined
b:undefined
d:undefined
}
3.形参实参相统一
AO:{
a:1
b:undefined
d:undefined
}
4.寻找函数声明,函数名作为AO属性名,函数体为AO属性值
AO:{
a:function(){}
b:function(){}
d:function(){}
}
5.使用AO对象,根据函数内代码一步步执行(变量提升部分忽略,赋值部分不能忽略)
*/
- 全局预编译
- 创建
GO
对象(global object
) - 找
变量声明
,将变量声明作为GO
对象的属性名,值赋予undefined
- 找
全局里
的函数声明
,将函数名
作为GO
对象的属性名
,函数体
作为该属性
的值
步骤总结:创建空GO
对象→添加变量声明``GO
对象→函数声明
添加至GO
对象→逐行执行函数(先全局
后函数体
)
1:global = 100 //赋值操作,不是声明
2:function fn() {
3: console.log(global); // undefined
4: global = 200
5: console.log(global); // 200
6: var global = 300
7:}
8:fn()
/*
解析:
一、全局预编译
1.创建空GO对象
GO:{}
2.寻找变量声明,而全局内没有变量声明,仅有函数声明及一个global变量赋值,故GO为空
GO:{}
3.寻找函数声明,有fn函数声明,写入GO对象中
GO:{
fn:function fn() {
console.log(global); // undefined
global = 200
console.log(global); // 200
var global = 300
}
}
到此处全局预编译完成,开始函数体内预编译
二、函数体内预编译
1.创建空AO对象
AO:{}
2.寻找变量声明及形参,写入AO对象
AO{
global:undefined
}
3.形参与实参相统一,此处无形参,AO不变
AO{
global:undefined
}
4.寻找函数体内的函数声明,此内无函数声明,AO不变
AO{
global:undefined
}
三、代码执行:
1.第一行,global变量赋值,而GO对象中没有global,因此会声明一个global变量赋值为100并写入GO对象中
2.第八行,执行函数fn,进入函数体内执行
3.第三行,global在AO对象中为undefined,故输出undefined
4.第四行,global赋值为200
5.第五行,输出global为200
6.第六行,赋值global为300(第3、4、5、6步均是在AO对象中的处理)
*/
29.AMD、CMD、Module与CommonJs的区别
AMD
与CMD
的区别:
AMD
推崇依赖前置,提前执行,即define
方法中传入的依赖模块会在一开始就下载并执行(定义模块依赖时就进行声明)CMD
推崇依赖就近,延迟执行,即在require
时依赖模块才执行(按需)- 常见
AMD
规范:RequireJs - 常见
CMD
规范:SeaJs
下面贴出一段代码,来源:《由浅入深,66条JavaScript面试知识点》——JakeZhang
// CMD
define(function(require, exports, module) {
var a = require("./a");
a.doSomething();
// 此处略去 100 行
var b = require("./b"); // 依赖可以就近书写
b.doSomething();
// ...
});
// AMD 默认推荐
define(["./a", "./b"], function(a, b) {
// 依赖必须一开始就写好
a.doSomething();
// 此处略去 100 行
b.doSomething();
// ...
});
CommonJs
和ES6Module
的区别:
与依赖的关系:
CommonJs
模块与依赖关系的建立发生在代码运行阶段(动态)Module
模块与依赖关系的建立发生在代码编译阶段(静态)
本质与特点:
CommonJs
输出的是一个值的拷贝(输出则不再受影响),而ES6中Module
输出的是值的引用(即变量的绑定)CommonJs
仅能导出单个模块,而Module
能导出多个
运行机制:
- ES6中
Module
与CommonJs
不同,Js引擎在对脚本进行静态分析时,若有import
,则生成一个只读引用
,到了脚本真正执行时才根据只读引用
加载模块并取得值(import
仅能写在顶层,而CommonJs
无限制) CommonJs
机制是运行时加载
,CommonJs
模块就是对象,即在加载模块后,生成一个对象,随后从这个对象上读取方法,这种机制就成为运行时加载
30.Js垃圾回收机制
Js垃圾回收两个策略:
- 标记回收
- 引用计数
- 标记回收是浏览器最常见的垃圾回收机制。当变量进入了
执行环境
,则该变量就标记为进入环境
,离开了执行环节
则标记为离开环境
,被标记为离开环境
的变量会被内存释放 - 垃圾收集器(
GC
)在运行时会给存储在内存中的所有变量加上标记。随后,它会去掉正在执行环境
中的变量的标记,随后将有标记的变量进行回收,垃圾收集器完成内存清理工作,销毁带标记的变量并回收它们所占用的内存
- 引用计数就是记录每个值
被引用的次数
,当声明了一个变量并将一个引用类型值
赋值给该变量时,则该值引用次数
加1
。相反,若该变量被赋值为另一个值
,则该值引用次数
减1
,当该值引用次数
为0
时,说明这个值已经没有使用价值
,在回收时,这个值所占的内存
就会被释放
- 存在的问题:可能会造成循环引用。例如:如下代码中,
obj1
与obj2
通过属性相互引用,当执行完成,obj1
、obj2
离开了作用域
,但是obj1
、obj2
引用次数并不为0
,因此不会被自动回收,导致循环引用(每运行一次函数则加1),内存需要手动回收。
//每调用一次,引用次数加1,永远不为0
function fun() {
let obj1 = {};
let obj2 = {};
obj1.a = obj2; // obj1 引用 obj2
obj2.a = obj1; // obj2 引用 obj1
}
//手动回收内存
obj1.a = null
obj2.a = null
当代码比较复杂时,垃圾回收代价就会比较大,因此应尽量减少垃圾回收,方法如下:
- 对
数组
进行优化:手动设置数组长度
为0 - 对
object
进行优化:object
尽量复用,若不再使用,则赋值为null
,使其尽快回收 - 对
函数
进行优化:若循环体内的函数
可复用,则尽量放在循环体外
31.内存泄漏
造成内存泄漏的原因:
- 意外的全局变量:使用了未声明的变量,意外地创建了一个全局变量
- 被遗忘的计时器(忘记取消
setInterval
)或回调函数(对外部变量有引用)无法被回收 - 脱离了DOM的引用:获取一个
DOM
元素的引用,而后面这个元素被删除,但一直保留了对这个元素的引用,导致无法被回收 - 不合理的闭包
32.IIFE(立即调用函数表达式)
用法:函数被创建后立即执行
(function IIFE(){
console.log( "Hello!" );
})();
// "Hello!"
33. for..in 和 for...of 的区别
对对象
遍历:
for...of
获取对象键值,for...in
获取对象键名
对数组
遍历:
for...of
获取数组元素,for...in
获取数组索引
区别:
for...in
循环主要是为了遍历对象
,不适用于遍历数组
;for...of
循环可以用来遍历数组
、类数组对象
,字符串
、Set
、Map
以及Generator
对象。
34.XSS和CSRF
- 原理:攻击者将恶意脚本代码植入到页面中,使用该页面的用户运行了该脚本,攻击者利用该脚本获取用户的
Cookie
等敏感信息,危害数据安全 - 分类:存储型(持久型,恶意代码在数据库中)、反射型(非持久型,恶意代码存在URL中)、DOM型
对XSS的防范:
- httpOnly:在
Cookie
中设置httpOnly
属性,让Javascript
脚本无法获取Cookie
信息 - 输入过滤:检查输入的格式,避免
Javascript
相关脚本代码的输入与上传 - 对输入、输出结果进行必要的转义
这里先贴出一张图,来源:《前端面试查漏补缺--(七) XSS攻击与CSRF攻击》——shotCat
- 原理:攻击者利用危险网站使用被攻击者的
Cookie
伪造成被用户进行危险操作 - 类型:GET类型、POST类型、链接类型
下面贴出一段代码,来源同上:
//GET类型CSRF
<img src="http://bank.example/withdraw?amount=10000&for=hacker" >
//在受害者访问含有这个img的页面后,浏览器会自动向`http://bank.example/withdraw?account=xiaoming&amount=10000&for=hacker`发出一次HTTP请求。bank.example就会收到包含受害者登录信息的一次跨域请求。
//POST类型CSRF
<form action="http://bank.example/withdraw" method=POST>
<input type="hidden" name="account" value="xiaoming" />
<input type="hidden" name="amount" value="10000" />
<input type="hidden" name="for" value="hacker" />
</form>
<script> document.forms[0].submit(); </script>
//访问该页面后,表单会自动提交,相当于模拟用户完成了一次POST操作。POST类型的攻击通常比GET要求更加严格一点,但仍并不复杂。任何个人网站、博客,被黑客上传页面的网站都有可能是发起攻击的来源,后端接口不能将安全寄托在仅允许POST上面。
//链接类型CSRF
<a href="http://test.com/csrf/withdraw.php?amount=1000&for=hacker" taget="_blank">
重磅消息!!
<a/>
//用户点击后触发请求
特点:
- 攻击一般在第三方网站发起
- 攻击者利用用户的登录凭证冒充用户进行操作,而不是直接窃取数据
- 整个过程中攻击者无法获取用户的
Cookie
数据,仅仅是冒用 - 攻击通常是跨域的
对CSRF的防范:
- 使用验证码:防止请求直接执行
- Referer检查:请求来源不符合要求则不执行请求
- 使用token令牌:在请求时验证token是否正确
- XSS是注入代码实现攻击、CSRF是利用用户
Cookie
非法请求Api实现非法操作 - XSS攻击不需要用户登录、CSRF需要用户登录后才能攻击
35.DOM节点的增删改查
//查找
document.getElementById(id)
document.getElementByName(name)
document.getElementByClassName(className)
document.getElementByTagName(tagName)
//创建
document.createElement("h1")
document.createTextNode(String)//创建文本节点
document.createAttribute("class")//创建属性节点
//删除
element.removeChild(node)
36.如何删除一个DOM
看看代码,来源:CSDN
<body>
<div id="main">
<div id="box">111</div>
<div class="box2">222</div>
</div>
<script type="text/javascript">
var box = document.getElementById("box");
var main = document.getElementById("main");
var newMask = document.createElement("div");
newMask.id ="newMask";
main.appendChild(newMask);
if(box){
box.parentNode.removeChild(box); //在这
}
else{
alert("没有这个div");
}
</script>
</body>
37.forEach、map、find与filter
forEach
没有实质性的返回,一般是直接操作原数组,而map
与filter
会分配内存空间存储新的数组并返回,对原数组没有影响,find
对原数组同样没有影响,返回一个回调函数forEach
用法:常用于展示(只能用于遍历数组,不返回值)map
用法:让数组
按照某一个函数
对数组中每个元素
进行相应操作
并重新建立数组
和返回
(必须要有返回值)filter
用法:让数组
按照某一个规则
对数组中每个元素
进行筛选
并重新建立数组
和返回
回调函数:callback
(arg1
、arg2
、arg3
)
- arg1:遍历得到的元素
- arg2:遍历得到的元素索引
- arg3:遍历的数组
代码如下,来源《JavaScript数组中一些实用的方法(forEach,map,filter,find)》——itclanCoder:
//forEach
var obj = {
"data":{
"members": [
{"id":111,"name":"小高"},
{"id":222,"name":"小凡"},
{"id":333,"name":"小王"}
]
}
}
var newArrs= [];
obj.data.members.forEach(function(member,index,originArrs){
newArrs.push(member.name);
})
console.log(newArrs); //["小高", "小凡", "小王"]
//map
var numbersA = [1,2,3,4,5,6];
var numbersB = []
var numbersC = numbersA.map(function(numberA,index,originArrs){
return numbersB.push(numberA*2);
}
console.log(numbersA); // [1,2,3,4,5,6]
console.log(numbersB);// [2, 4, 6, 8, 10, 12]
console.log(numbersC);// [1, 2, 3, 4, 5, 6]
console.log(numbersC==numbersA) // false
//filter
var persons = [
{name:"小王",type:"boy",city:"广西",age:15,height:170},
{name:"小美",type:"girl",city:"北京",age:16,height:180},
{name:"小高",type:"girl",city:"湖南",age:18,height:175},
{name:"小刘",type:"boy",city:"河北",age:20,height:177}
]
var filterPersons = persons.filter(function(person,index,arrs){
return person.type === "boy";
})
console.log(filterPersons) // 会过滤筛选出类型type为boy的整个对象,然后塞到一个新的数组当中去
参考来源(部分已在上文提到,不分先后)
- 《「2021」高频前端面试题汇总之JavaScript篇(上)》——CUGGZ
- 《「2021」高频前端面试题汇总之JavaScript篇(下)》——CUGGZ
- 《javascript篇--1.6万字带你回忆那些遗忘的JS知识点》——别催我码得慢
- 《由浅入深,66条JavaScript面试知识点》—JakeZhang
- 《# 26个精选的JavaScript面试问题》——Fundebug
- 后续可能会继续进行补充
最后:
概念篇我就暂时写到这里,这些题目都是我在这段时间内收集了当前部分公司提前批、秋招的一些前端Js面试题,往后如果遇到了没有写到的题目,我会继续在这里更新,如果各位大佬发现我写的有错误,敬请斧正,万分感激!最后我贴出这段时间里所参考的文章来源,十分感谢在整理过程中各位大佬对我的帮助,我也十分有收获,感谢大家的阅读o(^▽^)o,共勉。
——(完)
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!