[复习笔记-04] 04

306 阅读25分钟

导航

[react] Hooks

[封装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] 二分查找和排序

[深入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确定后,将其添加到栈中
    • 入栈:在函数执行上下文创建后,将其添加到栈中
    • 出栈:在当前函数执行完成后,栈顶的执行上下文对象出栈
    • 最后:当所有代码执行完后,栈中只剩下全局上下文对象
  • 栈的特点
    • 后进先出,入口出口一样
  • 队列的特点
    • 先进先出,入口出口不一样 image.png
  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. 提升优先级
- 函数形参 > 函数声明 > 变量声明
- 函数名已经存在,新的覆盖旧的
- 变量名已经存在,直接略过变量的声明
  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>
  );
}

image.png

(四) 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 - 位小数 image.png image.png

(4.2) 数值计算过程

  • 计算过程主要分为两步:( 进制转换 ) 和 ( 对阶运算 )

(4.3) 进制转换 - 存在精度丢失

  • 会根据IEEE754标准,将 十进制转成二进制
  • 用十进制数除以2,取余数,并自下而上取余数值即可,如下图 image.png
0.1 -> 0.0001100110011001...(无限循环)
0.2 -> 0.0011001100110011...(无限循环)

根据IEEE754尾数位数限制,需要将后面多余的位截掉,=> 所以存在精度丢失

(4.4) 对阶运算 - 存在精度丢失

  • 由于指数位不相同,运算时需要对阶运算
  • 对阶运算也可能存在精度丢失
    • 如果把阶码小的向阶码大的看齐,在移位过程中如果发生数据丢失,也是最右边的数据位发生丢失,最右边的数据位丢失,只会影响数据的精度,不会影响数据的大小
    • 在计算机中,采用小阶向大阶看齐的方法,实现对
  • 参考文章

(4.5) 总结

  • js中计算精度丢失出现在,( 进制转换 ) 和 ( 对阶运算 ) 中
  • 解决方案:将一个数乘以10000,计算完结果再除以10000
  • 其他注意点:
    • 如果一个 ( 10进制 ) 数字 ( 超出了16位 ) 后,转成字符串,会不相等
      • (12345678901234567).toString() 转成字符串后是 "12345678901234568"

(五) xml 和 html 的区别

  • ( html超文本标记语言 ) ( xml可扩展标记语言 )
  • 作用不同
    • html
      • html是 ( 显示数据 ) 的
    • xml
      • xml是用来 ( 描述数据 ) 和 ( 存储数据 ) 的,可以作为持久化的介质
      • xml将成为最普遍的数据处理和数据传输工具
      • xml没有语法规则,但是有句法规则
  • 语法要求不同
    • 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 表示可枚举
(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
          • get set
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()

image.png

(十五) 数组去重

// 数组去重

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…