拉勾前端高薪就业课程笔记第二弹(模块1-2)

1 篇文章 0 订阅
1 篇文章 0 订阅

一、ES6新特性

1. 块级作用域

  1. ES6新增了块级作用域的概念,即使用{}包裹的代码块即为块级作用域。
  2. ES6之前并没有块级作用域的概念,var 关键字声明的变量也不会受{}的限制。ES6也新增了两个生命变量的关键字,letconst
  3. letconst 声明的变量只在其所在的{} 内起作用,而且变量声明不存在声明提升,必须先声明后使用。
  4. let 声明的变量在赋值之后仍可以再次修改,而 const 则是用来定义常量,变量赋值之后就不可再修改。

2.模板字符串

  1. 模板字符串使用 `` 来包裹,字符串内的内容会原样输出。

    	const template = `hello 
      你好,
      李磊,
      哈哈
    `;
    console.log(template);
    

运行结果:在这里插入图片描述

  1. 模板字符串内可以使用${}来使用js表达式,表达式的值将会 替换${}的位置。

    const name = 'LiLei';
    const template = `hello, my name is ${name}`; // hello, my name is LiLei
    
  2. 带标签的模板字符串,这里的标签指得是标签函数。带标签的模板字符串的结果为标签函数返回的结果。

    const name = 'tom';
    const sex = 1;
    const r1 = console.log`${name} is a ${sex}`;
    console.log(r1);
    

    运行结果:在这里插入图片描述

  3. 标签函数接收一个按${}分割模板字符串得到的数组,和模板字符串中${}包裹的参数,并返回处理之后的结果作为带标签模板字符串的值。

    const name = 'tom';
    const sex = 1;
    function tempString(arr, name, sex) {
      const sexName = sex ? 'man' : 'woman'
      return name + arr[1] + sexName;
    }
    const r = tempString`${name} is a ${sex}`;
    console.log(r);
    

    运行结果:在这里插入图片描述

3.箭头函数

  1. ES6新增了一种函数的声明方式,箭头函数。

    const print = msg => {
      console.log(msg);
    }
    
  2. 箭头函数和普通函数的区别:箭头函数内部没有 this,其访问的 this 变量是其定义时上层作用域的 this
    如下面两段代码:

    var a = 10;
    var obj = {
      a : 20,
      fn() {
        setTimeout(() => {
          console.log(this.a);
        })
      }
    }
    obj.fn(); // 20
    

    上面的代码中 setTimeout 内部的执行函数为箭头函数,其内部的this指向的是 fn 函数的 this,而调用fn函数时是通过 obj 调用的,所以 this 指向 obj

    var a = 10;
    var obj = {
      a : 20,
      fn: () => {
        setTimeout(() => {
          console.log(this.a);
        })
      }
    }
    obj.fn();// 10
    

    上面的代码中 setTimeout 内部的执行函数为箭头函数,其内部的this指向的是 fn 函数的 this,而fn函数也是一个箭头函数,其this指向的是外层作用域,及全局对象,所以 this 指向 obj

4.Object.is 方法

  1. Object.is 方法判断两个变量是否相等,和 ===功能类似。

  2. Object.is=== 的区别,Object.is 可以判断 +0-0 不相等,NaNNaN 相等。

    console.log(Object.is(+0, -0)); // false
    console.log(Object.is(NaN, NaN)); // true
    console.log(Object.is(0, false)); // false
    console.log(+0 === -0); // true
    console.log(NaN === NaN); // false
    

5.Proxy代理对象

  1. Proxy代理对象,为对象添加统一的操作监听方法,如setgetdelete等。

    const obj = {
      a: 342,
      b: 123,
    };
    const person = new Proxy(obj, {
      // target === obj; receiver === person
      get: function (target, property, receiver) {
        console.log(target, property, receiver);
        return property in target ? target[property] : 'undefined'
      },
      // target === obj; receiver === person
      set: function(target, property, value, receiver) {
        console.log(target, property, value, receiver);
        target[property] = value;
      },
      deleteProperty(target, property) {
        console.log(target, property);
        delete target[property];
      }
    });
    person.a = 123;
    console.log(person.b);
    

    运行结果:
    在这里插入图片描述

  2. Object.defineProperty对对象的单个属性设置访问代理,但只针对 setget,不能对 delete 等操作进行监控。

    const obj = {
      a: 342,
      b: 123,
    };
    Object.defineProperty(obj, 'a', {
      get() {
        console.log(arguments);
        return obj._a;
      },
      set(val) {
        obj._a = val;
      }
    });
    
    obj.a = 123;
    console.log(obj.a);
    

    运行结果:
    在这里插入图片描述

  3. ProxyObject.defineProperty 的区别:

    1. Proxy功能更强大,不仅可以对setget进行监控,还可以对delete等操作进行监控,Object.defineProperty只能对setget进行监控
    2. Proxy操作更简单,只需要定义一次监控函数就可以对所有属性进行监控,而Object.definedProperty需要对每个属性定义监控函数。
    3. Proxy也可以对数组进行监控,Object.defineProperty对数组监控比较麻烦。
    4. Object.defineProperty对源对象进行监控,而Proxy是对代理对象进行监控。

6.Reflect

  1. Reflect是一个内置对象,和Math类似,不可构造。
  2. Reflect提供了一些方法和操作符的函数实现,为对象操作提供了统一的形式。其内的一些方法也是Proxy监听函数的默认操作方法。
    const person = new Proxy(obj, {
      get: function (target, property, receiver) {
        console.log(target, property, receiver);
        return Reflect.get(target, property)
      },
      set: function(target, property, value, receiver) {
        console.log(target, property, value, receiver);
        Reflect.set(target, property)
      },
      deleteProperty(target, property) {
        console.log(target, property);
        Reflect.deleteProperty(target, property)
      }
    });
    person.a = 123;
    console.log(person.b);
    
    运行结果:
    在这里插入图片描述

7.类

  1. 类的声明通过 class 来实现,通过 new 来创建类的实例对象。

    class Person {
      constructor(name) {
        this.name = name;
      }
      say() {
        console.log(`this is ${this.name}`);
      }
    }
    const tom = new Person('tom');
    tom.say(); // this is tom
    
  2. 类的静态成员通过 static 来定义,静态成员只能通过 Class. 的方式来访问,静态方法中的this 指向类本身。

    class Person {
      constructor(name) {
        this.name = name;
      }
      say() {
        console.log(`this is ${this.name}`);
      }
      static create(name) {
        console.log(this); //[class Person]
        return new Person(name)
      }
    }
    const tom = Person.create('tom');
    tom.say(); // this is  tom
    

8.Set

  1. Set 是ES2015 提供的一种新的数据结构,与数组类似,但是其中的元素不可重复。

  2. Set的创建可以通过 new 关键字来创建,可以选择传入一个初始数组。

    const arr = [1, 2, 3, 4, 5, 2, 1];
    const s = new Set(arr);
    const s1 = new Set();
    
  3. Set操作方法:

    1. 添加元素:add,接收一个要添加的值,返回一个新的Set,所以支持链式调用 。
      s.add(11).add('a').add(32).add(34);
      
    2. 遍历:forEachfor...of 循环。
      s.forEach(i => console.log(i));
      for (let i of s) {
        console.log(i);
      }
      
    3. 获取长度:set.size
      console.log(s.size);
      
    4. 判断是否包含某个值:set.has(val)
      s.has(1)
      
    5. 删除某个值:set.delete(val),返回删除的元素
      console.log(s.delete(1)); // true
      console.log(s.delete(1231));// false
      
    6. 清空:set.clear()
      s.clear();
      
  4. 与数组的相互转换

    1. 数组转化为Set,通过 new Set(array) 来实现
      const arr = [1, 2, 3, 4, 5, 2, 1];
      const s = new Set(arr);
      
    2. Set 转化为数组
      1. 通过 Array.from(Set)方法

        const array = Array.from(s);
        
      2. 通过扩展运算符 [...Set]

        const array = [...s]
        

9.Map

  1. 存储任意类型的键值对,接收任意类型的数据作为键。

    const m = new Map();
    const tom = {
      name: 'tom'
    };
    m.set(tom, 99); // 添加键值对
    m.forEach((v, k) => console.log(v, k));
    console.log(m);
    console.log(m.get(tom)); // 获取某个key对应的value
    console.log(m.has(tom)); // 判断是否包含某个key
    console.log(m.delete(tom));// 删除map中的某个key
    m.clear(); // 清空所有键值
    

    输出结果:
    在这里插入图片描述

10.Symbol

  1. Symbol是ES2015新增的基本类型,表示唯一。

    const symbol = Symbol('ffff');
    
  2. Symbol([string]):每次都会创建新的symbol类型,即使传入相同的描述。Symbol函数只能接受字符串,如果传入非字符串,会将其先转换成字符串。Symbol(true) ⇔ Symbol('true')

    console.log(Symbol('foo') === Symbol('foo')); // false
    
  3. Symbol.for(key):根据keysymbol注册表中查找Symbol,如果找到则返回,若没有找到,则创建一个新的Symbol, 并放入全局的 Symbol 注册表中。注册表内部维护了一个字符串到Symbol的映射

    console.log(Symbol.for('foo') === Symbol.for('foo')); // true
    
  4. Symbol还包含一些内置常量,如Symbol.toStringTag用于定义toString打印的对象标签

    const obj = {
      a: '123',
      [Symbol.toStringTag]: 'xswqe',
    };
    console.log(obj.toString()); // [object xswqe] 添加Symbol之前打印 [object Object]
    
  5. Object对象无法使用 for...of循环遍历, Symbol.iterator定义for...ofIterator接口

    const obj = {
      a: 12,
      b: 2,
      c: 3,
      [Symbol.iterator]() {// Iterable 接口
        const all = [this.a, this.b, this.c];
        let index = 0;
        return { // Iterator 接口
          next() {
            return { // IteratorResult 接口
              value: all[index],
              done: index++ >= all.length
            }
          }
        }
      }
    };
    for(const o of obj) {
      console.log(o);
    }
    

11.for…of循环

  1. 所有实现 Iterator 接口的对象,都可以使用 for...of 循环进行遍历,为集合提供统一的遍历方式

  2. 实现了Iterator 接口的对象都有一个[Symbol.iterator]方法
    在这里插入图片描述

  3. 该方法返回一个具有next方法的Iterator对象,调用next方法将返回一个包含valuedone 的对象,表示迭代器内部实现了一个指针,指向当前访问的值
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    const s = new Set();
    s.add(1).add(2).add(3);
    const setIterator = s[Symbol.iterator]();
    console.log(setIterator.next());
    console.log(setIterator.next());
    console.log(setIterator.next());
    console.log(setIterator.next());
    

在这里插入图片描述

  1. Object对象无法使用for...of进行遍历,因为缺乏Iterable接口,所有要想使用for...of进行循环,需要自己定义Iterable接口

    const obj = {
      a: 12,
      b: 2,
      c: 3,
      [Symbol.iterator]() {// Iterable 接口
        const all = [this.a, this.b, this.c];
        let index = 0;
        return { // Iterator 接口
          next() {
            return { // IteratorResult 接口
              value: all[index],
              done: index++ >= all.length
            }
          }
        }
      }
    };
    for(const o of obj) {
      console.log(o);
    }
    

12.ES2016

  1. array.includes:与array.indexOf相比,可以判断数组中的NaN

    const arr = [1, 2, 3, NaN, 4];
    console.log(arr.indexOf(1)); // 0
    console.log(arr.indexOf(NaN)); // -1
    console.log(arr.includes(1)); // true
    console.log(arr.includes(NaN)); // true
    
  2. 指数运算符:**

    console.log(2 ** 9); // 512
    

13.ES2017

  1. Object.values:返回值组成的数组

  2. Object.entries:返回键值对组成的数组

    const obj = {
      a: 123,
      b: 'c'
    };
    for(const [k, v] of Object.entries(obj)) {
      console.log(k, v);
    }
    console.log(Object.entries(obj)); //[ [ 'a', 123 ], [ 'b', 'c' ] ]
    console.log(new Map(Object.entries(obj)));
    

    运行结果:
    在这里插入图片描述

  3. Object.getOwnPropertyDescriptors:获取所有对象属性的完整描述

    1. Object.assign()不能完全复制对象的settergetter属性。assign()会把这两个属性作为普通属性进行复制,所以得到下面的结果

      const obj = {
      	firstName: 'Wu',
      	lastName: 'ShaoQing',
      	get fullName() {
      		console.log(this)
      		return (`${this.firstName} ${this.lastName}`)
      	},
      }
      const p = Object.assign({}, obj);
      p.firstName = 'Zhang';
      console.log(p, p.fullName) // WuShaoqing
      

      因为此时fullName只是p1中的一个普通属性,它的值是固定的。
      在这里插入图片描述

    2. 利用Object.getOwnPropertyDescriptors可以完成settergetter 属性的完美复制

      const p = {
        firstName: 'Lei',
        lastName: 'Wang',
        get fullName() {
          return `${this.firstName} ${this.lastName}`;
        }
      };
      
      const descriptors = Object.getOwnPropertyDescriptors(p);
      const p1 = Object.defineProperties({}, descriptors);
      p1.firstName = 'Wu';
      console.log(p1.fullName); // Wu Wang
      

二、TypeScript

1.语言类型分类

  1. 按类型系统划分:静态类型动态类型
    1. 静态类型:变量声明时类型就确定了,除了做强制类型转换,类型不可再更改
    2. 动态类型:运行阶段才能确定变量类型,变量类型可以随时变化
  2. 按类型安全划分:强类型弱类型
    1. 强类型:不允许任意的隐式类型转换,在编译期间就会报错
    2. 弱类型:允许任意的隐式类型转换

JS是弱类型的动态语言

2.Flow

  1. Flow是一种静态类型转换器,通过类型注解,在编译阶段检查代码,查找代码使用的异常。
  2. Flow的使用
    1. npm i flow-bin -d 安装 flow-bin 检查工具\

    2. 编辑文件,为要检查的文件添加@flow注释,并为代码添加类型注解
      在这里插入图片描述

    3. 执行yarn flow init 生成flow配置文件

    4. 执行yarn flow,注意项目的文件路径中不能有中文

  3. 移除Flow类型注解
    1. 使用flow-remove-types插件
      1. 安装 flow-remove-types 插件
      2. 运行命令 yarn flow-remove-types . -d dist 命令,将移除类型注解之后的文件放在dist目录中。其中 . 表示当前目录,如果是其他目录就换成目录名
    2. 使用 babel
      1. 安装 @babel/core @babel/cli @babel/preset-flow
      2. 新建.babelrc文件,并添加 presets: ['@babel/preset-flow']
      3. 运行命令 yarn babel . -d src 命令,将移除类型注解之后的文件放在dist目录中。其中 . 表示当前目录,如果是其他目录就换成目录名
  4. Flow 特性
    1. 类型推断:flow能够根据代码推断类型
    2. 类型注解:通过变量后面加上:[类型]对类型进行注解。推荐使用类型注解
    3. 原始数据类型:
      1. string
      2. number:100/NaN/Infinity
      3. boolean: true/false
      4. null: null
      5. undefined: undefined
      6. Symbol:Symbol()
      7. void: undefined
    4. 数组: Array<T>T[][number, string](元组)
    5. 对象:两种定义方法
      1. const obj: {foo?: string, bar: string} = {foo: ‘string’, bar: ‘string’ }
      2. const obj:{[string]: string} = {} 表示可以添加任意个字符串键值对
    6. 函数类型:
      1. 函数定义 function(a: stirng): string { return ‘string’; }
      2. 函数作为参数: function(callback:(string, number) => void){}
    7. 特殊类型:
      1. 字面量类型: const str: ‘f00’ = ‘f00'; 变量的值只能是变量后面指定的值

      2. 联合类型(或类型): const a: ‘success’ | ‘danger’ | ‘warning’ = ‘warning'; const b: string | mumber = 100; 只能指定值/类型中的一个

      3. 自定义类型: 使用type定义自定义类型

        type stringOrNumber = string | number;
        const a:stringOrNumber = 120;
        
      4. maybe类型: const a: ?number = undefined/null/100; 通过在类型前面添加?来定义,表示类型或undefinednull

      5. mixedany类型:表示任意类型。

        1. mixed是强类型,在确定变量类型之前,不能使用类型相关的方法,比如toString
        2. any是弱类型,可以使用类型相关方法。

3.Typescript(TS)

  1. TSJavaScript 的超集,包含 JavaScriptES6 和 类型系统

  2. TS 的使用

    1. npm i typescript -d 安装 TypeScript
    2. 编写.ts文件
    3. yarn tsc filename; 编译指定文件,会产生同名的js文件,js文件中会移除类型注解。
  3. 配置文件

    1. 运行命令 yarn tsc --init 生成配置文件
    2. 配置文件只会在执行 yarn tsc 对项目中所有文件进行编译时生效,如果通过 yarn tsc filename 编译指定文件时是不会生效的
  4. 原始数据类型

    1. string: 非严格模式下可以是null/undefined严格模式下不能。严格模式和非严格模式可以通过配置文件中的 strict 属性控制
    2. number100/NaN/Infinity,非严格模式下可以是null/undefined,严格模式下不能
    3. boolean: true/false,非严格模式下可以是null/undefined,严格模式下不能
    4. null: null
    5. undefined: undefined
    6. void: undefined, 非严格模式下还可以是null
    7. symbol: Symbol()Symboles2015特性,如果转化成ES5,则无法转化。配置文件中target
  5. 错误消息提示中文:yarn tsc --locale zh-CN

  6. 变量重名问题解决:

    1. 自执行函数包裹
    2. 添加 export {} 开启模块化
  7. object类型:除了原始类型之外的数据类型,包括 objectfunctionarray

    const obj:object =  function () {};// [] //{}
    const o: {} = {};
    
  8. 数组:

    const arr1: Array<number> = [1, 2, 3];
    const arr2: number[] = [1, 2, 3];
    
  9. 元组:固定长度固定类型的数组

     const tuple: [string, number] = ['a', 1];
    
  10. 枚举类型:通过 enum 声明

    1. 默认值为从0开始累加

    2. 如果没有给定值,则将从第一个给定值的地方开始累加

      enum postStatus {
          Draft = 0, // 0
          Unpublished, // 1
          Published = 'aa', // aa
      }
      
    3. 如果存在字符串,则字符串之后的所有元素需要给定值。

      enum postStatus {
          Draft = 0, // 0
          Unpublished, // 1
          Published = 'aa', // aa
          Private = 'bb'
      }
      
    4. 使用:和 Object 中属性的使用一致。

  11. 函数

    // 函数声明
    function fn(a: string, b?: number, ...rest: number[]): void {}
    // 字面量
    const func: (a:string, b:number) => void = function (a: string, b:number): void {
    };
    
  12. 任意类型:any, 动态类型

  13. 隐式类型推断:根据变量的使用推断变量的类型。尽可能为每个变量添加类型

  14. 类型断言(assertion):由代码编写者确定的类型,只在编译时起作用。使用方法:

    1. 通过as 关键字

    2. 在变量前使用 <>,但是在 jsx 语法中不能使用,因为 jsx 中存在 <> 表示标签

      const nums = [111, 2, 333];
      const res = nums.find(i => i > 0);
      const num1 = res as number;
      const num2 = <number> res;
      
  15. 接口:为有结构的数据做类型约束

    1. 可选成员、只读成员、动态成员

      interface Post {
          title: string
          desc: string
          // subTitle?: string // 可选成员
          readonly summary: string //只读成员
          [prop: string ]: string // 动态成员,允许添加指定类型的任意数量的成员
      }
      function printInfo(post: Post) {
          console.log(`${post.title} ${post.desc}`);
      }
      const post:Post = {
          title: 'book',
          desc: 'this is a book',
          summary: 'book store'
      };
      printInfo(post);
      
    2. 可选属性和动态成员同时存在时,会报错。因为动态成员的keystring/number类型,但是可选成员key则是 undefined | string 类型
      在这里插入图片描述

  16. 类的用法与ES6的不同:

    1. 类的成员属性需要在类中声明,不能只在constructor中动态定义

    2. 类成员必须指定默认值,在声明时或constructor中指定均可。

      class Person {
          name: string
          age: number
          constructor(name:string, age: number) {
              this.name = name;
              this.age = age;
          }
          sayHi(msg: string):void {
              console.log(`hello, this is ${this.name}`, msg);
          }
      }
      
    3. 类的成员可以使用访问修饰符修饰:

      1. public:默认 ,无特殊限制
      2. private:不可继承,只能在类内部访问。private 如果修饰 constructor 构造函数,则此类不可通过 new 关键字创建类的实例,需要提供方法,在类的内部创建实例并返回。
      3. protect: 可以继承,只能在类及其子类内部访问。
    4. 只读属性readonly:可以和访问修饰符一起使用,放在访问修饰符后面

      class Person {
          private name: string
          age: number
          protected readonly gender: number = 1
          constructor(name:string, age: number) {
              this.name = name;
              this.age = age;
          }
          sayHi(msg: string):void {
              console.log(`hello, this is ${this.name}`, msg);
          }
      }
      
  17. 接口:interface,接口功能应该尽量单一,然后使用implements 来让类实现多个接口

    interface Eat{
        eat(food: string): void
    }
    interface Run {
        run(distance: number): void
    }
    
    class Person implements Eat, Run{
        eat(food: string): void {
            console.log(`person eat ${food}`);
        }
        run(distance: number): void {
            console.log(`person run ${distance}`);
        }
    }
    
    class Animal implements Eat, Run{
        eat(food: string): void {
            console.log(`animal eat ${food}`);
        }
        run(distance: number): void {
            console.log(`animal run ${distance}`);
        }
    }
    
    export {}
    
  18. 抽象类:可以包含抽象方法,抽象方法和接口中的方法类似,只有方法声明,没有方法的具体实现。抽象类不可实例化,只能由子类继承,并实现其中的抽象方法。 抽象方法和抽象类都要用abstract修饰。抽象方法也可以包含普通方法

    abstract class Animal {
        abstract eat(food: string): void;
        say(msg: string) {
            console.log(msg);
        }
    }
    
    class Dog extends Animal{
        eat(food: string): void {
            console.log(`Dog eat ${food}`);
        }
    }
    new Dog().say('Hello')
    
  19. 泛型:在定义时不指定类型,调用时传入具体的类型。这样是为了最大程度复用代码。泛型的实现就是将类型作为参数,函数后面添加<T>T就是类型参数,使用时将类型传入到类型参数中。

    function createArray<T> (length: number, value: T): T[] {
        return Array<T>(length).fill(value)
    }
    const res = createArray<number>(3, 11);
    
  20. 类型声明:declare,在使用函数时,没有定义定义类型声明,可以使用declare声明类型。

    1. 使用第三方库时,如果没有类型声明文件时,可以先尝试导入类型声明文件,如果lodash 的类型声明库@types/lodash

    2. 如果没有类型声明库,则可以使用declare来声明

      import { camelCase } from 'lodash'
      declare function camelCase(val: string): string;
      const res = camelCase('wangliwei');
      export {}
      

js性能优化

1.内存管理:内存是可读写单元,表示可操作的空间。内存管理就是开发者主动申请、使用、释放内存空间。js的内存管理是自动的。

// 申请内存空间
let obj = {};
// 使用内存空间
obj.a = 123;
// 无法直接释放内存空间,通过这种方式释放内存空间
obj = null;

2.js垃圾回收(GC)机制:GC 的目的是为了实现内存空间的良性循环

  1. 可达对象: 可以访问到的对象,从根出发可以被找到的对象,也称活动对象
  2. 根: 程序执行的起始环境,在js中即被认为是 global 全局变量对象
  3. 垃圾对象: 无法从根上被访问的变量。
    1. 程序中不在需要使用的对象
    2. 程序中访问不到的变量对象
  4. 垃圾回收(GC): 变量从可达对象变为垃圾对象,并被回收机制回收的过程

3.GC算法

  1. GC是一种机制,垃圾回收器完成具体的工作
  2. 工作的内容:查找垃圾、释放空间、回收空间
  3. 算法就是工作时查找和回收时遵循的规则
  4. 常见的GC算法:
    1. 引用计数

      1. 实现原理:为对象设置引用数,通过判断引用数是否为0,来决定是否要回收对象空间
        1. 引用计数器:维护引用数
        2. 计数改变:在引用关系发生改变时改变,对象被其他对象引用时,引用加1,如果删除对该对象的引用,引用计数就减1
      2. 优点:
        1. 发现垃圾立即回收; 时刻监听引用计数的变化,如果为0,立即回收
        2. 最大限度减少程序暂停:垃圾回收分摊到每时每刻,所以几乎感知不到程序暂停。
      3. 缺点:
        1. 无法回收循环引用的对象,因为计数始终为1
        2. 时间和空间开销大,需要监控引用计数的变化,修改引用计数需要花费额外的时间
    2. 标记清除

      1. 实现原理:分为标记和清除两个阶段
        1. 标记:遍历所有对象,找到活动对象(可达对象)并标记,会递归进行深层次的查找并标记。
        2. 清除:遍历所有对象,清除所有没有标记的对象,并回收对象空间到空闲链表中,下次有对象要使用内存空间时会从空闲链表中取出使用
      2. 优点:可以回收循环引用的对象。
      3. 当回收的内存空间不连续时,而新定义的定义的对象所使用的空间大小不能匹配空闲链表中的空间碎片大小时,空间的使用效率会降低。 因为使用大的碎片空间会造成空间浪费,而使用小的碎片空间有不够。
        在这里插入图片描述
    3. 标记整理: 可以看做标记清除的增强,也分标记和清除两个阶段

      1. 标记阶段和标记清除一致
      2. 清除阶段在清除没有标记的对象之前,会先移动活动对象的位置,使地址连续,然后再清除没有标记的对象,回收的内存空间地址将连续,之后的使用将最大程度的使用内存空间
    4. 分代回收

4.V8引擎:最主流的js执行引擎,js 的高效运转

  1. 支撑js高效运转的基础
    1. 优秀的内存管理
      1. 内存设限:
        1. 64bit 下不超过1.5G
        2. 32bit 下不超过800M
      2. 内存设限原因:
        1. V8是专为浏览器设置的,限制内的内存足够浏览器应用使用
        2. 内部的垃圾回收机制决定限制是合理的。Google官方实验:当垃圾内存达到1.5G时,采用增量标记的算法进行垃圾回收需要50ms,而采用非增量标记的算法需要1s
    2. 即时编译
      1. 其他js执行引擎在执行js代码时需要先将代码转成字节码才能执行
      2. V8则是直接将js代码翻译成可以直接执行的机器码,所以速度非常快
  2. V8垃圾回收策略:垃圾回收主要是针对对象数据,原始数据由语言本身管理
    1. 采用分代回收的思想,将内存分为新生代和老生代
      1. 分代回收将内存分为两块,小的(64bit 32M | 32bit 16M)那块为新生代存储区,大的那块为老生代存储区

      2. 新生代:存活时间较短的对象,新生代空间分为带下相等的两块fromtofrom是使用空间,to是空闲空间

        1. 回收过程采用复制算法和标记整理算法
        2. 回收过程:
          1. 活动对象存储于from空间
          2. from空间使用超过一定程度后,对from空间的活动对象进行整理
          3. 将整理完的from空间的活动对象复制到to空间,并释放
        3. 回收过程细节
          1. 回收过程中可能出现 晋升
            1. 晋升是指从将新生代移至老生代
            2. 一轮GC之后还存活的对象需要晋升
            3. to空间的使用率超出25%之后,会将其中的对象都移动至老生代。
      3. 老生代:存活时间较长的对象,如全局变量、闭包等。 大小(64bit 1.4G | 32bit 700M),垃圾回收主要采用标记清除、标记整理、增量标记算法

        1. 首先使用标记清除完成垃圾空间的回收
        2. 当老生代中的空间碎片不足以供晋升使用时,会触发标记整理,对碎片空间进行优化。
        3. 采用增量标记提升效率。
          1. 垃圾回收将会阻塞js的执行
          2. 标记增量:就是将整个垃圾回收拆分成多个小段组合执行。程序执行和垃圾回收交替进行
            在这里插入图片描述
      4. 新老生代回收细节对比:

        1. 新生代采用复制算法速度快,但总有一半的空间闲置,是使用空间换时间。
        2. 老生代不适合复制算法,因为空间比较大,如果使用复制算法,太浪费了。
    2. 针对不同的对象采用不同的算法。
    3. 常用GC算法:
      1. 分代回收
      2. 空间复制
      3. 标记清除
      4. 标记整理
      5. 增量标记:为了提高效率

5.内存监控:performance工具

  1. 内存问题的外在表现:
    1. 页面出现延迟加载或暂停
    2. 糟糕的性能,经常卡顿(内存膨胀)
    3. 流畅度越来越差(内存泄漏)
  2. 界定内存问题的标准:
    1. 内存泄漏:内存使用持续升高
    2. 内存膨胀:应用本身消耗内存较大,有的设备可能满足不了
    3. 频繁的垃圾回收:通过内存变化图进行分析
  3. 监控内存的几种方式:
    1. 浏览器任务管理器:shift + esc 打开

    2. Timeline 时序图:浏览器工具 performance,监控内存变化

    3. 堆快照查找分离DOM

      1. 分离DOM: 界面元素是在DOM树上存在的,从DOM树上脱离,但是在JS代码中被引用的DOM 对象
      2. 垃圾对象的DOM节点:从DOM树上脱离,没有被引用的DOM节点,将会被GC 回收
      3. 分离DOM,会造成内存泄漏。可以使用堆快照查找,detached 开头的就是分离DOM, 浏览器工具 内存
        在这里插入图片描述
    4. 判断是否存在频繁的垃圾回收

      1. Timline 中内存的频繁的上升下降
      2. 任务管理器中频繁的数据变化

6.js性能优化

  1. JSBench的使用:https://jsbench.me/ 在线js性能测试

  2. 慎用全局变量

    1. 全局变量处于作用域链的顶端,变量搜索时会自底向上搜索,耗时比较长
    2. 全局变量存在于全局的执行上下文中,直到程序退出才会释放空间,不方便GC工作。
    3. 同名的局部变量会污染全局变量
  3. 缓存全局变量:在局部作用域中使用全局变量时,使用局部变量缓存全局变量更有效率。缩短了查找过程,但增加了内存消耗。

  4. 通过原型对象添加方法:把附加方法添加在原型对象中,比在方法内部添加方法 执行效率更好

    const fn1 = function () {
      this.foo = function () {
        console.log(111);
      }
    };
    const f1 = new fn1();
    f1.foo();
    
    const fn2 = function () {
    };
    fn2.prototype.foo = function () {
      console.log(222);
    };
    const f2 = new fn2();
    f2.foo();
    
  5. 闭包陷阱:即内存泄漏。

  6. 避免属性访问方法的使用,直接访问属性效率更高

    1. js中属性都是可以在外部访问的,没必要通过属性访问方法达到私有化的控制
    2. 使用属性访问方法会增加一层方法定义
  7. for循环的优化:缓存数组的长度。

    const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    for (let i = 0, len = arr.length; i < len; i++) {
      console.log(arr[i]);
    }
    
  8. 节点操作的优化:

    1. 对于多个节点的添加,创建好节点之后一次添加,而不是每次都添加
    2. 对于多次新增节点,可以先创建节点,然后克隆节点再添加。
  9. 堆栈中js的执行过程:

    1. 创建执行栈,并创建全局的执行上下文环境开始执行
    2. 变量定义
      1. 如果遇到基础类型定义,则直接在栈中开辟空间存放变量的值
      2. 如果遇到函数(引用类型)定义,则在堆区开辟空间存放函数定义信息,并将引用地址赋值给变量
    3. 函数执行:创建新的执行上下文,并开始执行函数内部代码,执行逻辑和全局的执行逻辑一致。过程:
      1. 确定this指向
      2. 初始化作用域链
      3. 声明变量
      4. 执行逻辑代码
  10. js 性能优化之代码优化

    1. 减少判断嵌套层级,先判断终止条件

      var doSomething = function (part, level) {
        const parts = ['A', 'B', 'C', 'D'];
        if (part){
          if (parts.includes(part)) {
            console.log('属于当前课程');
            if(level > 5) {
              console.log('请提供 VIP 身份');
            }
          }
        } else {
          console.log('请确认模块信息');
        }
      }
      

      优化后:

      var doSomething = function (part, level) {
        const parts = ['A', 'B', 'C', 'D'];
        if (!part){
          console.log('请确认模块信息');
          return;
        }
        if (!parts.includes(part)) return;
        console.log('属于当前课程');
        if(level > 5) {
          console.log('请提供 VIP 身份');
        }
      }
      
    2. 减少作用域链的查找层级:变量的查找是自底向上查找的,采用局部变量缓存上层作用域链的变量,可以缩短查找时间。空间换时间

    3. 字面量和构造式:字面量形式是直接开辟空间存储变量对应的值,而构造式则是调用函数,中间多出了很多过程,所以花费的时间要长很多

    4. 惰性函数:对于一些要根据平台才能判断使用的函数,应该使用变量来缓存,而不是每次调用函数的时候判断。

      let Request = function () {
        if(window.XMLHttpRequest) {
          return window.XMLHttpRequest;
        } else if(window.ActiveXObject) {
          return window.ActiveXObject;
        }
      };
      

      优化后:

      let Request = function () {
        if(window.XMLHttpRequest) {
          getRequest = window.XMLHttpRequest;
        } else if(window.ActiveXObject) {
          getRequest = window.ActiveXObject;
        }
        return getRequest;
      };
      
    5. 事件代理:利用冒泡机制,在父元素中统一绑定事件而不是在每个子元素中绑定事件

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值