Q、 script标签中defer和async的区别
defer: 中文意思是延迟。用途是表示脚本会被延迟到整个页面都解析完毕后再运行。因此,在script元素中设置defer属性,相当于告诉浏览器立即下载,但延迟执行。HTML5规范要求脚本按照它们出现的先后顺序执行,因此第一个延迟脚本会先于第二个延迟脚本执行,但执行脚本之间存在依赖,需要有执行的先后顺序时,就可以使用defer,延迟执行。我觉得把script脚本放在body底部和defer差不多。
defer流程:
1.浏览器开始解析HTML页面
2.遇到有defer属性的script标签,浏览器继续往下面解析页面,且会并行下载script标签的外部js文件
3.解析完HTML页面,再执行刚下载的js脚本(在DOMContentLoaded事件触发前执行,即刚刚解析完html,且可保证执行顺序就是他们在页面上的先后顺序
async: 中文意思是异步,这个属性与defer类似,都用于改变处理脚本的行为。同样与defer类似,async只适用于外部脚本文件,并告诉浏览器立即下载文件。但与defer不同的是,标记为async的脚本并不保证按照它们的先后顺序执行。
指定async属性的目的是不让页面等待两个脚本下载和执行,从而异步加载页面其他内容,这使用于之间互不依赖的各脚本。
async流程:
1.浏览器开始解析页面
2.遇到有sync属性的script标签,会继续往下解析,并且同时另开进程下载脚本
3.脚本下载完毕,浏览器停止解析,开始执行脚本,执行完毕后继续往下解析
注意:如script标签同时存在defer和async属性,则会当做async属性标签运行加载,defer将会无效;
Q、DOM各级的事件的特性
DOM 0级: 通过DOM直接赋值事件方法进行事件绑定,有事件冒泡阶段,但没有事件捕获阶段;
<button type="button" onclick="showFn()"></button>
<script>
// 方法一: 直接写在标签
function showFn() {
alert('Hello World');
}
// 方法二: 获取dom然后赋值事件方法
var btn = document.getElementById('btn');
btn.onclick = function() {
alert('Hello World');
}
// btn.onclick = null; 解绑事件
</script>
DOM 2级: 在DOM 0级基础上,增加了addEventListener、removeEventListener两个方法,支持一个dom元素单个事件能进行多个方法的绑定,还能选择是否在捕获阶段or冒泡阶段触发;
<button id="btn" type="button"></button>
<script>
var btn = document.getElementById('btn');
function showFn() {
alert('Hello World');
}
btn.addEventListener('click', showFn, false);
// btn.removeEventListener('click', showFn, false); 解绑事件
</script>
DOM 3级: 支持了更多的事件类型,如触摸、滑动、滚轮、dom变动,并且能够然开发者自定义事件;
事件流执行顺序: 先捕获,然后处理,然后再冒泡出去。如下图,从左往右
Q、 箭头函数的特性
this指向:函数的this指向函数外部的this,定义的时候就已经确定好;
不能作为构造函数去使用;
没有arguments,如果在箭头函数中获取arguments,则会获取到函数外部的arguments值;
call、apply、bind无法影响箭头函数的this指向;
没有prototype原型;
无法使用yield;
Q、 var、const、let的区别
变量提升:var可以可以在声明前调用,会返回undefined;而使用const、let声明的变量(常量),则会有暂时性死区,在声明前调用会直接报错;
多次声明:var 可以在同一作用域下多次声明,而使用const、let则不可以;
作用域:var不存在块级作用域,而const、let则存在块级作用域概念
Q、 基本类型和复杂类型
基本类型:Boolean Number String Undefined Null Symbol BigInt
复杂类型: object
可变性: 基本类型是不可通过原型方法改变值,只能通过重新赋值的方法改变其值;而复杂类型的值是可改变的,例如对象就可以通过修改对象属性值更改对象;
存储位置: 基本数据类型是存放在栈区的, 复杂类型是同时保存在栈区和堆区中的,栈区保存变量标识符和指向堆内存的地址
Q、 BigInt
js的数值类型遵循着保存成 64 位浮点数,所以只能保存正负9007199254740992的数字,所以ES22020推出一个新的数字类型BigInt用来作大整数的运算;只要在数字后面加小写n则代表这个是BigInt类型数字;
BigInt不能跟Number混合计算
console.log(190071992547409921231231); // Infinity
const bigintNumber = 9007199254740992123123123n;
const sum = bigintNumber + 54545213; // 报错
Q、 Iterator接口平时用过吗,用来实现了什么
Q、 ES5 的继承和 ES6 的继承区别
ES5 的继承时通过 prototype 或构造函数机制来实现。
- ES5 的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到 this 上(Parent.apply(this))。
- ES6 的继承机制完全不同,实质上是先创建父类的实例对象 this(所以必须先调用父类的 super()方法),然后再用子类的构造函数修改
this。
具体: ES6 通过 class 关键字定义类,里面有构造方法,类之间通过 extends 关键字实现继承。子类必须在 constructor 方法中调用 super 方法,否则新建实例报错。因为子类没有自己的 this 对象,而是继承了父类的 this 对象,然后对其进行加工。如果不调用 super 方法,子类得不到 this 对象。
注意:super 关键字指代父类的实例,即父类的 this 对象。在子类构造函数中,调用 super 后,才可使用 this 关键字,否则报错。
Q、 宏任务、微任务以及事件队列
Q、 localstorage、sessionstorage的区别
有效期: localstorage的有效期是永久,sessionstorage关闭浏览器则会删除掉
作用域: localstorage能在不同标签页共享,sessionstorage不可以
Q、 common JS和ES module
common JS:
Nodejs 环境所使用的模块系统就是基于CommonJs规范实现的,由于es6之前并没有一个模块化的正式规范,所以node自己实现了一个模块化的规范,使用module.exports导出,require引入
// a.js
module.exports = { age: 1, a: 'hello', foo:function(){} }
// b.js
const aJs = require(./a.js);
ES module:
ES6规范的模块化方案,使得引入的时候尽量静态化,在编译的时候确定是否使用,直接进行编译分析,使得可以进行tree shaking。还能进一步拓宽 JavaScript 的语法,比如引入宏(macro)和类型检验(type system)这些只能靠静态分析实现的功能。
ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。
// a.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year };
// b.js
import { firstName, lastName, year } from './a.js';
差异:
- CommonJs是单个值导出,ES6 Module可以导出多个
- CommonJs导出的是变量的一份复制,一旦引入,其内部怎么变化都不会改变复制的这一份;ES6 Module导出的是变量的绑定,别的地方改变了值,所有引入的地方就会一起改变
- CommonJs是动态导入语法可以写在判断里,ES6 Module静态语法编译时生成,只能写在顶层
- CommonJs可以更改引入的值,ES6 Module是只读的,重新赋值时会报错
Q、 手写数组去重
var list = [1, 2, 3, 4, 5, 1, 2, 3, 1, 1, 1123, 63, 1123];
// 方法一:es6 set类型
[...new Set(list)]
// 方法二:indexOf判断
function unique1(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
let res = []
for (let i = 0; i < arr.length; i++) {
if (res.indexOf(arr[i]) === -1) {
res.push(arr[i])
}
}
return res
}
// 方法三: Obj赋值
function unique2(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
let res = [],
obj = {}
for (let i = 0; i < arr.length; i++) {
if (!obj[arr[i]]) {
res.push(arr[i])
obj[arr[i]] = 1
} else {
obj[arr[i]]++
}
}
return res
}
Q、 手写深拷贝
主要注意点:遇到引用类型是递归里面的数据进行赋值,直到没有引用类型为止
function deepCopy( target , source ){ //source代表你想要被拷贝的元素 , target代表你想要拷贝的元素
//1.for in 循环拿到 被拷贝元素 里面的属性
for(let key in source){
//2.拿到 被拷贝元素 里面的属性的值
let sourceValue = source[key]
//3.拿到遍历到的属性值的时候,需要判断这个属性值是不是引用类型
if(sourceValue instanceof Object){
//3.1如果是引用类型,做以下处理
let subTarget = new sourceValue.constructor
/**
*这里说一下:引用类型都会有一个constructor属性,数组的constructor会返回Array,对象的constructor会返回Object
*所以new sourceValue.constructor 就省去了判断它是数组还是对象
*/
target[key] = subTarget
//递归
deepCopy(subTarget,sourceValue)
}else{
//3.2如果不是引用类型,直接赋值就完事了
target[key] = sourceValue
}
}
}
Q、 手写柯里化函数
function currying(fn) {
var allArgs = [];
// 运用闭包,将函数存于内存,这样就可以储存参数
return function next() {
var args = [...arguments];
if (args.length > 0) {
// 合并传入参数,储存参数
allArgs = allArgs.concat(args);
return next;
} else {
// 如果没有传入参数》(),则返回结果
return fn.apply(null, allArgs);
}
}
}
var add = currying(function () {
var sum = 0;
for (var i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum;
});
console.log(add(1)(2)(3, 4)(5)()); // 15
Q、 手写call
Function.prototype.myCall = function (context, ...arr) {
if (context === null || context === undefined) {
// 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
context = window
} else {
context = Object(context) // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
}
const specialPrototype = Symbol('特殊属性Symbol') // 用于临时储存函数
context[specialPrototype] = this; // 函数的this指向隐式绑定到context上
let result = context[specialPrototype](...arr); // 通过隐式绑定执行函数并传递参数
delete context[specialPrototype]; // 删除上下文对象的属性
return result; // 返回函数执行结果
};
Q、 一次性插入1000个div,如何优化插入的性能;
使用Fragment
Q、 向1000个并排的div元素中,插入一个平级的div元素,如何优化插入的性能
- 使用Fragment
- 赋予key,然后使用virtual-dom,先render,然后diff,最后patch
- 脱离文档流,用GPU去渲染,开启硬件加速
注意: 实际上浏览器有做优化,使用Fragment并不比innerHTML插入快多少,具体有待深究;