some JS Frontend Interview Quesitions
# 1. 变量声明与类型
# 1.1 var let const 区别
- var 是 ES5 语法,let、const 是 ES6 的语法
- var 有变量提升
- var、let 是变量,可修改;const 是常量,不可修改
- let、const 块级作用域;var 函数作用域
【ES6】变量声明 - var-let-const - 区别与联系 - 总结
# 1.2 数据类型
值类型(7 个):Undefined、Null、Number、String、Boolean、Symbol (ES6)、BigInt (ES10)
引用类型:Object:Array、Function
【JS】JavaScript-ES5 数据类型 - 基本数据类型 - 引用数据类型 - 类型之间的转换 - 数据类型的判断
# 1.3 值类型与引用类型的区别
值类型 存在栈内存中,变量拿到的就是它的值
引用类型 存在堆内存中,变量拿到的只是它的一个引用,是它的地址
【JS】JavaScript - 对象 - Object - 内建对象 - 宿主对象 - 自定义对象 - 操作对象 - 基本数据类型与引用数据类型区别
# 1.4 typeof 能判断哪些类型
- undefined、string、number、boolean、symbol、bigint【除了 null 的基本类型】
- function 【函数】
- object (typeof null === ‘object’) 【所有引用类型只能到 object 这里】
# 1.5 判断数据类型的方式
- typeof 【除了 null 的基本类型 + function】
- instanceof 【引用类型】【从子类到父类直到 object】【顺着原型链】
- toString () 【任意类型】
- Array.isArray () 【数组】
# 1.6 ===
与 ==
===
严格的比较是否相等
==
会进行类型转换,再进行比较
以下都是成立的
100 == '100'
0 == ''
0 == false
fase == ''
null == undefined
有一个情况可以用下 ==
if(a == null) {}
// 等价于
if(a === null || a === undefined)()
# 1.7 truly 变量与 falsely 变量
truly 变量: !!a === true
的变量
falsely 变量: !!b === false
的变量
以下是 falsey 变量,除了这六种情况,其余都是 truely 变量
!!0 === false
!!NaN === false
!!'' === false
!!null === false
!!undefined === false
!!false === false
# 1.8 强制类型转换和隐式类型转换
强制: parseInt
、 parseFloat
、 toString
隐式: if
、逻辑运算、 ==
、 +
拼接字符串
一定要看这个,狠详细
【JS】JavaScript-ES5 数据类型 - 基本数据类型 - 引用数据类型 - 类型之间的转换 - 数据类型的判断
# 1.9 语句与表达式
表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
a
a+b
demo(1)
x===y? 'a': 'b'
语句
if(){}
for(){}
# 2. 数组字符串相关
# 2.1 手写深拷贝
function deepClone(obj){
if (typeof obj !== 'object' || obj === null){
return obj
}
let result = Array.isArray(obj) ? []: {}
for (let key in obj) {
if(obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key])
}
}
return result
}
【JS】自定义 JS 工具函数库 - 自定义对象方法 - new-instanceof-mergeObject - 实现数组与对象的深拷贝与浅拷贝 - 封装字符串相关函数
# 2.2 手写深度比较
`// 判断是否是对象或数组
function isObject(obj) {
return typeof obj === object && obj !== null;
}
// 深度比较
function isEqual(obj1, obj2) {
if (!isObject(obj1) || !isObject(obj2)) {
// 值类型,直接判断【一般不会传函数,不考虑函数】
return obj1 === obj2;
}
if (obj1 === obj2) {
return true;
}
// 两个都是对象或数组,而且不相等
// 1. 先判断键的个数是否相等,不相等一定返回false
const obj1Keys = Object.keys(obj1);
const obj2Keys = Objext.keys(obj2);
if (obj1Keys.length !== obj2Keys.length) {
return false;
}
// 2. 以obj1为基准,和obj2依次递归比较
for (let key in obj1) {
// 递归比较
const res = isEqual(obj1[key], obj2[key]);
if (!res) {
return false;
}
}
// 3. 全相等
return true;
}`
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreBlack.png)
* 1
* 2
* 3
* 4
* 5
* 6
* 7
* 8
* 9
* 10
* 11
* 12
* 13
* 14
* 15
* 16
* 17
* 18
* 19
* 20
* 21
* 22
* 23
* 24
* 25
* 26
* 27
* 28
* 29
* 30
* 31
* 32
# 2.3 数组的 API 有哪些是纯函数
纯函数:①不改变原数组 (没有副作用) ②返回一个新数组
concat、map、filter、slice
非纯函数:push、pop、shift、unshift、forEach、some、every、reduce
【JS】你不得不知道的 JavaScript 数组相关知识【全面总结】复习专用
# 2.4 split()
和 join()
的区别
split()
是字符串的方法
join()
是数组的方法
'1-2-3'.split('-') // [1,2,3]
[1,2,3].join('-') // 1-2-3
# 2.5 数组 slice
与 splice
区别
slice 切片
splice 剪接
【JS】JavaScript 数组 - 操作方法 - concat - 数组强制打平 - slice-splice 方法使用
# 2.6 手写字符串 trim
String.prototype.trim = function() {
return this.replace(/^\s+/, '').replace(/\s+$/, '')
}
# 3. 函数相关
# 3.1 函数声明与函数表达式
函数声明式
function fn(a, b) {
return a + b;
}
函数表达式
let fun = function(a, b){
return a + b;
}
# 3.2 什么是 JSON
- JSON 是一种数据格式,本质是一段字符串
- JSON 格式与 JS 对象结构一致,对 JS 语言更友好
window.JSON
是一个全局对象,常用的两个方法JSON.stringify
和JSON.parse
# 3.3 将 URL 参数解析成 JS 对象
传统方法,分析 search
function queryToObj() {
const res = {}
const search = location.search.substr(1)
search.split('&').forEach(paramStr => {
const arr = paramStr.split('=')
const key = arr[0]
const val = arr[1]
res[key] = val
})
return res
}
使用 URLSearchParams
function queryToObj() {
const res = {}
const pList = new URLSearchParams(location.search)
pList.forEach((val, key) => {
res[key] = val
})
return res
}
# 4. 原型与原型链
# 4.1 解释一下原型与原型链
每个函数对象都有显式原型 prototype
每个实例对象都有隐式原型 __proto__
实例对象的 __proto__
指向函数对象的 prototype
(之前博文中的图)
原型链:实例对象在获取对象上的属性和方法时,先在自身找,找不到就去隐式原型上面找
(之前博文中的图)
# 4.2 class 的原型本质
class 是 ES6 语法规范,由 ECMA 委员会发布【构造函数、继承】
ECMA 只规定语法规则,不规定如何实现
下面博文具体介绍了 class 语法,以及具体的原生实现【构造函数、继承】
【ES6】JavaScript 面向对象 - 面向对象与面向过程的对比 - 类 class - 继承 extends - 构造函数 - super
【JS】JavaScript 创建对象 - 工厂模式 - 构造函数模式 - 原型模式 - 原型链 - 组合模式
【JS】JavaScript 继承 - 原型链 - 盗用构造函数 - 组合继承 - 原型式继承 - 寄生式继承 - 寄生式组合继承
# 4.3 new Object () 与 Object.create () 的区别
{}
等同于new Object()
,原型为Object.prototype
Object.create(null)
没有原型Object.create({...})
可以指定原型
# 4.4 用 class 语法写一个简单的 jQuery
`class jQuery {
constructor(selector) {
const result = document.querySelectorAll(selector);
const length = result.length;
for (let i = 0; i < length; i++) {
this[i] = result[i];
}
this.length = length;
this.selector = selector;
}
get(index) {
return this[index];
}
each(fn) {
for (let i = 0; i < this.length; i++) {
const elem = this[i];
fn(elem);
}
}
on(type, fn) {
return this.each((elem) => {
elem.addEventListener(type, fn, false);
});
}
}
// 插件
jQuery.prototype.dialog = function(info){
console.log(info);
}
// 拓展性
class myjQuery extends jQuery{
constructor(selector){
super(selector)
}
// 扩展自己的方法
addClass(className){}
addStyle(data){}
}`
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreBlack.png)
* 1
* 2
* 3
* 4
* 5
* 6
* 7
* 8
* 9
* 10
* 11
* 12
* 13
* 14
* 15
* 16
* 17
* 18
* 19
* 20
* 21
* 22
* 23
* 24
* 25
* 26
* 27
* 28
* 29
* 30
* 31
* 32
* 33
* 34
* 35
* 36
* 37
* 38
* 39
* 40
* 41
* 42
* 43
# 5. 作用域与闭包
这篇博文写的很详细,推荐阅读
【JS】你不知道的 JavaScript 笔记(一)—— 作用域与闭包 - 编译原理 - LHS - RHS - 循环与闭包 - 模块 - 词法作用域 - 动态作用域
【JS】函数定义与调用方式 - 函数 this 指向问题 - call-apply-bind 方法使用与自定义
【JS】你不知道的 JavaScript 笔记(二)- this - 四种绑定规则 - 绑定优先级 - 绑定例外 - 箭头函数
# 5.1 作用域
一个变量合法的使用范围,JS 中采用的是词法作用域(静态作用域)
【变量的查找,取决于在哪里定义,而不是在哪里执行】
全局作用域 - 函数作用域 - 块级作用域
自由变量:一个变量在当前作用域没有定义,但是被使用了。这时就向上级作用域一层一层依次查找,直到找到为止,最后在全局作用域都没找到就报错 ReferenceError:xxx is not defiend
# 5.2 this 不同场景下如何取值
this 查找采用的是动态作用域
【this 的指向,取决于在哪里执行,而不是在哪里定义】
例题
const User = {
count: 1,
getCount: function() {
return this.count
}
}
console.log(User.getCount()) // 1
const func = User.getCount
console.log( func() ) // undefined
【JS】你不知道的 JavaScript 笔记(二)- this - 四种绑定规则 - 绑定优先级 - 绑定例外 - 箭头函数
# 5.3 手写 bind
【JS】函数定义与调用方式 - 函数 this 指向问题 - call-apply-bind 方法使用与自定义
Function.prototype.myBind = function() {
// 将参数拆解为数组
const args = Array.prototype.slice.call(arguments)
// 获取this
const t = args.shift()
// fn1.bind(...)中的 fn1
const self = this
return function() {
return self.apply(t, args)
}
}
使用
function fn1(a, b, c) {
console.log('this', this)
console.log(a, b, c)
return 'this is fn1'
}
const fn2 = fn1.myBind({x: 100}, 10, 20, 30)
const result = fn2()
console.log(result)
# 5.4 闭包
当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包
① 函数作为参数被传递 ② 函数作为返回值被返回
【JS】你不知道的 JavaScript 笔记(一)—— 作用域与闭包 - 编译原理 - LHS - RHS - 循环与闭包 - 模块 - 词法作用域 - 动态作用域
# 5.5 闭包的应用场景
隐藏数据,只提供 API
function createCache() {
const data = {} // 闭包中的数据被隐藏,不被外界访问
return {
set: function(key, value) {
data[key] = value
},
get: function(key){
return data[key]
}
}
}
const cache = createCache()
c.set('a', 100)
console.log(c.get('a'))
# 6. ES6 新特性
【ES6】变量声明 - var-let-const - 区别与联系 - 总结
【ES6】JavaScript 函数 - 箭头函数 - this 指向 - 简写
【ES6】JavaScript - 变量的解构赋值 - 数组解构 - 对象解构 - 对象的属性 - 对象的方法
【ES6】JavaScript 对象 - 增强的对象语法 - 属性值简写 - 可计算属性 - 简写方法名
【ES6】JavaScript 函数 - 参数的默认值 - 与解构赋值的结合使用 - 对 arguments 的影响 - 默认参数作用域与暂时性死区
【ES6】JavaScript 数组 - 数组的创建 - 构造函数 - 字面量 - Array.from ()-Array.of () 静态方法
【ES6】JavaScript 面向对象 - 面向对象与面向过程的对比 - 类 class - 继承 extends - 构造函数 - super
【Promise】入门 - 同步回调 - 异步回调 - JS 中的异常 error 处理 - Promis 的理解和使用 - 基本使用 - 链式调用 - 七个关键问题
【ES6 模块化】import - export - 按需引入 - 项目中使用 babel - ES6 模块化引入 npm 包
# 7. 异步相关
因为单线程,所以异步【同步会阻塞代码执行】
JS 单线程, 和 DOM 渲染共用一个线程【因为 JS 可以修改 DOM 结构】
浏览器和 node.js 已经支持 JavaScript 启动进程,如 Web Worker
# 7.1 同步与异步的区别
【JavaScript】同步与异步 - 异步与并行 - 异步运行机制 - 为什么要异步编程 - 异步与回调 - 回调地狱 - JavaScript 中的异步操作
# 7.2 前端使用异步的场景
网络请求,如 ajax
定时任务
# 7.3 Promise 的三种状态
- pending 等待中 不会触发
then
和catch
- resolved 成功了 会触发后续的
then
回调函数 - rejected 失败了 会触发后续的
catch
回调函数
then
正常返回 resolved,里面有报错则返回 rejected
catch
正常返回 resolved,里面有报错则返回 rejected
【Promise】入门 - 同步回调 - 异步回调 - JS 中的异常 error 处理 - Promis 的理解和使用 - 基本使用 - 链式调用 - 七个关键问题
# 7.4 promise 的 then 和 catch
Promise.resolve().then(()=>{
console.log(1) // 执行
}).catch(()=>{
console.log(2) // 不执行
}).then(()=>{
console.log(3) // 执行
})
Promise.resolve().then(()=>{
console.log(1) // 执行
throw new Error('err1')
}).catch(()=>{
console.log(2) // 执行
}).then(()=>{
console.log(3) // 执行
})
Promise.resolve().then(()=>{
console.log(1) // 执行
throw new Error('err1')
}).catch(()=>{
console.log(2) // 执行
}).catch(()=>{
console.log(3) // 不执行
})
# 7.5 手写 promise 加载图片
``function loadImg(src){
return new Promise((resolve, reject)=>{
const img = document.createElement('img')
img.onload = () =>{
resolve(img)
}
img.onerror = () => {
reject(new Error(`图片加载失败 ${src}`))
}
img.src = src
})
}
// 使用
const url = ''
loadImg(url).then(img => {
console.log(img.width)
return img
}).then(img => {
console.log(img.height)
}.catch(err => console.error(err))
// 使用加载多张图片
url1 = ''
url2 = ''
loadImg(url1).then(img1 => {
console.log(img1.width)
return img1
}).then(img1 => {
console.log(img1.height)
return loadImg(url2)
}).then(img2 => {
console.log(img2.width)
return img2
}).then(img2 => {
console.log(img2.height)
}).catch(err => console.error(err))``
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreBlack.png)
* 1
* 2
* 3
* 4
* 5
* 6
* 7
* 8
* 9
* 10
* 11
* 12
* 13
* 14
* 15
* 16
* 17
* 18
* 19
* 20
* 21
* 22
* 23
* 24
* 25
* 26
* 27
* 28
* 29
* 30
* 31
* 32
* 33
* 34
* 35
* 36
* 37
# 7.6 async/await 与 Promise
- 执行
async
函数,返回的是 Promise 对象 await
相当于 Promise 的then
try/catch
可以捕获异常,代替了 Promise 的catch
# 7.7 字节面试看代码题
async function async1() { // 函数定义
console.log("async1 start"); // 2
await async2(); // 函数执行
// await后面的内容,可以看作是回调里的内容,即异步执行
console.log("async1 end"); // 6
}
async function async2() { // 函数定义
console.log("async2"); // 3
}
console.log("script start"); // 1
async1(); // 函数执行
new Promise((resolve)=>{
console.log('promise1'); // 4
resolve();
}).then(()=>{
console.log('promise2'); // 7
})
console.log("script end"); // 5 同步代码执行完毕
# 7.8 for-of 的应用场景 【异步】
for、forEach、for-in 是常规的【同步】遍历
for-of 常用于【异步】的遍历
function muti(num) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(num * num);
}, 1000);
});
}
const nums = [1, 2, 3];
nums.forEach(async (i) => {
const res = await muti(i);
console.log(res); // 会同时打印出三个结果
});
(async function () {
for (let i of nums) {
const res = await muti(i);
console.log(res); // 会每隔一秒打印出一个结果
}
})();
# 8. Event Loop
# 8.1 宏任务 macroTask 与微任务 microTask
- 宏任务:setTimeout、setInterval、Ajax、DOM 事件【W3C 规范】
- 微任务:Promise、async/await【ES 规范】
微任务执行时机比宏任务早
- 微任务在 DOM 渲染前触发 【Promise】
- 宏任务在 DOM 渲染后触发 【setTimeout】
# 8.2 Event Loop 机制
- 回调栈 Call Stack
- 事件循环 event loop
- 回调队列 Callback Queue
- 微任务队列 micro task queue
- 同步代码,一行一行放在回调栈中执行,执行完了就出栈
- 遇到异步,记录下来,等待时机;时机到了,就移动到回调队列中
- 当回调栈为空,(微任务 [微任务队列])【尝试 DOM 渲染】 事件循环开始工作:轮询查找(宏任务)回调队列,有则移动到回调栈中执行
- 继续轮询 loop
【同步任务 —— 微任务 ——DOM 渲染 —— 宏任务】—— 同步任务 —— 微任务 ——DOM 渲染 —— 宏任务…
实际上真正的 event loop 是这样的
- 一开始整个脚本作为一个宏任务执行
- 执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列
- 当前宏任务执行完出队,检查微任务列表,有则依次执行,直到全部执行完
- 执行浏览器 UI 线程的渲染工作
- 检查是否有 Web Worker 任务,有则执行
- 执行完本轮的宏任务,回到 2,依此循环,直到宏任务和微任务队列都为空
所以过程是
【宏任务(代码整体)—— 同步任务 —— 微任务 ——DOM 渲染】—— 宏任务 —— 同步任务 —— 微任务 ——DOM 渲染…
# 8.3 event loop 练习
`console.log('1'); // ①同步任务
setTimeout(function() { // ① 宏任务
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() { // ① 微任务
console.log('6');
})
new Promise(function(resolve) {
console.log('7'); // ① 同步任务
resolve();
}).then(function() { // ① 微任务
console.log('8')
})
setTimeout(function() { // ① 宏任务
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
console.log('13');
process.nextTick(function() {
console.log('14'); // 微任务 process.nextTick 比 promse.then优先级要高
})
})`
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreBlack.png)
* 1
* 2
* 3
* 4
* 5
* 6
* 7
* 8
* 9
* 10
* 11
* 12
* 13
* 14
* 15
* 16
* 17
* 18
* 19
* 20
* 21
* 22
* 23
* 24
* 25
* 26
* 27
* 28
* 29
* 30
* 31
* 32
* 33
* 34
* 35
* 36
* 37
* 38
* 39
* 40
* 41
* 42
* 43
* 44
* 45
1 7 6 8 2 4 3 5 9 11 13 10 14 12
# 9. DOM
# 9.1 获取节点操作
document.getElementById('yk') // 元素
document.getElementsByTagName('div') // 集合
document.getElementsByClassName('container') // 集合
document.querySelectorAll('p') // 集合
# 9.2 标签属性 attribute
修改的是标签属性【内联样式】
修改 html 属性,会改变 html 结构
const pList = document.querySelectorAll('p')
const p = pList[0]
p.getAttribute('data-name')
p.setAttribute('data-name','ykjun')
p.getAttribute('style')
p.setAttribute('style', 'font-size: 10px')
# 9.3 对象属性 property
用 JS 的属性操作 DOM 元素
修改对象属性,不会 体现到 html 结构中
const pList = document.querySelectorAll('p')
const p = pList[0]
console.log(p.style.width) // 获取样式
p.style.width = '100px' // 修改样式
console.log(p.className) // 获取class
p.className = 'p1' // 修改class
console.log(p.nodeName) // 获取nodeName节点名称
console.log(p.nodeType) // 获取nodeType节点类型
# 9.4 DOM 结构操作
`const div1 = document.getElementById('div1')
const div2 = document.getElementById('div2')
// 新建节点
const newP = document.createElement('p')
newP.innerHTML = 'this is new p'
// 插入节点
div1.appendChild(newP)
// 移动节点
const p1 = document.getElementsByTagName('p')[0]
div2.appendChild(p1)
// 获取父元素
console.log(p1.parentNode)
// 获取子元素列表
const div1ChildNodes = div1.childNodes
console.log('div1ChildNodes', div1ChildNodes)
const div1ChildNodesP = Array.from(div1ChildNodes).filter(child => {
if(child.nodeType === 1) {
return true;
}
return false;
}
console.log('div1ChildNodesP', div1ChildNodesP)
// 删除节点
div1.removeChild(div1ChildNodesP[0])`
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreBlack.png)
* 1
* 2
* 3
* 4
* 5
* 6
* 7
* 8
* 9
* 10
* 11
* 12
* 13
* 14
* 15
* 16
* 17
* 18
* 19
* 20
* 21
* 22
* 23
* 24
* 25
* 26
* 27
* 28
* 29
* 30
* 31
# 9.5 优化 DOM 性能
# 9.5.1 对 DOM 查询进行缓存
不使用缓存 DOM 查询结果
for(let i = 0; i < document.getELementsByTagName('p').length; i++){
// 每次循环都会计算length,频繁进行DOM查询
}
缓存 DOM 查询结果
const pList = document.getELementsByTagName('p')
const length = pList.length
for(let i = 0; i < length; i++){
// 缓存length, 只进行一次DOM查询
}
# 9.5.2 将频繁操作改为一次性操作
const listNode = document.getElementById('list')
// 创建一个文档片段,此时还没有插入到DOM树中
const frag = document.createDocumentFragment()
// 执行插入操作
for(let i = 0; i < 10; i++){
const li = document.createElement('li')
li.innerHTML = "Iist Item " + i
frag.appendChild(li)
}
// 都完成后,再插入DOM树中
listNode.appendChild(frag)
# 10. BOM
- navigator
- screen
- location
- history
【BOM】JavaScript - 定时器 - 执行机制 - location-navigator-history
# 10.1 检查浏览器类型
const ua = navigator.userAgent
const isChorme = ua.indexOf('Chrome')
console.log(isChorme)
# 10.2 拆解 URL 各个部分
location
# 11. 事件
【DOM】JavaScript - 事件高级 - 注册事件 - 事件流 - 事件对象 - 事件冒泡 - 委派 - 鼠标键盘事件
# 11.1 事件绑定、冒泡、代理
const btn = document.getElementById('btn1')
btn.addEventListener('click', event => {
console.log('clicked')
})
# 11.2 写一个通用的事件绑定函数
function bindEvent(elem, type, fn){
elem.addEventListener(type, fn)
}
const btn1 = document.getELementById('btn1')
bindEvent(btn1, 'click', event => {
console.log(event.target) // 获取触发的元素
event.preventDefault() // 阻止默认行为
alert('clicked')
})
升级版【支持代理】
`function bindEvent(elem, type, selector, fn){
if(fn == null){
fn = selector
selector = null
}
elem.addEventListener(type, event => {
if(selector){
// 代理绑定
if (target.matches(selector)){
fn.call(target, event)
}
}else {
// 普通绑定
fn.call(target, event)
}
})
}
// 普通绑定
const btn1 = document.getELementById('btn1')
bindEvent(btn1, 'click', function(event) {
console.log(event.target) // 获取触发的元素
event.preventDefault() // 阻止默认行为
alert(this.innerHTML)
})
// 代理绑定 [在a父节点div上绑定事件]
const div3 = document.getElementById('div3')
bindEvent(div3, 'click', 'a', function(event) {
event.preventDefault()
alert(this.innerHTML)
})`
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreBlack.png)
* 1
* 2
* 3
* 4
* 5
* 6
* 7
* 8
* 9
* 10
* 11
* 12
* 13
* 14
* 15
* 16
* 17
* 18
* 19
* 20
* 21
* 22
* 23
* 24
* 25
* 26
* 27
* 28
* 29
* 30
* 31
* 32
* 33
# 11.3 描述事件冒泡过程
- 基于 DOM 树形结构
- 事件会顺着触发元素往上冒泡
- 应用场景:事件代理
# 11.4 无限下拉的图片列表,如何监听每个图片的点击
- 事件代理
- 用
event.target
获取触发元素 - 用
matches
来判断是否是触发元素
# 12. AJAX
【Ajax】HTTP 相关问题 - GET-POST-XHR 使用 - jQuery 中的 ajax - 跨域 - 同源 - jsonp-cors
【axios】使用 json-server 搭建 REST API - 使用 axios - 自定义 axios - 取消请求 - 拦截器
# 12.1 手写一个简单的 ajax
function ajax(url) {
const p = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('GET', url, true)
xhr.onreadystatechange = function () {
if(xhr.readyState === 4) {
if(xhr.status === 200) {
resolve(
JSON.parse(xhr.responseText)
)
} else if (xhr.status === 404) {
reject(new Error('404 not found'))
}
}
}
xhr.send(null)
})
return p
}
// 使用
const url = '/data/test.json'
ajax(url)
.then(res => console.log(res))
.catch(err => console.log(err))
# 12.2 跨域解决方案
浏览器中加载图片、css、js 可以无视同源策略
<img />
可用于统计打点,可使用第三方服务<link />
<script>
可以使用 CDN<script>
可以实现 JSONP
所有跨域,都必须经过 server 端允许和配合
未经 server 允许就实现跨域,说明浏览器有漏洞
- JSONP
- CORS
- 代理
【Ajax】HTTP 相关问题 - GET-POST-XHR 使用 - jQuery 中的 ajax - 跨域 - 同源 - jsonp-cors
# 13. 浏览器存储
cookie
- 本身用于浏览器和服务器通讯
- 存储大小最大 4KB
- http 请求时需要发送到服务端,增加请求数据量
- 是能用
document.cookie='...'
来修改,太过简陋
localStorage 和 sessionStorage
- HTML5 专门为存储设计,最大可存 5M
- API 简单易用
setItem
getItem
- 不会随着 HTTP 请求被发出去
# 14. 页面加载
# 14.1 资源的形式
- HTML 代码
- 媒体文件,如图片、视频
- JavaScript 代码 css 代码
# 14.2 从输入 url 到渲染出页面的整个过程
① 获取资源
DNS 域名解析:域名 ——> IP 地址
TCP 三次握手建立连接
浏览器根据 IP 地址向服务器发起 HTTP 请求
服务器处理 HTTP 请求,并资源返回给浏览器
② 渲染页面
浏览器根据 HTML 代码生成 DOM Tree
根据 CSS 代码生成 CSSOM
将 DOM Tree 和 CSSOM 整合成 渲染树 Render Tree
根据 Render Tree 渲染页面
遇到 scrpit 标签 则暂停渲染,优先加载并执行 JS 代码,完成再继续
直至把 Render Tree 渲染完成
# 14.3 window.onload 与 DOMContentLoaded 的区别
window.onload
资源全部加载完成才能执行,包括图片
DOMContentLoaded
DOM 渲染完成即可,图片可能尚未下载
const img1 = document.getElementById('img1')
img1.onload = function() {
console.log('img loaded') // 2
}
window.addEventListener('load', function() {
console.log('window loaded') // 3
})
document.addEventListener('DOMContentLoaded', function() {
console.log('dom content loaded') // 1
})
# 14.4 重绘与回流
重绘:当我们对 DOM 的修改导致了样式的变化、却并未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式(跳过了上图所示的回流环节)。
回流:当我们对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来。这个过程就是回流(也叫重排)
# 15. 性能优化
# 15.1 前端常见性能优化方案
- 思路
- 多使用内存、缓存或其他方法
- 减少 CPU 计算量,减少网络加载耗时
- 空间换时间
- 加载优化
- 减少资源体积:压缩代码
- 减少访问次数:合并代码,SSR 服务端渲染、缓存
- 使用更快的网络:CDN
- 渲染优化
- CSS 放在 head,JS 放在 body 最下面
- 尽早开始执行 JS,用 DOMContentLoaded 触发
- 懒加载
- 对 DOM 查询进行缓存
- 频繁 DOM 操作,合并到一起插入 DOM 结构
- 节流与防抖
# 15.2 缓存
静态资源加 hash 后缀,根据文件内容计算 hash
文件内容不变,则 hash 不变,则 url 不变
url 和文件不变,就会自动触发 HTTP 缓存机制,返回 304
网络方面的缓存分为三块:DNS 缓存、HTTP 缓存、CDN 缓存, HTTP 缓存也称为浏览器缓存
(建议收藏) 为什么第二次打开页面快?五步吃透前端缓存,让页面飞起
# 15.3 SSR 服务渲染
将网页和数据一起加载,一起渲染
非 SSR(前后端分离):先加载网页,再加载数据 (ajax),再渲染数据
# 15.4 图片懒加载
先给一个小的预览图,判断到用户访问到当前位置,再加载高清
<img id="img1" src="preview.png" data-realsrc="real.png" />
<script>
let img1 = document.getElementById('img1')
img1.src = img1.getAttribute('data-realsrc')
<script>
# 15.5 节流与防抖
# 16. 前端安全
# 16.1 XSS 跨站请求攻击
# 16.2 XSRF 跨站请求伪造
# 17. 算法刷题
【算法】经典排序算法总结 - JavaScript 描述 - 图解 - 复杂度分析
【LeetCode】经典题分类(数学 - 数组 - 字符串)精选 - JavaScript - ES6 - 技巧总结
【LeetCode】经典题分类(链表 )精选 - JavaScript - ES6 - 技巧总结
[【LeetCode】经典题分类(树 & 图 )精选 - JavaScript - ES6 - 技巧总结](