1. 函数传参是值传递, 还是引用传递?
- 函数调用时, 是将实参变量的数据拷贝一份赋值给形参变量
- 只是实参变量数据可能是基本类型值 ==> 值传递
- 也可能是引用类型的值(也就是地址值) ==> 引用传递/值传递
- 注意下面的代码, 准确的说不是将a内存的地址赋值给b, 而是将a中保存的地址值赋值给b
var a = {} var b = a
2. 说说你对作用域与作用域链的理解
-
作用域
- 一个变量可以合法使用的范围/区域
- 作用域起到了隔离变量, 避免了变量重名冲突的问题(也就是允许了不同作用域中可以有同名的变量)
- 分类:
- 全局作用域
- 函数作用域
- 块作用域 => ES6的let或const变量
-
作用域链
- 多个嵌套的作用域形成的由内向外的结构, 用于查找变量
- 本质: 包含由内向外的多个变量对象的数组 ==> 这个可以不用说
- 当查找一个变量, 在整个作用域链中都找不到时: 会报引用错误(RefrenceError), 错误信息(message)为这个变量没有定义
3. 说说变量提升与函数提升
- 变量提升
- 变量声明提升: 变量的声明部分被提升到了作用域的最前面执行 ==> 预解析
- 结果/现象: 在变量声明语句前, 就可以访问这个变量, 只是值为undefined
- 函数提升
- 函数声明提升: 函数声明语句被提升到了作用域的最前面执行 ==> 预解析
- 结果/现象: 在函数声明语句前, 就可以调用这个函数了
- let/const变量没有变量提升
4. 区分执行函数定义与执行函数
- 执行函数定义: 创建函数对象, 如果指定了函数名, 同时会定义变量并指向这个函数对象
- 执行函数: 执行函数内部的语句
- 必须先执行函数定义, 再执行函数 ===> 注意: 函数定义有可能会提升到最上面执行
5. 说说你对闭包的理解
-
是什么?
- 通过chrome的debugger调试工具得知: 闭包本质是内部函数中的一个容器(非js对象), 这个容器中包含了引用的变量
-
如何产生?
- 嵌套的内部函数引用了外部函数的变量, 当调用外部时就会产生闭包
- 闭包不是在调用内部函数时产生, 而是在创建内部函数对象时产生
-
作用?
- 延长局部变量的生命周期
- 让函数外部能间接操作内部的局部变量
-
区别产生闭包与使用闭包及释放闭包?
- 产生闭包: 内部函数对象创建时产生, 包含那个被引用的变量的容器(不是js对象)
- 使用闭包: 执行内部函数
- 释放闭包: 让内部函数对象成为垃圾对象, 断开指向它的所有引用
-
应用?
- 举删除删除列表中的的某个商品的例子(带确定框)
- IIFE
- 模块编译之后的运行代码
-
写一个简单的闭包程序
function fn1() { var a = 2; var b = 3; function fn2() { a++; console.log(a); } return fn2; } var f = fn1(); f(); f(); f = null;
6. 如何判断函数中的this?
- 常规情况下, 函数中的this取决于执行函数的方式
- fn(): 直接调用 ==> this是? window
- new fn(): new调用 ==> this是? 新建的对象
- obj.fn(): 通过对象调用 ==> this是? obj
- fn.call/apply(obj): 通过函数对象的call/apply来调用 ==> this是? obj
- bind(obj)返回的函数 ==> this是? obj
- 特殊情况:
- 箭头函数 > this是? 外部作用域的this》 沿着作用域链去外部找this
- 回调函数 它不是我们调用的
- 定时器/ajax/promise/数组遍历相关方法回调 ==> this是? window
- DOM事件监听回调 ==> 发生事件的DOM元素
- vue控制的回调函数(生命周期/methods/watch/computed) ==> this是? 组件的实例
- React控制的生命周期回调, 事件监听回调 ==> this是? 组件对象 / undefined
7. 如何改变(指定)函数中的this?
- 情况一: 将任意对象指定为当前函数中的this
- 函数立即调用: call() / apply()
- 函数后面某个时候调用: bind()
- 情况二: 将外部的this指定为当前函数中的this
- ES6之前: 将外部this保存为其它名称变量, 当前函数中不使用this, 而使用这个变量
- ES6之后: 箭头函数
8. 说说原型与原型链
-
原型
- 每个函数都有一个显式原型属性:
prototype
- 每个实例都有一个隐式原型属性:
__proto__
- 实例的
__proto__
与对应函数的prototype
都指向原型对象 - 函数的默认的原型对象是Object的实例, 也就是一个{}
- 原型对象上有一个
constructor
属性指向对应的构造函数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZtCL10Zn-1676984567660)(./images/显示原型与隐式原型.png)]
- 每个函数都有一个显式原型属性:
-
原型链
-
从对象的
__proto__
开始, 连接的所有对象。也可称为“隐式原型链”[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b9bvteIF-1676984567665)(./images/image-20201113231139677.png)]
-
原型链的作用:
- 在查找对象属性或调用对象方法时, 会先在对象自身上查找, 找不到就会沿着原型链查找
- 利用原型链可以实现继承
-
9. instanceof的内部原理和自定义实现
-
作用:
- 判断一个任意类型对象的具体类型
-
instanceof内部如何判断?
- 对于 A instanceof B
- A是实例对象, B是构造函数
- 如果B的prototype属性所指向的原型对象是A实例对象的原型链接上的某个对象, 返回true, 否则返回false
-
原型链详图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jL6aIXWJ-1676984567667)(./images/image-20211113081655048.png)]
-
自定义instanceof功能函数
/* 自定义instanceof工具函数: 语法: myInstanceOf(obj, Type) 功能: 判断obj是否是Type类型的实例 实现: Type的原型对象是否是obj的原型链上的某个对象, 如果是返回true, 否则返回false */ function myInstanceOf(obj, Type) { // 得到原型对象 let protoObj = obj.__proto__ // 只要原型对象存在 while(protoObj) { // 如果原型对象是Type的原型对象, 返回true if (protoObj === Type.prototype) { return true } // 指定原型对象的原型对象 protoObj = protoObj.__proto__ } return false }
10. 编码实现继承
-
基于构造函数的继承
- 原型链 + 借用构造函数的组合式继承
- 让子类的原型为父类的实例: Student.prototype = new Person()
- 让子类型原型的构造器为子类型: Student.prototype.constructor = Student
- 借用父类型构造函数: Person.call(this, name, age)
// 父类型 function Person(name, age) { this.name = name this.age = age } Person.prototype.fn = function () {} Person.prototype.sayHello = function () { console.log(`我叫${this.name}, 年方${this.age}`) } // 子类型 function Student(name, age, price) { // this.name = name // this.age = age // 借用父类型的构造函数 Person.call(this, name, age) // 相当于执行this.Person(name, age) this.price = price } // 让子类的原型为父类的实例 Student.prototype = new Person() // 让原型对象的构造器为子类型 Student.prototype.constructor = Student // 重写方法 Student.prototype.sayHello = function () { console.log(`我叫${this.name}, 年方${this.age}, 身价: ${this.price}`) } const s = new Student('tom', 23, 14000) s.sayHello() s.fn()
- 原型链 + 借用构造函数的组合式继承
-
基于ES6的类的继承
- 子类 extends 父类: class Teacher extends Person2
- 子类构造器中调用父类的构造: super(name, age)
// 父类 class Person2 { constructor (name, age) { this.name = name this.age = age } fn () {} sayHello () { console.log(`我叫${this.name}, 年方${this.age}`) } } // 子类 class Teacher extends Person2 { constructor (name, age, course) { super(name, age) this.course = course } // 重写父类的方法 sayHello () { console.log(`我叫${this.name}, 年方${this.age}, 课程:${this.course}`) } } const t = new Teacher('bb', 34, 'CC') t.sayHello() t.fn()
11. 说说面向对象的三大特征
-
封装:
- 将可复用的代码用一个结构包装起来, 后面可以反复使用
- js的哪些语法体现了封装性: 函数 ==> 对象 ==> 模块 ==> 组件 ==> 库
- 封装都要有个特点: 不需要外部看到的必须隐藏起来, 只向外部暴露想让外部使用的功能或数据
-
继承
- 为什么要有继承? 复用代码, 从而减少编码
- js中的继承都是基于原型的继承: ES6的类本质也是
- 编码实现: 原型链+借用构造函数的组合 / ES6的类继承
-
多态: 多种形态
- 理解
- 声明时指定一个类型对象, 并调用其方法,
- 实际使用时可以指定任意子类型对象, 运行的方法就是当前子类型对象的方法
- JS中有多态:
- 由于JS是弱类型语言, 在声明时都不用指定类型
- 在使用时可以指定任意类型的数据 ==> 这已经就是多态的体现了
- 理解
12. 说说JS的垃圾回收机制
- 在JS中对象的释放(回收)是靠浏览器中的垃圾回收器来回收处理的
- 垃圾回调器
- 浏览器中有个专门的线程, 它每隔很短的时间就会运行一次
- 主要工作:判断一个对象是否是垃圾对象, 如果是, 清除其内存数据,并标记内存是空闲状态
- 如何判断对象是垃圾对象呢?
- 机制1:引用计数法
- 机制2:标记清除法
- 垃圾回收机制1:引用计数法
- 最初级的垃圾收集算法,现在都不用了,把“对象是否不再需要”简化定义为“对象有没有引用指向它“
- 每个对象内部都标记一下引用它的总个数, 如果引用个数为0时,即为垃圾对象
- 有循环引用问题:如果2个对象内部存在相互引用,断开对象的引用后, 它们还不是垃圾对象
- 垃圾回收机制2:标记-清除法
- 当前垃圾回收算法的基础, 它“对象是否不再需要”简化定义为“对象是否可以获得”
- 从根对象(也就是window)开始查找所有引用的对象, 并标记为‘使用中’,没有标记为使用中的对象就是垃圾对象
- 没有循环引用问题
13. 区别一下内存溢出与内存泄漏
-
内存溢出
-
运行程序需要分配的内存超过了系统能给你分配的最大剩余内存
-
抛出内存溢出的错误,程序中断运行
-
演示代码
const arr = [] for (let index = 0; index < 100000000; index++) { arr[index] = new Array(1000) }
-
-
内存泄漏
-
理解: 当程序中的某个内存数据不再需要使用, 而由于某种原因, 没有被释放
-
常见情况:
-
意外的全局变量
function fn () {a = new Array(100000)} fn()
-
没有及时清除的定时器
-
this.intervalId = setInterval(() => {}, 1000)
// clearInterval(this.intervalId)
```-
没有及时解绑的监听
this. b u s . bus. bus.on(‘xxx’, this.handle)
// this. b u s . bus. bus.off(‘xxx’)
```- 没有及时释放的闭包
-
14. 区别一下同步与异步
- 同步:
- 从上往下按顺序依次执行, 只有将这个任务完全执行完后, 才执行后面的
- 会阻塞后面的代码执行
- 同步回调: 只有执行完回调函数后任务的函数才结束
- 异步
- 启动任务后, 立即向下继续执行, 等同步代码执行完后才执行回调函数
- 不会阻塞后面的代码执行
- 异步回调函数即使触发了, 也是要先放入队列中待执行, 只有当同步任务或前面的异步任务执行完才执行
15. 说一下JS的事件循环机制
- JS内部是通过事件循环机制来实现单线程异步执行执行的
- 分析事件循环机制
- 所有任务(同步/异步)都在主线程上执行,形成一个执行栈
- 执行栈之外有用于存储待执行异步回调的任务队列(task queue) ==> 准备的说是2个(宏列队与微队列)
- 浏览器中有在其它分线程执行相关管理模块
- 定时器管理模块
- ajax请求管理模块
- DOM事件管理模块
- JS引擎执行代码的顺序为
- 在执行栈中执行初始化同步代码(也就是script宏任务)
- 执行过程中如果有启动异步任务, 交给对应的管理模块处理, 管理模块会在后面特定时间将回调函数放入任务队列中待执行
- 在执行栈中所有代码都执行完后, 依次取出任务队列中的回调到执行栈中依次执行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0VJ7SplP-1676984567669)(file://D:\work\毕业精讲-code\note\images\event loop(宏任务与微任务)].png?lastModify=1658125218)
-
宏任务与微任务
- script(整体代码)
- setTimeout / setInterval,
- Ajax
- DOM事件监听
- postMessage (H5, 向其它窗口分发异步消息)
- setImmediate(Node.js 环境)
-
宏队列与微队列
- Promise
- async & await
- mutationobserver(H5, 监视DOM元素变化)
-
整体的执行顺序
-
script(整体代码)
-
所有微队列中的微任务
-
宏队列中的第一个宏任务
-
所有微队列中的微任务
后面3-4循环处理
-
16. 说说ES6的promise
- ES6推出的更好的异步编程解决方案(相对于纯回调的方式)
- 解决嵌套回调的回调地狱问题 ==> 通过promise.then的链式调用
- 内部使用的是微任务, 效率更高
- 指定读取结果数据的回调函数时机更灵活 ==> 请求后/请求前/请求完成后都可以
- promise对象有3种状态
- pending
- fulfilled/resolved
- rejected
- promise状态的2种变化
- pending --> fulfilled
- pending --> rejected
- 如何改变promise的状态
- 调用resolve(value)
- 调用reject(reason)
- throw error
17. 说说ES6中Promise的then方法
- 指定成功或失败的回调函数来读取promise成功的value或失败的reason数据
- then()总是返回一个新的promise
- 新promise的结果状态由then指定的回调函数执行的结果决定
- 抛出错误 => 失败且reason就是抛出的错误
- 返回失败的promise => 失败且reason是返回的promise的reason
- 返回成功的promise => 成功且value是返回的promise的value
- 返回其它任何非Promise值 => 成功且value是返回的值
- 返回pending的promise => pending的promise
18. 区别一下Promise的all方法与race方法
- Promise.all([p1, p2, p3])
- 接收包含多个promise的数组, 返回一个新的promise
- 只有当所有接收的promise都成功了, 返回的promise才成功, 且成功的value为所有成功promise的value组成的数组
- 一旦有一个失败了, 返回的promise就失败了, 且失败的reason就是失败promise的reason
- Promise.race([p1, p2, p3])
- 接收包含多个promise的数组, 返回一个新的promise
- 返回的promise的结果由第一个完成的promise决定
19. 说说async与await的理解和使用
- 作用: 简化promise对象的使用, 不用再使用then/catch来指定回调函数
- async与await是异步编程的终极解决方案 => 消灭回调函数
- 使用
- await一般在结果为promise的表达式左侧
- async在await所在函数定义的左侧
- 注意:
- 调用async函数得到是一个promise, 其结果状态由async函数体执行的结果决定
- await的右侧也可以不是promise, 如果不是, 直接返回表达式的值
20. var 、 let 、const之间的区别
- const定义常量, let和var定义变量
- let相对于var
- 有块作用域
- 没有变量提升
- 不会添加到window上
- 不能重复声明
21. 箭头函数的特点
- 编码简洁
- 没有自己的this, 使用外部作用域中的this, 不能通过bind来绑定this
- 不能通过new来创建实例对象
- 内部没有arguments, 可以通过rest参数来代替
22. ES6的模块化语法
- 导出语法: 整个模块总是一个对象, 所有导出语法都是向模块对象中添加属性或方法
- 分别导出: export const a = 2
- 默认导出: export default 3
- 统一导出: export {b, c}
- 导入
- 静态导入
- import {a} from ‘./test’
- import value from ‘./test’ import {default as xxx} from ‘./test’
- import * as test from ‘./test’
- 静态导入的模块打包到一起
- 动态导入
- import(‘./test’).then(module => {})
- 动态导入的模块会被单独打包
- 静态导入
23. 说出几个ES6常用新语法
- const与let
- 解构赋值
- 模板字符串
- 箭头函数
- 形参默认值
- rest/剩余参数
- 对象的属性与方法简写
- 扩展运算符: …
- 类语法: class / extends / constructor / static /super
- 异步语法: promise / async & await
- 模块化语法: export / default / import / import()
- 新容器: Set / Map
24. 说说事件冒泡与事件委托
- 事件冒泡
- 事件在传递给目标元素后, 会由内向外传递给外层的元素处理
- 事件委托
- 不直接给多个子元素绑定多个事件监听, 而是给它们共同的父元素绑定一个监听
- 当操作任意子元素时, 事件会冒泡到父元素上处理
- 在事件回调中通过event.target得到发生事件的目标元素, 并进行相关处理
25. HTTP请求的常用响应状态码
- 2XX: 表示成功处理请求
- 200 成功的通用响应码
- 201 添加成功
- 3XX: 重定向,表明浏览器需要执行某些特殊的处理以正确处理请求
- 302 让浏览器重定向到location响应头所指定的地址
- 304 告诉浏览器资源没有变化, 可以使用缓存资源
- 4XX: 客户端请求错误
- 401 未授权, 也就身份校验失败, 一般是token过期或非法
- 404 请求的资源不存在
- 5XX: 服务器端错误
- 500 服务器运行出错,无法完成请求处理
26. 对原生ajax进行简单的封装
/*
xhr + promise 封装一个异步ajax请求的通用函数 简洁版
ajax ('xxx.json')
*/
function ajax(url) {
return new Promise((resolve, reject) => {
// 创建一个XHR对象
const xhr = new XMLHttpRequest()
// 初始化一个异步请求(还没发请求)
xhr.open('GET', url, true)
// 绑定状态改变的监听
xhr.onreadystatechange = function () {
/*
ajax引擎得到响应数据后
将xhr的readyState属性指定为4
将响应数据保存在response / responseText属性上
调用此回调函数
*/
// 如果状态值不为4, 直接结束(请求还没有结束)
if (xhr.readyState !== 4) {
return
}
// 如果响应码在200~~299之间, 说明请求都是成功的
if (xhr.status>=200 && xhr.status<300) {
// 指定promise成功及结果值
resolve(JSON.parse(xhr.responseText))
} else { // 请求失败了
// 指定promise失败及结果值
reject(new Error('request error staus '+ request.status))
}
}
xhr.send()
})
}
27. 说说你对跨域问题的理解和解决办法
- 同源: 协议, 域名, 端口, 三者都相同
- 同源策略
- ajax请求时, 浏览器要求当前网页和Server必须同源(安全), 否则会抛出跨域的错误
- 加载image/link/script不受同源策略限制
- 解决ajax跨域问题的办法
- JSONP
- CORS
- Proxy
28. 说说你知道的ajax跨域解决方案
- JSONP: 利用script发跨域请求目标接口, 得到响应数据
- CORS: 浏览器直接请求跨域的目标接口, 服务器返回响应头告诉浏览器允许跨域
- Proxy: 浏览器发同源请求代理服务器, 代理服务器转发请求跨域的目标接口
29. 说说你项目中的axios的二次封装
- 配置通用的基础路径和超时
- 每个请求自动携带userTempId的请求头: 在请求拦截器中实现
- 如果当前有token, 自动携带token的请求头
- 显示请求进度条
- 显示进度条: 请求拦截器回调
- 结束进度条: 响应拦截器回调
- 成功返回的数据不再是response, 而直接是响应体数据response.data
- 统一处理请求错误, 具体请求也可以选择处理或不处理
- 对token过期的错误进行处理
30. 说说axios的整体执行流程
-
基本执行顺序
- 请求拦截器的回调函数
- xhr发请求
- 响应拦截器成功/失败的回调
- 具体请求成功/失败的回调
-
内部原理: 通过promise.then的链式调用将这4个任务串连越来, 依次执行并进行数据传递
axios({}) Promise.resolve(config) .then((config) => { // 请求拦截器 // 显示进度/携带token/userTempId return config }) .then((config) => { // 使用xhr发ajax请求 return new Promise((resolve, reject) => { // 根据config创建xhr对象发送异步ajax // 如果请求成功(响应状态码200--299之间) const response = { data: JSON.parse(xhr.responseText), status: xhr.statusCode } resolve(response) // 如果请求失败 reject(new Error('请求失败 code ' + xhr.statusCode)) }) }) .then( // 响应拦截器 response => { return response.data }, error => { throw error } ) .then(result => { // 特定请求的回调 }).catch(error => {
})
## 31. 从输入url到渲染出页面的整个过程
1. 得到服务器对应的IP地址 ==> DNS解析
2. 通过IP连接上服务器: 3次握手
3. 向服务器发请求, 接收服务器返回的响应
4. 解析响应数据(html/css/js)显示页面
解析html => dom树
解析css => cssom树
解析js => 更新dom树/cssom树
生成渲染树 = dom树 + cssom树
布局
渲染
5. 断开连接:4次挥手
## 32. 说说前台数据存储
存储方式:
cookie: 会话/持久化两种
sessionStorage
localStorage
数据库(很少用)
区别:
---------------------sessionStorage VS localStorage
刷新在/ 关闭不在 关闭还在
---------------------cookie VS sessionStorage与localStorage
- 容量 小
- 请求时是否自动携带 会
- API易用性 不好用
- 浏览器是否可禁用 可
## 33. 自定义call和bind
```js
/*
自定义函数对象的call方法
*/
function call (fn, obj, ...args) {
// 如果传入的是null/undefined, this指定为window
if (obj===null || obj===undefined) {
// obj = window
return fn(...args)
}
// 给obj添加一个方法: 属性名任意, 属性值必须当前调用call的函数对象
obj.tempFn = fn
// 通过obj调用这个方法
const result = obj.tempFn(...args)
// 删除新添加的方法
delete obj.tempFn
// 返回函数调用的结果
return result
}
/*
自定义函数对象的bind方法
重要技术:
闭包
call()
三点运算符
*/
function bind (fn, obj, ...args) {
return function (...args2) {
return call(fn, obj, ...args, ...args2)
}
}
34. 区别函数节流与防抖
1. 事件高频触发处理的问题
如果更新界面 => 界面更新卡顿
如果发送ajax请求 => 发送了很多没必要的请求
2. 解决办法
函数节流
函数防抖
3. 区别:
当事件高频发生很多次时, 防抖只执行最后一次, 而节流执行少量几次
4. 应用场景
节流: 窗口调整(resize)/ 页面滚动(scroll)/ OM元素的拖拽功能实现(mousemove)
防抖: 输入搜索联想功能
35. 自定义函数节流与防抖
/*
用于产生节流函数的工具函数
*/
function throttle (callback, delay) {
// 用于保存处理事件的时间, 初始值为0, 保证第一次会执行
let start = 0
// 返回事件监听函数 ==> 每次事件发生都会执行
return function (event) {
console.log('---throttle')
// 发生事件的当前时间
const current = Date.now()
// 与上一次处理事件的时差大于delay的时间
if (current-start>delay) {
// 执行处理事件的函数
callback.call(event.target, event)
// 保证当前时间
start = current
}
}
}
/*
用于产生防抖函数的工具函数
*/
function debounce (callback, delay) {
// 返回事件监听函数 ==> 每次事件发生都会执行
return function (event) {
console.log('---debounce')
// 如果还有未执行的定时器, 清除它
if (callback.timeoutId) {
clearTimeout(callback.timeoutId)
}
// 启动延时delay的定时器, 并保证定时器id
callback.timeoutId = setTimeout(() => {
// 执行处理事件的函数
callback.call(event.target, event)
// 删除保存的定时器id
callback.timeoutId = null
}, delay);
}
}
36. 自定义数组扁平化
/*
数组扁平化: 取出嵌套数组(多维)中的所有元素放到一个新数组(一维)中
如: [1, [3, [2, 4]]] ==> [1, 3, 2, 4]
*/
/*
方法一: 递归 + reduce() + concat() + some()
*/
function flatten1 (array) {
return array.reduce((pre, item) => {
if (Array.isArray(item) && item.some((cItem => Array.isArray(cItem)))) {
return pre.concat(flatten1(item))
} else {
return pre.concat(item)
}
}, [])
}
/*
方法二: ... + some() + concat()
*/
function flatten2 (arr) {
// 只要arr是一个多维数组(有元素是数组)
while (arr.some(item => Array.isArray(item))) {
// 对arr进行降维
arr = [].concat(...arr)
}
return arr
}
37. 自定义new
/*
自定义new工具函数
语法: newInstance(Fn, ...args)
功能: 创建Fn构造函数的实例对象
实现: 创建空对象obj, 调用Fn指定this为obj, 返回obj
*/
function newInstance(Fn, ...args) {
// 创建一个新的对象
const obj = {}
// 执行构造函数
const result = Fn.apply(obj, args) // 相当于: obj.Fn()
// 如果构造函数执行的结果是对象, 返回这个对象
if (result instanceof Object) {
return result
}
// 给obj指定__proto__为Fn的prototype
obj.__proto__ = Fn.prototype
// 如果不是, 返回新创建的对象
return obj
}
38. 实现数组的冒泡排序和sort排序
/*
冒泡排序的方法
*/
function bubbleSort (array) {
// 1.获取数组的长度
var length = array.length;
// 2.反向循环, 因此次数越来越少
for (var i = length - 1; i >= 0; i--) {
// 3.根据i的次数, 比较循环到i位置
for (var j = 0; j < i; j++) {
// 4.如果j位置比j+1位置的数据大, 那么就交换
if (array[j] > array[j + 1]) {
// 交换
// const temp = array[j+1]
// array[j+1] = array[j]
// array[j] = temp
[array[j + 1], array[j]] = [array[j], array[j + 1]];
}
}
}
return arr;
}
const products = [{price: 23, sales: 103}, {price: 22, sales: 101}]
/*
根据销量升序
*/
products.sort((p1, p2) => { // 比较函数 ==> 返回数值 如果大于o, p2放在左边
return p1.sales - p2.sales
})
39. 说说重排(回流)与重绘
- 页面显示过程
- 解析HTML生成DOM树
- 解析CSS生成CSSOM树
- 解析JS更新DOM树和CSSOM树
- DOM树 + CSSOM树生成渲染树
- 布局(也称回流, 确定个节点显示的位置)
- 渲染绘制
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iFP0u97G-1676984567670)(images/image-20220302150041228.png)]
-
更新DOM或Style
- 可能会导致局部重排(也称回流, 重新布局)
- 可能会导致局部重绘
-
注意:
- 重排肯定会重绘, 但重绘不一定有重排
- 重排比重绘开销更大, 更消耗性能
-
哪些操作会导致重排
- 元素位置和尺寸发生改变的时候
- 新增和删除可见元素
- 内容发生改变(文字数量或图片大小等等)
- 元素字体大小变化。
- 激活CSS伪类(例如::hover)。
- 设置style属性
- 查询某些属性。比如说:
offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight
-
哪些操作会导致重绘
- 更新元素的部分属性(影响元素的外观,风格,而不会影响布局),比如
visibility、outline、背景色等属性的改变。
- 更新元素的部分属性(影响元素的外观,风格,而不会影响布局),比如
-
例子代码
var s = document.body.style; s.padding = "2px"; // 回流+重绘 s.border = "1px solid red"; // 再一次 回流+重绘 s.color = "blue"; // 重绘 s.backgroundColor = "#ccc"; // 重绘 s.fontSize = "14px"; // 再一次 回流+重绘 document.body.appendChild(document.createTextNode('abc!'));// 添加node,再一次 回流+重绘
-
如何减少重排次数
- 更新节点的样式, 尽量通过类名而不是通过style来更新
-
优质博文: https://juejin.cn/post/6844904083212468238
40. 区别HTTP与HTTPS
- http协议的问题
- 传输的数据都是明文, 如果攻击者截取了传输报文,就可以直接读懂其中的信息
- 不适合传输一些敏感信息,比如:信用卡号、密码等支付信息
- https协议
- http的升级版, 在http外包装上SSL(Secure Sockets Layer)层
- 是带加密传输、身份认证的网络协议,要比http协议安全