学习笔记2-ES6/TypeScript/JavaScript内存优化

ES6

  • 数组解构

    let arr = [100,200,300]
    const [name, age, sex] = arr
    console.log(name, age, sex)
    
    const [, ,bar] = arr
    console.log(bar)
    
    const [a,...rest] = arr   // rest展开运算符只能用在最后的位置
    console.log(a)
    console.log(rest)
    
  • 对象解构

    let person = {
      name: "John",
      age: 30,
      sex: "male"
    }
    
    const { name, age, sex } = person
    console.log(name, age, sex)
    
    const {name: restName , age, sex} = person
    console.log(restName, age, sex)
    
  • 模板字符串

    可以使用 \ 转义

    自动识别换行符

    插值赋值(${js语句})

    // 带标签的模板字符串   模板字符串之前添加一个标签(也就是一个函数)
    const name = 'aa'
    const value = 2
    function check(strings) {
      console.log(strings)
      return '111'
    }
    
    const result = check`hey, ${name} is a ${value}`
    console.log(result)
    
  • 字符串的扩展方法

    • includes:字符串中是否包含某个字符
  • 参数默认值(形参中使用等号)

    function fn(a = true) {
      
    }
    fn()   // 此时默认参数为 true
    
  • 扩展运算符(rest)

    // rest 展开数组
    const arr = [100, 200, 300]
    let arr1 = []
    console.log.apply(console, arr)  // 老语法
    console.log(...arr)
    arr1.push(...arr)
    
  • 箭头函数

    优势:1. 代码简洁 2. 易读性高 3. 不改变 this 指向

    const i = n => n + 1
    console.log(i(100))
    const person = {
      name: 'John',
      age: 50,
      say: () => {
        console.log(this.name)
      },
      go: function() {
        console.log(this.name)
      }
    }
    person.say()
    person.go()
    
  • 对象字面量的增强

    • Object.assign(target, source) 将目标对象中的属性放到源对象中

      const source1 = {
        a: 123,
        b: 123
      }
      const source2 = {
        b: 789,
        d: 789
      }
      const target = {
        a: 456,
        c: 456
      }
      const result = Object.assign(target, source1, source2)
      console.log(target)
      console.log(target === result)
      
    • Object.is(value1, value2) 判断两个对象是否相同(值相同并且内存中的地址也相同)

      let a = { a: 1, b: 2 }
      let b = { a: 1, b: 2 }
      let c = a
      Object.is(a, b)  // false
      Object.is(a, c)  // true
      
    • Object.keys(obj) 返回对象所有键组成的数组

      let obj = { a: 1, b: 2, c: 3 }
      Object.keys(obj)   // [a, b, c]
      
    • Object.values(obj) 返回对象所有值组成的数组

      let obj = { a: 1, b: 2, c: 3 }
      Object.values(obj)   // [1, 2, 3]
      
    • Object.entries(obj) 返回对象所有的键值对

      let obj = { a: 1, b: 2, c: 3 }
      Object.entries(obj)   // [[a, 1], [b, 2], [c, 3]]
      
    • Object.getOwnPropertyDescriptors(obj) 返回对象的所有信息(包括是否可写、是否可配置、是否可枚举)

      const p1 = {
        firstName: 'John',
        lastName: 'Smith',
        get fullName() {
          return this.firstName + ' ' + this.lastName
        }
      }
      const p2 = Object.assign({}, p1)   // { firstName: 'aaa', lastName: 'Smith', fullName: 'John Smith' }   单纯的复制
      p2.firstName = 'aaa'
      console.log(p2)
      const descriptors = Object.getOwnPropertyDescriptors(p1)
      console.log(descriptors)
      const p3 = Object.defineProperties({}, descriptors)
      p3.firstName = 'bbb'
      console.log(p3.fullName)   // bbb Smith   统一进行了修改
      // String.prototype.padStart / String.prototype.padEnd   用给定长度的字符串去填充目标字符串的开始/结束位置
      const books = {
        html: 5,
        css: 16,
        javascript: 133
      }
      for (const [name, count] of Object.entries(books)) {
        console.log(name, count)
      }
      for (const [name, count] of Object.entries(books)) {
        console.log(`${name.padEnd(16, '-')}|${count.toString().padStart(3, '0')}`)
      }
      
  • 对象代理(Proxy

    const person = {
      name: 'John',
      age: 30
    }
    const personProxy = new Proxy(person, {
      get(target, property) {
        console.log(target)
        return property in target ? target[property] : 'default'
      },
      set(target, property, value) {
        if (property === 'age') {
          if (!Number.isInteger(value)) {
            throw new typeError('value must be an integer')
          }
        }
        target[property] = value
      }
    })
    personProxy.age = 100
    personProxy.gender = true
    console.log(personProxy.name)
    console.log(personProxy.xxx)
    
    // Proxy和defineProperty   后者只能监听读写   前者还能监听到诸如delete的操作
    const person = {
      name: 'John',
      age: 30
    }
    const personProxy = new Proxy(person, {
     deleteProperty(target, p) {
       console.log('delete', p)
       delete target[p]
     }
    })
    delete personProxy.age
    console.log(personProxy)
    const list = []
    const listProxy = new Proxy(list, {
      set(target, p, value) {
        console.log('set', p, value)
        target[p] = value
        return true
      }
    })
    listProxy.push(100)   // set 0 100
    
  • Reflect静态类

    Reflect成员方法就是Proxy处理对象的默认实现

    Reflect 对象提供了一套用于操作对象的API

    const obj = {
      foo: 123,
      bar: 456
    }
    const proxy = new Proxy(obj, {
      get(target,p) {
        console.log('watch logic')
        return Reflect.get(target,p)
      }
    })
    console.log(proxy.foo)
    Reflect 对象实际使用方法
    const obj = {
      name: 'foo',
      age: 30
    }
    老方法
    console.log('name' in obj)    // true
    console.log(delete obj['age'])   // true
    console.log(Object.keys(obj))    // ['name']
    Reflect对象方法
    console.log(Reflect.has(obj,'name'))    // true
    console.log(Reflect.deleteProperty(obj,'age'))   //true
    console.log(Reflect.ownKeys(obj))   // ['name']
    
  • Promise全新的异步解决方案

  • class类关键词

    // ES5
    function Person(name) {
      this.name = name;
    }
    Person.prototype.say = function () {
      console.log(this.name)
    }
    // ES6
    class Person2 {
      constructor(name) {
        this.name = name
      }
      say() {
        console.log(this.name + 'say')
      }
    }
    const p = new Person('lin')
    p.say()
    const p2 = new Person2('lin')
    p2.say()
    
    • 静态方法 static关键词

      class Person2 {
        constructor(name) {
          this.name = name
        }
        say() {
          console.log(this.name + 'say')
        }
        static fn() {
          
        }
      }
      Person2.fn()   // 类直接调用
      
    • 类的继承 extend关键词

      class Person {
        constructor(name) {
          this.name = name;
        }
        say() {
          console.log(this.name + 'say');
        }
      }
      class Student extends Person {
        constructor(name,number) {
          super(name);
          this.number = number;
        }
        hello() {
          super.say();
          console.log(this.number + 'number');
        }
      }
      const person = new Student('lin', 100);
      person.hello();
      
  • Set 集合 新的类型(数据结构)内部数据是唯一的,不重复的

    const s = new Set();
    s.add(1).add(2).add(3).add(1);
    console.log(s);
    console.log(s.size);
    console.log(s.has(100));
    console.log(s.delete(3));
    console.log(s)
    s.clear()
    console.log(s)
    
  • Map 键值对,键名可以为任意数据类型

    const m = new Map();
    const o = {p: 'Hello World'};
    
    m.set(o, 'content')
    m.get(o) // "content"
    
    m.has(o) // true
    m.delete(o) // true
    m.has(o) // false
    
  • Symbol 最主要的作用就是为对象添加独一无二的属性名

    const name = Symbol()
    const person = {
      [name]: 'lin',
      say() {
        console.log(this[name])
      }
    }
    person.say()
    
    // Symbol 补充
    console.log(Symbol() === Symbol())  // false
    console.log(Symbol('aa') === Symbol('aa')) // false
    const s1 = Symbol.for('foo')   // 内部维护的是字符串的对应关系,不是字符串的会自动转化为字符串
    const s2 = Symbol.for('foo')
    console.log(s1 === s2)   // true
    console.log(Symbol.for('true') === Symbol.for(true)) // true
    console.log(Symbol.iterator)  // 内置对象接口
    console.log(Symbol.hasInstance)
    const obj = {}
    console.log(obj.toString()) // [object Object]
    const obj1 = {
      [Symbol.toStringTag]: 'XObject'
    }
    console.log(obj1.toString()) // [object XObject]
    const obj2 = {
      [Symbol()]: 'symbol value',
      foo: 'normal value'
    }   // 正常获取对象属性键名的方法获取不到Symbol类型
    console.log(Object.getOwnPropertySymbols(obj2)) // [ Symbol() ]
    
  • for...of...循环

    作为遍历所有数据结构的统一方式

    使用 break 终止遍历

    类数组也可以直接使用

    Set Map 对象也可以直接使用

    无法遍历非迭代对象

    const arr = [100, 200, 300, 400]
    for (const item of arr) {
      console.log(item)
      if (item > 100) {
        break
      }
    }
    const s = new Set([100, 200])
    for (const item of s) {
      console.log(item)
    }
    const m = new Map()
    m.set('foo', '123')
    m.set('bar', '456')
    for (const item of m) {
      console.log(item)
    }
    const obj = { foo: 123, bar: 456 }   // obj并不是一个迭代对象
    for (const item of obj) {
      console.log(item)   // 报错  TypeError: obj is not iterable
    }
    
  • 可迭代接口(Iterable接口)

    实现Iterable 接口是for...of...循环遍历的前提

    const s = new Set(['foo', 'bar', 'baz'])
    const iterable = s[Symbol.iterator]()
    console.log(iterable.next())   // { value: 'foo', done: false }
    console.log(iterable.next())   // { value: 'bar', done: false }
    console.log(iterable.next())   // { value: 'baz', done: false }
    console.log(iterable.next())   // { value: undefined, done: true }
    console.log(iterable.next())   // { value: undefined, done: true }
    
    • 实现一个可迭代接口

      const obj = {
        store: ['foo', 'bar', 'baz'],
        [Symbol.iterator]: function () {
          let index = 0;
          const self = this;
          return {
            next: function () {
              const result = {
                value: self.store[index],
                done: index >= self.store.length
              }
              index += 1
              return result
            }
          }
        }
      }
      for (const item of obj) {
        console.log(item)
      }
      
    • 举个栗子理解迭代器

      // 场景: 两人协同开发一个任务清单应用
      const todos = {
        life: ['吃饭', '睡觉', '打豆豆'],
        learn: ['语文', '数学', '外语'],
        work: ['敲代码', '出差'],
      
        each: function (callback) {
          const all = [].concat(this.life, this.learn, this.work)
          for (const item of all) {
            callback(item)
          }
        },
      
        [Symbol.iterator]: function() {
          const all = [...this.life, ...this.learn, ...this.work]
          let index = 0
          return {
            next: function () {
              return {
                value: all[index],
                done: index ++ >= all.length
              }
            }
          }
        }
      }
      todos.each(function(item) {
        console.log(item)
      })
      for (const item of todos) {
        console.log(item)
      }
      // 简化工作流程,降低代码耦合度
      
  • 生成器对象函数

    function * foo() {
      console.log('aaaa')
      return 100
    }
    const result = foo()
    console.log(result)  //Object [Generator] {}
    console.log(result.next())    // { value: 100, done: true }
    function * foo1() {
      console.log('aaaa')
      yield 100
      console.log('bbbb')
      yield 200
      console.log('cccc')
      yield 300
    }
    const generator = foo1()
    console.log(generator.next())
    console.log(generator.next())
    console.log(generator.next())
    
    • 生成器的应用

      // 案例1:发号器
      function * createIdMaker() {
        let id = 1
        while (true) {
          yield id ++
        }
      }
      const idMaker = createIdMaker()
      console.log(idMaker.next())
      console.log(idMaker.next())
      // 案例2: 使用Generator 函数实现 iterator 方法
      const todos = {
        life: ['吃饭', '睡觉', '打豆豆'],
        learn: ['语文', '数学', '外语'],
        work: ['敲代码', '出差'],
      
        [Symbol.iterator]: function * () {
          const all = [...this.life, ...this.learn, ...this.work]
          for (const item of all) {
            yield item
          }
        }
      }
      for (const item of todos) {
        console.log(item)
      }
      
  • 数组的includes方法(判断数组中是否用某个元素)

    let arr = [1, 2, 3]
    arr.includes(1)  // true
    arr.includes(4)  // false
    
  • 指数运算符

    console.log(Math.pow(2, 10))
    console.log(2 ** 10)
    
  • 可以在函数参数中添加尾逗号

    function foo(
      bar,
      foos,
      a,
    ) {
    
    }
    
  • async...await 更加优雅的解决异步方案(内部实现其实也用了生成器的概念)

Flow:JavaScript的类型检查器

  • 安装

    yarn add flow-bin --dev
    
  • 运行检查

    yarn flow
    # 如遇报错没有配置文件则运行
    yarn flow init
    
  • 关闭类型检查监听

    yarn flow stop
    
  • 去除flow的注解

    1. 使用官方的flow-remove-types

      安装:

      yarn add flow-remove-types --dev
      

      运行:

      yarn flow-remove-types . -d dist
      # . 表示当前目录
      # -d 参数表示输出目录
      # dist 输出目录的目录名(不存在会自动创建)
      
    2. 使用babel

      安装:

      yarn add @babel/core @babel/cli @babel/preset-flow --dev
      # babel/core  核心模块
      # babel/cli  命令行工具
      # babel/preset-flow  flow转换工具
      

      添加babel的配置文件 .babelrc:

      {
        "presets": ["@babel/preset-flow"]
      }
      

      运行:

      yarn babel . -d dist
      # . 表示当前目录
      # -d 参数输出目录
      # dist 输出目录名
      
  • 安装Flow插件(VSCode):安装Flow Language Support 插件(体验不是很友好)

    更多插件请访问Flow官网:https://flow.org/en/docs/editors/

  • Flow的类型推断(Type Inference):根据代码中的使用情况自动推断出参数类型

  • Flow的类型注解(Type Annotations)

    // 标记变量的类型
    function a(n: number) {
      return n * n
    }
    let num: number = 100
    // 标记函数返回值
    function b(): number {
      return 100
    }
    // 没有返回值的函数类型: 函数没有返回值默认返回了undefined, 标记返回值类型为 void
    function c(): void {
      
    }
    
  • Flow的原始类型(Primitive Types)

    const a: string = 'a'
    const b: number = 100  // Nan  // Infinity(无穷大)
    const c: boolean = false // true
    const d: null = null
    const e: void = undefined
    const f: symbol = Symbol()
    
  • 数组类型(Array Types)

    const arr: Array<number> = [1, 2, 3]    // 全是数字组成的数组
    const arr1: number[] = [1, 2, 3]      // 全是数字组成的数组
    const foo: [string, number] = ['foo', 100]   // 元组:固定长度的数组,同时确定了数据类型
    
    
  • 对象类型(Object Types)

    const obj: {foo: string, bar: number} = {foo: 'foo', bar: 100}  // 固定键的数量和值的数据类型
    const obj1: {foo?: string, bar: number} = {bar: 100}   // 添加 ? 表示当前属性可选(非必填)
    const obj2: { [string]: string } = {}    // 确定后期添加给对象的键和值的类型
    
  • 函数类型(Fuction Types)

    // 对函数的参数类型和返回值类型进行限制
    // 限制函数参数
    function a(n: number) {
      return n * n
    }
    // 限制函数返回值
    function b(): number {
      return 100
    }
    // 没有返回值的函数类型: 函数没有返回值默认返回了undefined, 标记返回值类型为 void
    function c(): void {
      
    }
    // 限制函数参数中的回调函数的参数类型
    function foo(callback: (string, number) => void) {
      callback('foo', 100)
    }
    foo(function(str, num) {
      // str => string
      // num => number
    })
    
  • 特殊类型

    // 字面量类型:限制变量必须是某一个值
    const a: 'foo' = 'foo'   // 赋值时必须为那个值
    const b: 'success' | 'warning' | 'danger' = 'success'   // | (或运算符)可使用
    const c: string | number = 'str'   // 类型为string或者number
    // 通过type关键词声明类型的别名
    type StringOrNumber = string | number
    const d: StringOrNumber = 'string'   // 效果同上
    // maybe类型 ? 
    const e: ?number = undefined   // 不会报错
    const f: number | null | void = undefined    // 效果同上
    
  • 任意类型(Mixed & Any)

    // mixed === string | number | boolean ...
    function passMixed(value: mixed) {
      
    }
    // any === .....
    function paddAny(value: any) {
      
    }
    // 二者之间的差异为mixed类型仍然为强类型(仍然符合类型安全原则),any却为弱类型
    // 例
    function passMixed(value: mixed) {
      // 这种用法在语法上会报错
      // value.substr(1)
      // value * value
      // 正确用法为判断类型之后执行相应的逻辑
      if(typeof value === 'string') {
        value.substr(1)
      }
      if(typeof value === 'number') {
        value * value
      }
    }
    function paddAny(value: any) {
      // 这种用法在语法上不会报错
      value.substr(1)
      value * value
    }
    
  • 具体类型问题请查看官方文档

    // 官方文档:https://flow.org/en/docs/types/
    // 第三方类型手册:https://www.saltycrane.com/cheat-sheets/flow-type/latest/
    
  • Flow运行环境API(内置对象)

    // 对原装API的类型限制
    // 例
    const element: HTMLElement | null = document.getElementById('app') // 参数中传入其他类型会报错,返回值为一个HTML元素或者null
    // 具体查看一下链接中的API
    // https://github.com/facebook/flow/blob/master/lib/core.js
    // https://github.com/facebook/flow/blob/master/lib/dom.js
    // https://github.com/facebook/flow/blob/master/lib/bom.js
    // https://github.com/facebook/flow/blob/master/lib/cssom.js
    // https://github.com/facebook/flow/blob/master/lib/node.js
    

TypeScript: 基于JavaScript,是JavaScript类型的超集

在JavaScript的基础之上多了一些拓展特性

  1. 强大的类型系统
  2. 对ES6+ 性特性的支持

最终都会被编译为JavaScript代码,任何一种JavaScript运行环境都支持

相对于Flow的优势:

  1. 功能更为强大
  2. 生态更加健全、更完善

相对于JavaScript的缺点:

  1. 语言本身多了很多概念(接口/泛型/枚举)
  2. 对于周期较短的小型项目来说,TypeScript会增加一些成本(编写很多的类型声明)

配置文件:yarn tsc --init

Typescript 原始类型

!在非严格模式下,string | number | boolean 三种类型是允许为空的,可被赋值为null | undefined

  • string

    const a: string = 'foo'
    
  • number

    const b: number = 100  // NaN Infinity
    
  • boolean

    const c: boolean = true  // false
    
  • void

    // 常用于标记函数值返回为空,只能为 null | undefined
    // 在严格模式下只能是 undefined
    const d: void = null  //  undefined
    
  • null

    const e: null = null
    
  • undefined

    const f: undefined = undefined
    
  • symbol

    const g: symbol = Symbol()    // target: 'es5' 配置时会报错,es5并没有symbol类型
    // 对于这种报错解决办法有两种:1. 修改 target 配置  2. 在配置文件中的 lib 标准库声明中添加“ES2015”
    
中文错误信息

默认跟随系统或者编辑器的语言类型

在执行编译时添加参数:–local zh-CN

并不建议修改为中文

作用域问题
其他类型
  • Object类型

    Typescript 中的 Object 类型并不单指对象类型,还包括了函数和数组类型

    const foo: object = function () {}    // []  |  {}
    
    // 如果只想让变量只为对象类型就得使用字面量的语法:
    const foo: { foo: number } = { foo: 100 }
    
  • 数组类型

    数组类型的定义和Flow一样

    // 定义全为数字的数组
    const a: Array<number> = [1, 2, 3]
    const b: number[] = [4, 5, 6]
    // 列举强类型的好处(函数中操作必须的元素必须全为数字)
    function sum(...args: number[]) {
      return args.reduce((prev, current) => prev + current, 0)
    }
    // 数组中有数字有字符串
    const arr: (number | string)[] = [1, "string", 2]
    // 数组中对象类型的定义
    const persons: { name: string, age: Number }[] = [
      { name: "刘英", age: 18 },
      { name: "谢大脚", age: 28 },
    ];
    
  • 元组类型

    确定了元素数量和元素类型的数组

    const tuple: [number, string] = [100, 'foo']
    
  • 枚举类型

    在 JavaScript 中,我们常用一个对象来模拟枚举类型

    在 Typescript 中,我们使用 enum 关键字来声明一个枚举类型

    枚举类型会在编译时被编译成一个键值对对象

    // 在声明时可以不用 = 进行赋值,默认从 0 开始累加 如果第一个已经声明了具体数值,后面的会在此基础上进行累加
    enum PostStatus {
      a = 0,
      b = 1,
      c = 2
    }
    //枚举类型并不都是数字,也有字符串枚举(字符串枚举不能自动进行累加,需要在声明时就把值定好)
    enum StringEnum {
      a = 'a',
      b = 'b'
    }
    // 可以通过索引来访问枚举对象  PostStatus[0]
    // 在使用时和JavaScript一样
    const obj = {
      aa: 'aa',
      bb: PostStatus.c
    }
    
  • 函数类型

    对函数的输入输出进行类型约束

    // 对形参进行类型约束,可选的形参用 ? 进行标识,剩余参数可用 ...rest操作符进行展开
    function fn(a: number, b?: string, ...rest: number[]): string {
      return 'aa'
    }
    // 对函数表达式的类型约束
    const fn2 = (a: number, b: string) => string => function(a: number, b: string): string {
      return 'aa'
    }
    // 对于一个无返回值的函数,我们可以使用 void 来标记
    function fn1({ one, two }: { one: number, two: number }): void {
      console.log(one, two)
    }
    // 而对于一个永远也执行不完的函数,我们可以使用 never 来标记
    function fn2(): never {
      throw new Error()
      console.log('hello')
    }
    function fn3(): never {
      while(true) {}
      console.log('hello')
    }
    
  • 任意类型

    任意类型(弱类型,原本的JavaScript就是使用任意类型的语言)

    any 类型:any 类型也是一种动态类型,Typescript 不会对 any 执行类型检查(在 any 的基础上调用任意方法时语法不会报错)

    function stringify(value: any) {
      return JSON.stringify(value)
    }
    stringify('bar')
    stringify(100)
    let foo: any = 100
    foo.bar()   // 语法上是不会报错的
    
  • 隐式类型推断

    我们没有使用类型注解的方式标注某一变量的类型时,在赋值时自动推断该变量的类型

    对于无法推断的变量类型(只声明不赋值的变量),将会把该变量标记为any类型

    let age = 18 // 推断为number类型  等于 let age: number = 18
    age = 'a'   // 此时语法将会报错
    
    let foo    // 只声明不赋值的变量默认标记为any类型
    
  • 类型断言

    Typescript 在无法推断某个变量的具体类型时,我们需要进行类型断言,让 Typescript 知道这个变量的具体类型,断言其实就是明确告诉 Typescript 某个变量确定为某个类型

    // 假设 arr 的来源已确定
    const arr = [100, 200, 300]
    const a = arr.find(i => i > 0)   // 我们知道此时 a 的值必定是一个数字,但是 Typescript 并不知道,它认为你的值除了是 number 类型之外还可能是 undefined 
    const b = a * a    // 没有进行断言的时候语法将会报错
    // 断言的使用
    const num1 = a as number
    const num2 = <number>a   // react JSX中不可用
    
  • 接口(interface)

    接口就是一种类型规范,我们在使用一个接口的时候就必须遵循该接口的规则(确定接口成员和成员的数据类型)

    接口只是用来对我们实际使用中的数据进行类型约束的,并没有实际的使用意义(查看编译过后的 JavaScript 代码就能看出)

    interface Post {
      title: string;   // 使用分号分隔(也可以不加,和平时写JavaScript代码一样)
      content: string
      id: number
    }
    function printPost(post: Post) {
      console.log(post.title)
      console.log(post.content)
    }
    printPost({
      title: 'title',
      content: 'content',
      id: 1
    })
    
    • 可选成员

      // 接上述例子
      interface Post {
        title: string;   // 使用分号分隔(也可以不加,和平时写JavaScript代码一样)
        content: string
        id: number
        subtitle?: string   // 可选成员 解析类型为 string | undefined
      }
      
    • 只读成员

      // 接上述例子
      interface Post {
        title: string;   // 使用分号分隔(也可以不加,和平时写JavaScript代码一样)
        content: string
        id: number
        subtitle?: string   // 可选成员 解析类型为 string | undefined
        readonly summary: string   // 只读成员
      }
      const b: Post = {
        title: 'title',
        content: 'content',
        id: 1,
        summary: 'summary'
      }
      b.summary = 'string'  // summary为只读成员,再进行更改时将会报错
      
    • 动态成员(例子:缓存的键值对是动态的)

      interface Cache {
        [prop: string]: string   // 动态成员,key | value 的类型都为string
      }
      
  • 类(class)

    描述一类具体事物的抽象特征

    ES6 以前都是用函数 + 原型来模拟实现类

    在 typescript 中增强了 class 的相关语法

    class Person {
      name: string  // 值可以在此进行初始化,或者在构造函数中进行赋值(二者必须取其一,要不然就会报错)
      age: number
      constructor (name: string, age: number) {
        this.name = name
        this.age = age
      }
      
      sayHi(msg: string): void {
        
      }
    }
    
    • 类中的访问修饰符

      // 接上述代码
      class Person {
        public name: string  // 公有属性,类内部属性就是默认为公有属性
        private age: number   // 私有属性,只能在类中进行访问
        protected gender: boolean
        constructor (name: string, age: number) {
          this.name = name
          this.age = age
          this.gender = true
        }
        
        sayHi(msg: string): void {
          console.log(msg)
        }
      }
      
      class Student extends Person {
        // 对于构造函数也可以使用 private 修饰符,此时该类不能被继承和直接实例化使用,也不能调用实例方法,此时我们可以使用静态方法进行解决
        // 使用 protected 是也是不能直接实例化使用,但是可以被继承
        //private constructor(name: string, age: number) {
        //  super(name, age)
        //}
        //static create(name: string, age: number) {
        //  return new Student(name, age)
        //}
        
        // 在外部调用静态方法进行实例化
        // const a = Student.create('a', 18)
        
        constructor(name: string, age: number) {
          super(name, age)
          // protected 表示只允许在此类对象中访问,子类也归属于父类
          console.log(this.gender)   // 此时能访问到
        }
      }
      
      const ming = new Person('ming', 18)
      console.log(ming.name)
      //console.log(ming.age)   // 此时将会报错,使用了在实例中使用了类中的私有属性
      //console.log(ming.gender)  // 此时也报错,对于用 protected 进行修饰的成员对象也不能在类的外部访问
      
    • 类的只读属性

      class Person {
        public name: string 
        private age: number
        protected readonly gender: boolean   // 此时的 gender 只读,只能在此处或者构造函数中进行初始化并且只能初始化一次,再次进行改变时将会报错
        constructor (name: string, age: number) {
          this.name = name
          this.age = age
        }
        
        sayHi(msg: string): void {
          
        }
      }
      
    • 类与接口

      // 使用接口来约束类中的方法属性
      interface Run {
        run(food: string): void
      }
      interface Eat {
        eat(distance: number): void
      }
      class Person imolements Eat, Run {   // 使用接口来约束类的方法
        eat(food: string): void {
          console.log('人吃东西')
        }
        run(distance: number): void {
          console.log('人在跑')
        }
      }
      class Animal imolements Eat, Run {
        eat(food: string) {
          console.log('动物吃东西')
        }
        run(distance: number) {
          console.log('动物在跑')
        }
      }
      
    • 抽象类

      // 抽象类和接口类似,都是用来约束类的实现
      // 不同的是使用抽象类之后能被继承,但是无法使用 new 关键字来创建实例对象
      abstract class Animal {
        eat(food: string): void {
          console.log('动物在吃东西')
        }
        // 定义抽象方法
        abstract run(distance: number): void    // 不需要有具体的方法函数,但是在子类中需要有具体的方法函数来进行调用
      }
      class Dog extends Animal {
        run(distance: number): void {
          console.log('动物在跑')
        }
      }
      const dog = new Dog()
      dog.eat('狗粮')
      dog.run(100)
      
    • 泛型

      泛型就是我们在定义类或者接口的时候没有指定具体的类型,等到我们在使用的时候在进行指定具体类型的特征

      // 创建指定长度的数组,内部元素固定了数据类型(不使用泛型时不够灵活)
      function createNumberArray(length: number, value: number): number[] {
        const arr = Array(length).fill(value)
        return arr
      }
      function createStringArray(length: number, value: number): string[] {
        const arr = Array(length).fill(value)
        return arr
      }
      const res = createNumberArray(3, 100)   // res => [100, 100, 100]
      const res1 = createStringArray(3, 'string')  // res1 => ['string', 'string', 'string']
      // 使用泛型来进行定义
      function createArray<T>(length: number, value: T): T[] {
        const arr = Array<T>(length).fill(value)
        return arr
      }
      const res2 = createArray<string>(3, 'string')   // res2 => ['string', 'string', 'string']
      const res3 = createArray<number>(3, 100)   // res3 => [100, 100, 100]
      
    • 类型声明

      我们在开发过程中会使用各种各样的模块包,这些模块包并不全是使用 Typescript 来进行开发的,在我们使用的时候需要进行单独的类型声明(现在大多数常用的包都有了自己的类型声明模块,如果没有的就需要我们使用 declare 关键词来进行类型声明)

      // 以使用 lodash 为例
      import { camelCase } from 'loadsh'
      declare function camelCase(input: string): string
      const res = camelCase('hello world')
      

JavaScript的性能优化、了解V8引擎

JavaScript中的垃圾回收(内存管理)
  • JavaScript中的垃圾:
    • JavaScript中的内存管理是自动的
    • 对象不再被引用时就是垃圾,需要被回收
    • 对象不能从根上访问到时就是垃圾,也需要被回收
  • JavaScript中的可达对象:
    • 可以访问到的对象就是可达对象(对象引用/作用域链)
    • 可达的标准就是从根触发是否能够被找到
    • JavaScript中的根就是全局变量对象
JavaScript中的垃圾回收算法(GC算法)
  • GC的定义与作用
    • GC就是垃圾回收机制的简写
    • GC可以找到内存中的垃圾、并释放和回收空间
  • GC中的垃圾是什么
    • 程序中不再需要使用的对象
    • 程序中不能再访问到的对象
  • GC算法是什么
    • GC是一种机制,垃圾回收期完成具体的工作
    • 工作的内容就是查找垃圾释放空间、回收空间
    • 算法就是工作时查找和回收所遵循的规则
  • 常见的GC算法
    • 引用计数法
    • 标记清除法
    • 标记整理法
    • 分代回收法
引用计数法的实现原理
  • 核心思想:设置引用数,判断当前引用数是否为0
  • 引用计数器
  • 引用关系改变时修改引用数字
  • 引用数字为0时立即回收
引用计数法的优缺点
  • 优点
    • 发现垃圾时立即回收
    • 减少程序卡顿时间
  • 缺点
    • 无法回收循环引用的对象
    • 资源开销大
标记清除法的实现原理
  • 核心思想:分为标记和清除两个阶段完成
  • 遍历所有对象对活动对象进行标记
  • 遍历所有对象对没有标记的对象进行清除
  • 回收相应的空间
标记清除法的优缺点
  • 优点
    • 对循环引用的对象能进行标记清除
  • 缺点
    • 空间碎片化,内存地址不连续
    • 不会立即回收垃圾对象
标记整理法的实现原理
  • 编辑整理可以看做是标记清除的增强
  • 标记阶段的操作和标记清除一致
  • 清除阶段会先执行空间整理,移动对象位置
  • 配合标记清除能有效执行垃圾回收操作
标记整理法的优缺点
  • 优点
    • 减少碎片化的内存空间
  • 缺点
    • 也不会立即回收垃圾对象
认识V8引擎
  • V8是一款主流的JavaScript执行引擎
  • V8采用即时编译
  • V8内存设置上限(64位操作系统下不超过1.5G,32位操作系统下不超过800M)
V8垃圾回收策略
  • 采用分代回收的思想
  • 内存分为新生代和老生代
  • 针对不同对象执行不同的垃圾回收算法
V8中用到的GC算法
  • 分代回收
  • 空间复制
  • 标记清除
  • 标记整理
  • 标记增量
V8内存分配
  • V8内存空间一分为二(新生代和老生代)
  • 小空间用于存放新生代对象(64位操作系统下为32M,32位操作系统下为16M)
  • 新生代指的是存活时间较短的对象
  • 大空间用于存放老生代对象(64位操作系统下为1.4G,32位操作系统下为700M)
  • 老生代对象像指的是存活时间较长的对象
新生代对象的回收
  • 回收过程采用复制算法 + 标记整理
  • 新生代内存分为两个等大小的空间
  • 使用空间为 From 空间空间为 To
  • 活动对象存储在 From 空间中
  • 标记整理后将活动对象拷贝至 To
  • From 与 To 交换空间,释放 From 中的内存
回收细节说明
  • 拷贝过程中可能出现晋升
  • 晋升就是将新生代对象移动到老生代中
  • 一轮GC之后还存活的新生代对象需要晋升
  • To 空间的使用率超过了 25%,将空间内的对象移动到老生代空间中
老生代对象的回收
  • 主要采用标记清除、标记整理、增量标记算法
  • 首先使用标记清除完成垃圾空间的回收
  • 采用标记整理进行空间优化
  • 采用增量标记进行效率优化
新老生代回收细节对比
  • 新生代垃圾回收使用空间换时间
  • 老生代垃圾回收不适合复制算法
  • 标记增量怎么优化垃圾回收:
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值