导航
[封装01-设计模式] 设计原则 和 工厂模式(简单抽象方法) 适配器模式 装饰器模式
[封装02-设计模式] 命令模式 享元模式 组合模式 代理模式
[React 从零实践01-后台] 代码分割
[React 从零实践02-后台] 权限控制
[React 从零实践03-后台] 自定义hooks
[React 从零实践04-后台] docker-compose 部署react+egg+nginx+mysql
[React 从零实践05-后台] Gitlab-CI使用Docker自动化部署
[源码-webpack01-前置知识] AST抽象语法树
[源码-webpack02-前置知识] Tapable
[源码-webpack03] 手写webpack - compiler简单编译流程
[源码] Redux React-Redux01
[源码] axios
[源码] koa
[源码] vuex
[源码-vue01] data响应式 和 初始化渲染
[源码-vue02] computed 响应式 - 初始化,访问,更新过程
[源码-vue03] watch 侦听属性 - 初始化和更新
[源码-vue04] Vue.set 和 vm.$set
[源码-vue05] Vue.extend
[源码-vue06] Vue.nextTick 和 vm.$nextTick
[源码-react01] ReactDOM.render01
[源码-react02] 手写hook调度-useState实现
[部署01] Nginx
[部署02] Docker 部署vue项目
[部署03] gitlab-CI
[深入01] 执行上下文
[深入02] 原型链
[深入03] 继承
[深入04] 事件循环
[深入05] 柯里化 偏函数 函数记忆
[深入06] 隐式转换 和 运算符
[深入07] 浏览器缓存机制(http缓存机制)
[深入08] 前端安全
[深入09] 深浅拷贝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模块化
[深入13] 观察者模式 发布订阅模式 双向数据绑定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手写Promise
[深入20] 手写函数
[深入21] 数据结构和算法 - 二分查找和排序
[深入22] js和v8垃圾回收机制
[深入23] JS设计模式 - 代理,策略,单例
[深入24] Fiber
[深入25] Typescript
[深入26] Drag
[前端学java01-SpringBoot实战] 环境配置和HelloWorld服务
[前端学java02-SpringBoot实战] mybatis + mysql 实现歌曲增删改查
[前端学java03-SpringBoot实战] lombok,日志,部署
[前端学java04-SpringBoot实战] 静态资源 + 拦截器 + 前后端文件上传
[前端学java05-SpringBoot实战] 常用注解 + redis实现统计功能
[前端学java06-SpringBoot实战] 注入 + Swagger2 3.0 + 单元测试JUnit5
[前端学java07-SpringBoot实战] IOC扫描器 + 事务 + Jackson
[前端学java08-SpringBoot实战总结1-7] 阶段性总结
[前端学java09-SpringBoot实战] 多模块配置 + Mybatis-plus + 单多模块打包部署
[前端学java10-SpringBoot实战] bean赋值转换 + 参数校验 + 全局异常处理
[前端学java11-SpringSecurity] 配置 + 内存 + 数据库 = 三种方式实现RBAC
[前端学java12-SpringSecurity] JWT
[前端学java13-SpringCloud] Eureka + RestTemplate + Zuul + Ribbon
复习笔记-01
复习笔记-02
复习笔记-03
复习笔记-04
前置知识
一些单词
execution 执行 // execution context 执行上下文
mutation 改变 突变 // MutationObserver
algorithm 算法
migration 迁移
repository 仓库
setup 设置 组织
induction 入职 归纳 诱导
descriptor 描述符
immutable 不可变的
(一) 作用域 和 执行上下文 的区别
- ( 作用域 ) ( 作用域链 ) ( 执行上下文 ) ( 执行上下文栈 )
- 总结 作用域 和 执行上下文 的区别!!!!!!!!!!!!!!!
- 形成的时机
- 函数作用域:在函数定义时确定
- 函数执行上下文对象:在函数调用并且在函数体未执行时创建
- 静态和动态
- 函数作用域:是静态的,函数定义时确定,且不会再变化
- 函数执行上下文对象:是动态的,函数调用时创建,函数执行完后被释放
- 联系
- ( 执行上下文对象 ) 是从属于所在的 ( 作用域 )
- ( 全局执行上下文对象 ) 从属于 ( 全局作用域 )
- ( 函数执行上下文对象 ) 从属于 ( 函数作用域 )
- 形成的时机
(1.0) 作用域
- 作用域的概念
- 指的是 ( 变量和函数 ) 存在的范围
- 作用域的
主要作用
隔离变量
,即不同作用域
下的同名变量
不会冲突
- 作用域的分类
全局作用域
- 变量在整个程序中一直存在函数作用域
- 变量只在函数内部存在,函数创建时形成函数作用域
块级作用域
- 函数本身的作用域
- 函数本身也是一个值,也有自己的作用域
函数的作用域与变量一样,是声明时所在的作用域,与其运行时所在的作用域无关
- 函数执行时所在的作用域,是函数定义时的作用域,而不是调用时的作用域
- ( 函数体 ) ( 内部 ) 声明的函数,( 内部的函数的作用域 ) 绑定在外面函数的内部
- 特点
全局变量
:函数外部声明的变量是全局变量,可以在函数内部读取(只能是一层函数)局部变量
:- 函数外部无法读取函数内部的变量
( 函数内部的变量 ) 会在 ( 该作用域内 ) 覆盖 ( 全局变量 )
对于var命令来说,局部变量只能在函数中声明,在其他区块中声明,一律是全局变量
(1.1) 作用域链 - 由嵌套的作用域组成
(1.2) 执行上下文
- EC: execution context
- 详见 juejin.cn/post/684490…
- 分类
全局执行上下文
函数执行上下文
eval
- 全局执行上下文
- 在
执行全局代码之前
,将 (window
确定为全局执行上下文
) - 对 ( 全局数据 ) 进行 ( 预处理 )
- 全局变量
- var定义的全局变量(变量提升):赋值为undefined,并添加为window的属性
- 全局函数
- function声明的全局函数(函数提升):赋值为fun函数,添加为window的方法
- this
- 将 this 赋值给 widnow
- 全局变量
- 开始执行全局代码
console.log(`a`, a, window.a); console.log(`b`, b, window.b); console.log(`this`, this) var a = 10; function b() { return 20; } // a undefined undefined // b b() { return 20; } b() { return 20; } // this window
- 在
- 函数执行上下文
- 在 (
调用函数
),(准备执行函数体之前
),(创建
) 对应的 (函数执行上下文对象
)- 注意对象全局执行上下文和函数执行上下文
- 全局:全局代码执行前,将window作为全局执行上下文对象 ->
window之前就存在
- 函数:函数调用时并且函数体执行前,创建函数执行上下文对象 ->
新创建的
- 对局部数据进行预处理
- 形参
- 声明一个形参变量,并赋值为实参,并添加为该函数执行上下文对象的属性
- argument对象
- argument对象的赋值,并添加为该函数执行上下文对象的属性
- 局部变量
- 变量提升,声明变量,并赋值为undefined,并且添加为该函数执行上下文对象的属性
- function
- 函数提升,整个函数提升到头部,并且添加为该函数执行上下文对象的属性
- this
- 将this赋值给,调用该函数的对象
- 形参
- 在 (
- 执行上下文总结
- 执行上下文分为:全局执行上下文,函数执行上下文,eval
- 执行上下文的本质:( 执行上下文 ) 就是一个 ( 对象 )
- 全局执行上下文
- 全局代码执行前,把window作为全局执行上下文对象
- 全局变量提升,全局函数提升,this赋值
- 函数执行上下文:在函数调用时,并且在函数体执行之前,创建函数执行上下文对象
- 函数执行上下文的创建时机是:函数调用时,并且在函数体执行之前
- 形参赋值,arguments对象赋值,局部变量提升,局部函数提升,this赋值
每调用一次函数,就会产生一个函数执行上下文
(1.3) 执行上下文栈
- 定义
- 在 (
全局代码执行前
),js引擎会创建一个 (栈
) 来存储管理所有的 (执行上下文对象
) - 入栈:在全局执行上下文window确定后,将其添加到栈中
- 入栈:在函数执行上下文创建后,将其添加到栈中
- 出栈:在当前函数执行完成后,栈顶的执行上下文对象出栈
- 最后:当所有代码执行完后,栈中只剩下全局上下文对象
- 在 (
- 栈的特点
- 后进先出,入口出口一样
- 队列的特点
- 先进先出,入口出口不一样
console.log(`a`, a);
var a = 1;
fn(a);
function fn(a) {
if (a === 4) return;
console.log("前", a);
fn(a + 1);
console.log("后", a);
}
// undefined 前1前2前3后3后2后1
(1.4) 箭头函数this和上层作用域的关系
- 箭头函数中的this,指的是 ( 该箭头函数定义时所在的作用域的 ) ( 上层作用域中的this )
- 这里复习下什么是作用域
- 作用域:
- 指的是变量存在的范围,分为全局作用域,函数作用域,块级作用域
- 函数作用域:函数定义时产生,并且不会再变化
- 执行上下文
- 执行上下文其实就是一个对象
- 分为全局执行上下文,函数执行上下文,eval
- 函数执行上下文对象:在函数调用时,并且在函数体执行前生成
- 作用域:
箭头函数this
箭头函数中的this指得是: 该箭头函数定义时,上层作用域中的this
-------
// 1
var a = 100;
const obj = {
a: 1,
b: function () {
console.log(this.a);
},
c: () => console.log(this.a),
};
obj.b(); // 1,普通函数中的this,在函数调用时确定指向,即this指代的是函数调用时所在的对象
obj.c();
// 100,
// 箭头函数中的this,是箭头函数定义时所在所用域的上层作用域中的this
// 1. c箭头函数定义时所在的作用域是 ( 块级作用域obj )
// 2. obj块级作用域的上层作用域是window,window.a=100,所以这里输出100
// 2
var A = {
name: "A",
sayHello: function () {
var s = () => console.log(this.name);
return s;
},
};
var sayHello = A.sayHello(); // 不管外面如何调用,都不会影响箭头函数this的指向,和调用无关
sayHello();
// 输出 A
// 1. 箭头函数s定义时所在的作用域是:函数作用域 sayHello 函数
// 2. 上层作用域:是块级作用域A,this就指向A,A.name = 'A'
(1.5) 变量提升相关 - 题目
1. 提升优先级
- 函数形参 > 函数声明 > 变量声明
- 函数名已经存在,新的覆盖旧的
- 变量名已经存在,直接略过变量的声明
- 案列连接
- 案例 1
function a(name, age) {
console.log(name); // wang
var name = 10;
console.log(name); // 10
console.log(age); // undefined
}
a('wang')
---
实际执行的代码如下:
function a(name, age) {
// 1. 变量提升:形参 > 函数声明 > 变量声明
var name = 'wang' // 形参赋值实参
var age = undefined // 未传实参,则将形参赋值为undefined
// var name = undefined 变量提升,但是变量名已经存在,则直接略过变量的声明
console.log(name) // 'wang'
name = 10 // 从新赋值
console.log(name) // 10
console.log(age) // undefined
}
---
最终结果
'wang' 10 undefined
- 案例 2
function a(name) {
console.log(name); // function name() {.....}
var name = 10;
function name() { console.log('20') }; }
a('wang')
---
实际执行的代码如下:
function a(name) {
var name = 'wang'
// var name = undefined 变量提升,但是变量名已经存在,直接略过变量的声明
function name() { console.log('20')} // 函数提升,但是函数名已经存在,则新的覆盖旧的,即函数覆盖掉'wang'
console.log(name) // 此处打印函数
name = 10 // 从新赋值
}
---
最终结果
function name() { console.log('20') };
(二) 浏览器的组成
- 浏览器的 ( 核心 ) 是两个部分
- (
渲染引擎
) - (
javascript引擎
) - ( 也叫javascript解析器
)
- (
(2.1) 渲染引擎
- 渲染引擎的主要作用
- 将 ( 网页代码 ) 渲染成 ( 用户视觉可以感知到的平面文档 )
- 不同的浏览器有不同的渲染引擎
- 渲染引擎处理代码,主要分为 6 个阶段
parseHtml->parseStylesheet->evaluateScript->layout->paint->composite
- 详见此链接
(2.2) javascript引擎
- javascript引擎的作用读取网页中的javascript代码,对其处理后运行
- 浏览器对javascript代码处理如下
词法分析
:读取代码,进行词法分析(Lexical analysis)
,将代码
分解成词元(token)
语法分析
:对词元token
进行语法分析(parsing)
,将代码整理成“语法树”(syntax tree)
字节码
:使用“翻译器”(translator),将代码转为字节码(bytecode)
机器码
:使用“字节码解释器”(bytecode interpreter),将字节码
转为机器码
- 总结
代码 -> 词元token -> 语法树syntaxTree -> 字节码 -> 机器码
(三) react-ElementDiff中,index作为key的缺点?
- vue类似
- 案例1
- 渲染三个input,分别在三个input中输入 0 1 2
- 删除第一个input
key=index
- 删除第一个input的index=0,删除后,剩下两个input渲染时index分别是0 1
- diff对比时
- 由于之前的(key=012),( 现在的key=01 )
- 所以react认为前两个元素没有发生变化,只是没了第三个元素
- 所以操作就是直接删除了第三个元素,剩下的两个input框的值是 0 1,而期望是 1 2
- 不符合预期
key=id
- 删除第一个input的key=0,删除后,剩下两个input渲染时key分别是1 2
- diff对比时
- 由于之前的(key=012),( 现在的key=12 )
- 所以react认为删除了第一个元素
- 所以剩下的两个input的值是 1 2
- 符合预期
import { useState } from "react";
export default function App() {
const list = [{ id: 0 }, { id: 1 }, { id: 2 }];
const [arr, setArr] = useState(list);
const del = () => {
const _arr = [...arr];
_arr.shift();
setArr(_arr);
};
const renderIndex = () => arr.map((_, index) => <input key={index} />);
const renderId = () => arr.map(({ id }) => <input key={id} />);
return (
<div className="App">
错误:{renderIndex()}
<br />
正确:{renderId()}
<br />
<button onClick={del}>删除第一个元素</button>
</div>
);
}
(四) 0.1 + 0.2 精度丢失
(4.1) 前置知识
- js中用
number
类型来表示数字,没有区分整数
和浮点数
- js中通过
64位二进制
编码来表示一个数字,遵循IEEE754
标准 - IEEE754
- 规定了四种方法表示浮点数
单精度( 32位 )
双精度( 64位 )
- 延伸单精度 43 - 很少用
- 延伸双精度 80 - 很少用
- 一个浮点数的表示
Value = sign * exponent * fraction
浮点数的实际值 = 符号位 * 指数偏移值 * 分数值
- exponent 指数
- fraction 分数 小数 碎片
- 符号位 ------- 1
- 最高有效位 - 被指定为符号位
- 1 - 位符号位
- 指数偏移值 ------- 11
- 次高有效的e个比特,存储指数部分
- 指数偏移值:指的是指数域的偏移值
指数偏移值 = 指数的实际值 + 某个固定的值
- 11 - 位指数偏移值
- 小数-分数值 ------- 52
- 剩下的f个低有效位的比特,存储“有效数”(significand)的小数部分
- (在非规约形式下整数部分默认为0,其他情况下一律默认为1)
- 52 - 位小数
(4.2) 数值计算过程
- 计算过程主要分为两步:(
进制转换
) 和 (对阶运算
)
(4.3) 进制转换 - 存在精度丢失
- 会根据IEEE754标准,将 十进制转成二进制
用十进制数除以2,取余数,并自下而上取余数值即可
,如下图
0.1 -> 0.0001100110011001...(无限循环)
0.2 -> 0.0011001100110011...(无限循环)
根据IEEE754尾数位数限制,需要将后面多余的位截掉,=> 所以存在精度丢失
(4.4) 对阶运算 - 存在精度丢失
- 由于指数位不相同,运算时需要对阶运算
- 对阶运算也可能存在精度丢失
如果把阶码小的向阶码大的看齐,在移位过程中如果发生数据丢失,也是最右边的数据位发生丢失,最右边的数据位丢失,只会影响数据的精度,不会影响数据的大小
- 在计算机中,采用小阶向大阶看齐的方法,实现对
- 参考文章
(4.5) 总结
- js中计算精度丢失出现在,( 进制转换 ) 和 ( 对阶运算 ) 中
- 解决方案:将一个数乘以10000,计算完结果再除以10000
- 其他注意点:
- 如果一个 ( 10进制 ) 数字 ( 超出了16位 ) 后,转成字符串,会不相等
(12345678901234567).toString() 转成字符串后是 "12345678901234568"
- 如果一个 ( 10进制 ) 数字 ( 超出了16位 ) 后,转成字符串,会不相等
(五) xml 和 html 的区别
- ( html超文本标记语言 ) ( xml可扩展标记语言 )
- 作用不同
- html
- html是 ( 显示数据 ) 的
- xml
- xml是用来 ( 描述数据 ) 和 ( 存储数据 ) 的,可以作为持久化的介质
- xml将成为最普遍的数据处理和数据传输工具
- xml没有语法规则,但是有句法规则
- html
- 语法要求不同
- html中不区分大小写,xml中严格区分大小写
- html可以拥有不带值的属性名,xml所有属性都必须带相应的值
- 标记不一样
- html使用固有标记,而xml没有固有标记
- html标签是预定义的,而xml是自定义的,可扩展的
(六) html5标签的兼容性问题
(6.1) html5的新标签 header
nav
不兼容ie8
- 解决:
- 通过 document.createElement('header')
- 在css中设置 header: { display: block; }
- 原因:
- IE8浏览器认为header标签是个用户自定义的标签,所以显示的时候不支持
- document.createElement(‘header’)明确告诉它 这里是创建的标签
- 然后 针对header标签 设置对应的CSS样式为display:block
document.createElement('header');
header {
display: block;
}
(七) Object.keys(), Object.getOwnPropertyNames(), for...in 三者的区别
- Object.keys()
- 只遍历自身属性,不包括继承的属性
- 并且是可枚举属性
自身属性 + 可枚举属性
- Object.getOwnPropertyNames()
- 遍历自身属性,不包括继承的属性
- 包括不可枚举属性
自身属性 + 可枚举属性 + 不可枚举属性
- for ... in
自身的属性 + 可枚举属性 + 继承的属性
- property in object
- 'name' in {name: 111} // true
- 检测对象是否包含某个属性,包含true,不包含false
自身属性 + 继承的属性
- Object.keys()和Object.getOwnPropertyNames()的对比
- 相同点:
- 都是遍历自身属性,都不可遍历继承的属性
- 都可以遍历 ( 对象和数组 ),即参数是数组或对象
- 不同点
Object.keys() = 自身属性 + 可枚举属性
Object.getOwnPropertyNames() = 自身属性 + 可枚举属性 + 不可枚举属性
- 所以
- 一般情况都是使用 Object.keys() 去遍历对象
- 而不是使用 Object.getOwnPropertyNames()
- 相同点:
// Object.keys()
// Object.getOwnPropertyNames()
// - Object.keys()
// - 只遍历自身属性,不包括继承的属性
// - 并且是可枚举属性
// - `自身属性 + 可枚举属性`
// - Object.getOwnPropertyNames()
// - 遍历自身属性,不包括继承的属性
// - 包括不可枚举属性
// - `自身属性 + 可枚举属性 + 不可枚举属性`
// - for...in
// - `自身的属性 + 可枚举属性 + 继承的属性`
// - **Object.keys()和Object.getOwnPropertyNames()的对比**
// - 相同点:
// - 都是遍历自身属性,都不可遍历继承的属性
// - **都可以遍历 ( 对象和数组 )**,即参数是数组或对象
// - 不同点
// - `Object.keys() = 自身属性 + 可枚举属性`
// - `Object.getOwnPropertyNames() = 自身属性 + 可枚举属性 + 不可枚举属性`
// - 所以
// - 一般情况都是使用 Object.keys() 去遍历对象
// - 而不是使用 Object.getOwnPropertyNames()
const arr = [1, 2];
const obj = {
name: 1,
age: 2,
};
Object.defineProperty(obj, "EnumerableProperty", {
value: "EnumerableProperty",
enumerable: true,
});
Object.defineProperty(obj, "NotEnumerableProperty", {
value: "NotEnumerableProperty",
enumerable: false,
});
const obj2 = Object.create(obj); // obj2继承obj的属性
obj2.extends = "extends";
// obj
const keys = Object.keys(obj);
const ownPropertyNames = Object.getOwnPropertyNames(obj);
for (let i in obj2) {
console.log("for...in", i); // extends name age EnumerableProperty
}
console.log(`keys`, keys); // ['name', 'age', 'EnumerableProperty']
console.log(`ownPropertyNames`, ownPropertyNames); // ['name', 'age', 'EnumerableProperty', 'NotEnumerableProperty']
// arr
const keys2 = Object.keys(arr);
const ownPropertyNames2 = Object.getOwnPropertyNames(arr);
for (let i in arr) {
console.log("for-in", i);
} // 0 1
console.log(`keys2`, keys2); // ['0', '1']
console.log(`ownPropertyNames2`, ownPropertyNames2); //['0', '1', 'length']
(八) Object 上的一些静态方法
- 前置知识
- 1.什么是可枚举属性,什么是不可枚举属性?如何设置
枚举属性:指的是可以遍历的属性
不可枚举属性:指的是不可遍历的属性
- 设置:Object.defineProperty(obj, property, {value, enumerable:true})
- enumerable: true 表示可枚举
- 1.什么是可枚举属性,什么是不可枚举属性?如何设置
(8.1) 控制对象状态的方法
Object.preventExtensions() ------- 防止对象扩展,不能添加属性
Object.seal() -------------------- 禁止对象配置,不能添加删除,但是可以修改
Object.freeze() ------------------ 冻结一个对象,不能添加删除修改
Object.isExtensible 判断对象是否可扩展
Object.isSealed() 判断对象是否可配置
Object.isFrozen() 判断对象是否被冻结
(8.2)对象属性模型的相关方法
Object.getOwnPropertyDescriptor():获取某个属性的描述对象
Object.getOwnPropertyNames():遍历自身属性 + 可枚举属性 + 不可枚举属性
Object.defineProperty():通过描述对象,定义某个属性
Object.defineProperties():通过描述对象,定义多个属性
Object.preventExtensions()
- 作用:防止对象扩展,即不能添加新的属性
- 注意点
- 如果是通过 Object.defineProperty(obj, property, {}) 的方式添加属性会报错
- 如果是通过 object.a = xxx 的方式添加属性,不会报错,但是添加不了新的属性
// Object.preventExtensions() // 防止对象扩展 // 即永远不能添加属性 Object.preventExtensions(obj); obj.e = 5; console.log(`Object.preventExtensions()1`, obj); // e不能添加到obj中,但是不报错 Object.defineProperty(obj, 'f', {value: 6}) console.log(`Object.preventExtensions()2`, obj); // 直接报错,Uncaught TypeError: Cannot define property f, object is not extensible
Object.seal() 和 Ojbect.freeze()
- Object.seal()
- 作用:不能添加删除属性,但是可以修改属性
- Object.freeze()
- 作用:不能添加删除修改属性
Object.defineProperty
- 1.作用
- 添加一个属性
- 修改一个属性
- 并返回此对象
- 2.语法
- Object.defineProperty(obj, prop, descriptor)
- 参数
- obj需要定义属性的对象,prop要定义或者修改的属性的名称,descriptor属性描述符
- 返回值
- 被传递给函数的对象
- 默认情况
- 默认情况下,通过 Object.defineProperty() 添加的属性是 ( 不可修改的 )
- 相当于在属性描述对象中的 immutable=true
属性描述符
的两种形式
- 数据描述符 和 存取描述法,
都是对象
,一个描述符:只能是其中一种,不能同时写
- 数据描述符对象:是一个具有值的属性
- 存取描述法对象:是由 get 和 set 函数组成的对象
- ( 数据描述符对象 ) 和 ( 存取描述符对象 ) ( 共享 ) 的方法
- value
- 属性对应的值
- writable
- 只有值是true时,value才能被改变
默认是false
- enumerable
- 属性是否可枚举
默认是false
- configurable
- 值为true时
- 该属性的描述符对象才能够被改变
- 该属性可以被删除
默认是false
- 值为true时
- get set
- value
- 数据描述符 和 存取描述法,
- 参数
- Object.defineProperty(obj, prop, descriptor)
console.log(`----------------------`);
const obj3 = { a: 1 };
Object.defineProperty(obj3, "b", {
value: 2,
writable: false, // 默认是false,只有是true时,value的值才可以改变
enumerable: false, // 默认是false,不可枚举,( 可枚举属性可以被Object.keys() Object.getOwnPropertyNames() for...in 遍历到 )
configurable: true,
// 默认是false,表示属性描述符不能被修改
// 如果是true,属性描述符对象可以被修改,该属性可以被删除
});
obj3.b = 22;
console.log(
`Object.defineProperty()添加的属性,默认情况下是不能修改的,即immutable=true`,
obj3.b // 2,还是2,并为被修改成22,但是如果 writeable=true 时,b属性的值是可以被修改的
);
console.log(`Object.keys(obj3) => 自身属性+可枚举属性`, Object.keys(obj3))
console.log(`Object.getOwnPropertyNames(obj3) => 自身属性+可枚举属性+不可枚举`, Object.getOwnPropertyNames(obj3))
for(let item in obj3) {
console.log(`for..in => 自身属性+可枚举属性+继承的属性`, item)
}
Reflect.deleteProperty(obj3, 'b')
console.log(`configurable=true,属性描述对象可以被修改,该属性可以被删除`, obj3)
(九) 常见的 宏任务 和 微任务
- 微任务
promise
process.nextTick
- 在node生命周期的任意阶段优先执行,因为它是一个微任务MutationObserver
- 改变观察的意思
- 宏任务
setTimeout
setInterval
setImmediate
- 在node的poll阶段之后的check阶段中执行requestAnimationFrame
- 占用主线程
- 扩展:
- 1.node和浏览器的事件循环机制
- 2.为什么process.nextTick可以在node事件循环的各个阶段优先执行?
(十) 闭包
- 闭包产生的条件
- 要有嵌套的函数
- 内部函数 引用 到了外部函数的 变量
- 执行外层函数
- 闭包的个数计算
- 闭包的个数就是 ( 外层函数被调用的次数 )
- 闭包的作用
- 1.使 ( 函数内部的变量 ) 在 ( 函数调用结束 ) 后,仍然保存在内存中,( 延长了局部变量的生命周期 )
- 2.让函数外部,可以操作到函数内部的变量
- 3.可以用来封装模块,即module可以操作内部变量,但是不会污染外部的变量
- 如何清除闭包
- 如何清除
- 将外层函数赋值的变量,重新给该变量赋值其他的值,比如 a = null
- 能清除的原因
- 包含了 ( 闭包变量 ) 的 ( 函数对象 ) 成为 ( 垃圾对象 ),所以闭包将会被垃圾回收机制清除
- 其实就是因为变量还引用这内层函数
- 如何清除
(十一) vue中父子组件的生命周期的顺序
(1) mount 阶段
- 创建:父组件 先于 子组件 创建
- 挂载:子组件 先于 父组件 挂载
父组件-beforeCreate
父组件-created
父组件-beforeMount
子组件-beforeCreate
子组件-created
子组件-beforeMount
子组件-mounted
父组件-mounted
(2) update 阶段
- 父组件 先于 子组件 beforeUpdate
- 子组件 先于 父组件 updated
父组件-beforeUpdate
子组件-beforeUpdate
子组件-updated
父组件-updated
(3) unMount 阶段
- 父组件 先于 子组件 beforeUnmount
- 子组件 先于 父组件 unmounted
父组件-beforeUnmount
子组件-beforeUnmount
子组件-unmounted
父组件-unmounted
(4) 在vue3中 ( 自定义指令 ) 中来实战父子生命周期
- vue3中优化了指令中的钩子名称,和组件的生命周期保持一致,比vue2做的更好了
1. 全局指令
const app = Vue.createApp({})
app.directive('focus', {
mounted(el) {}
})
2. 局部指令
directives: {
focus: {
mounted(el) {}
}
}
3. 指令中的钩子函数
- created
- beforeMount
- mounted
- mounted 表示在 ( 被绑元素的父元素 ) 被 ( 挂载后 ) 调用
- 注意:
- 因为:mounted生命周期钩子是 子组件的mounted()执行后,父组件的mounted()才会执行
- 所以:这里父组件挂载完时,子组件即绑定指定的组件肯定也挂在完成了
- beforeUpdate
- updated
- beforeUnmount
- unmounted
4. 钩子函数的参数
- el
- 指令所绑定的元素,可以用来直接操作 DOM
bind
- 一个对象,包含以下属性
- name:指令名,不包括 v- 前缀
- value:指令的绑定值
- oldValue:指令绑定的前一个值
- expression:字符串形式的指令表达式
- arg:传给指令的参数,可选
- modifiers:一个包含修饰符的对象
- vnode:Vue 编译生成的虚拟节点
- oldVnode:上一个虚拟节点
(十二) plainObject
前置知识
1
typeof
typeof 返回的数据类型,一共有 7 种
- number string boolean undefined symbol
- object function
2
原型链
2.1 函数
- 所有的 ( 函数 ),都是大写的 ( Function.prototype ) 的实例
- 包括自身,即 Function.__proto__ === Function.prototype
2.2 函数的prototype
- 所有 ( 函数的prototype ),都是大写的 ( Object.prototype ) 的实例
2.3 案例
- 2.3.1
Function.__proto__ === Function.prototype 所有的函数都是Function.prototype对象的实例
- 2.3.2
function Constructor(){}
Constructor.__proto__ === Function.prototype 所有的函数都是Function.prototype对象的实例
Constructor.prototype.__proto__ === Object.prototype 所有 函数的prototype 都是 Object.prototype 的实例
(1) 什么是 plainObject
- 定义:通过对象字面量方式定义的对象{}和通过Object.create()定义的对象就是plainObject
- const obj = {}
- const obj = Object.create()
(2) react-redux 对 plainObject 的utils函数的定义
- 分析1
- 因为:
const obj = Object.create(null)
- 所以:
Object.getPrototypeOf(obj) === null
// true
- 因为:
- 分析2
- 因为:
const obj = {}
- 所以:
Object.getPrototypeOf(obj) === Object.prototype
// true
- 因为:
react-redux 中如何利用 ( 原型链 ) 来判断是否是 plainObject
-------
/**
* @param {any} obj The object to inspect.
* @returns {boolean} True if the argument appears to be a plain object.
*/
export default function isPlainObject(obj) {
if (typeof obj !== 'object' || obj === null) return false
let proto = Object.getPrototypeOf(obj)
if (proto === null) return true // 说明是通过 Object.creare(null) 创建,生成的就是一个plainObject
let baseProto = proto
while (Object.getPrototypeOf(baseProto) !== null) {
// 如果:( 参数对象 ) 的原型对象不是 ( null ),即满足while的条件
// 那么:就一层层往上赋值成原型对象,直到参数对象的原型对象是null
// 而:Object.prototype === null
// 所以:baseProto === Object.prototype 就终止寻找
baseProto = Object.getPrototypeOf(baseProto)
}
// 2. 原型链第一个和最后一个比较
// 因为:plainObject对象的 obj.__proto__ === Object.protype
// 所以:是 plainObject 返回 true
// 3
// 对比一下
// function 的情况
// 假设我们这里obj是一个 function
// proto 是 Function.prototype
// baseProto 是 Object.prototype
// 所以:当参数传入的是一个function的时,返回的是 false,说明不是一个纯对象
// 4
// 对比一下
// array 的情况
// 假设我们这里obj是一个 array
// proto 是 Array.prototype
// baseProto 是 Object.prototype
// - Object.getPrototypeOf([]) === Array.prototype
// - Object.getPrototypeOf(Array.prototype) === Object.prototype // 任何函数的prototype 都是 Object.prototype 的实例
// 所以:当参数传入的是一个数组的时,返回的是 false,说明不是一个纯对象
return proto === baseProto
}
(十三) background-position: 50% 和 20% 的定位算法
- 是根据图片本身的百分比来定位的
- 比如:
background-position: 50%;的定位点是图片的50%来作为定位点,所以能完全居中
- 比如:
background-position: 0;的定位点事图片的0%的位置来定位,所以是左上角,不会超出为负数
(十四) 处理URL
- 2021/12/04 更新
1
const url = new URL(url [, base])
- 参数
- url
- 一个表示 ( 绝对或者相对 ) URL 的 DOMString
- 绝对URL:base参数将被忽略
- 相对URL:会把 base 参数作为基准URL
- base
- 一个基准URL的DOMString
- base参数生效的前提是:第一个参数url是相对URL
- 返回值
- 新生成的url实例对象
- 如果给定的基本 URL 或生成的 URL 不是有效的 URL 链接,则会抛出一个`TypeError`
- !!!!!!!! ----> url.searchParams() 和 new URLSearchParams() 的返回值一样
- !!!!!!!! ----> new URL('https://.../?s=url').searchParams.get('s') === new URLSearchParams('?s=url').get('s')
- 例子
const url = new URL('/api/?author=woow_wu7&age=20#head', 'http://www.baidu.com')
// 等同于 const url = new URL('http://www.baidu.com/api/?autho=woow_wu7&age=20#head')
// href: "http://www.baidu.com/api/?author=woow_wu7&age=20#head"
// 注意:这里 query 一定要在 hash 前面,不然取不到query,会把query部分也作为hash
2
const URLSearchParams = new URLSearchParams(init)
- 参数
- init:一个url信息的string
- 返回
- URLSearchParams 实例
- 实例对象上的属性
- append(key, value) 添加search键值对
- delete(key) 删除
- entries() 返回迭代器对象 - 可以被 for...of 遍历
- forEach() 遍历
- get(key) 获取
- getAll(key) 因为可以存在相同key的情况,对应不同的value值
- has(key) 是否存在
- keys()
- set(key, value)
- sort()
- toString()
(十五) 数组去重
// 数组去重
const arr = [1, 3, 3, 4, 5, 5, 5, 7];
// 1
const single = (arr) => {
const result = [];
for (let i = 0; i < arr.length; i++) {
!result.length && result.push(arr[i]);
let hasSame = false;
for (let j = 0; j < result.length; j++) {
result[j] == arr[i] && (hasSame = true);
}
hasSame ? (hanSame = true) : result.push(arr[i]);
}
return result;
};
// 2
const single = (arr) => {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j]) {
arr.splice(j, 1);
j--;
}
}
}
return arr;
};
// 3
const single = (arr) => {
return [...new Set(arr)];
};
// 4
const single = (arr) => {
const map = new Map();
const result = [];
for (let i = 0; i < arr.length; i++) {
if (!map.has(arr[i])) {
result.push(arr[i]);
map.set(arr[i], 1);
} else {
map.set(arr[i], map.get(arr[i]) + 1);
}
}
console.log(map);
return result;
};
const res = single(arr);
console.log("res", res);
(十六) 判断是否是数组的 5 种方法
// 判断数组的方法
const arr = [1, 2, 3];
// 1
// Array.isArray(arr)
const isArray = Array.isArray(arr);
// 2
// Object.prototype.toString.call(arr) -> '[object, Array]'
const typeString = Object.prototype.toString.call(arr);
const isArray2 = typeString.includes("Array");
// 3
// array instanceof Array
const isArray3 = arr instanceof Array;
// 4
// array
// - Array.prototype.isPrototypeOf(arr) -> 因为所有的数组都是构造函数Array通过new命名生成的,所以 ( array实例的原型 ) 就是 ( Array.prototype )
// object
// - Object.prototype.isPrototypeOf(obj)
// - 用来判断 ( Object.prototype ) 是不是 ( 参数对象 ) 的 ( 原型对象 )
const isArray4 = Array.prototype.isPrototypeOf(arr);
// 5
// array.constructor === Array
// array实例可以继承其原型对象上的constructor属性,指向原型对象的构造函数,即 Array.prototype.constructor === Array
const isArray5 = arr.constructor === Array;
资料
plainObject juejin.cn/post/684490…