③ ES6 新特性

1 面向过程与面向对象

1.1 冰箱装大象

  • js是一种基于对象(object-based)的语言

1.2 类与对象

  • 类是对象的模板,定义了同一组对象共有的属性和方法

2 ES5中的类与继承

2.1 类

1. 如何定义类

  • 属性定义在类上

  • 方法定义在原型上

function People(name, age) {
  this.name = name
  this.age = age
}

People.prototype.showName = function() {
  console.log('我的名字是' + this.name);
}

let p1 = new People('zhouzhou', 12)
console.log(p1);
p1.showName()

let p2 = new People('zhangsan', 18)
console.log(p2);
p2.showName()

2. 如何定义实例属性 实例方法 静态属性 静态方法

  • 静态属性 静态方法:定义在类上

  • 实例属性:定义在构造函数上

  • 实例方法:定义在类的原型对象上

function People(name, age) {
  console.log('this', this); // 指向实例对象
  // 实例属性:定义在构造函数上
  this.name = name
  this.age = age
  People.count++
}
// 静态属性:定义在类上
People.count = 0
// 静态方法
People.getCount = function() {
  console.log('People.count--this', this); // 指向当前构造函数
  console.log('当前共有' + People.count + '个人');
}

// 实例方法
People.prototype.showName = function() {
  console.log('我的名字是' + this.name);
}

let p1 = new People('zhouzhou', 12)
console.log(p1);
p1.showName()

let p2 = new People('zhangsan', 18)
console.log(p2);
p2.showName()

console.log(People.count);
People.getCount()

// 静态方法
Math.max(4, 5)
Math.random()

2.2 继承

// 父类
function Animal(name) {
  this.name = name
}
Animal.prototype.showName = function() {
  console.log('名字是:' + this.name);
}

1. 构造函数继承

继承属性

// 子类
function Dog(name, color) {
  Animal.call(this, name) // 继承属性
  this.color = color
}

let d1 = new Dog('fulai', 'white')
console.log(d1);
d1.showName()

2. 原型继承

继承方法

// 子类
function Dog(name, color) {
  this.color = color
}
// 继承方法
Dog.prototype = new Animal()
Dog.prototype.constructor = Dog

let d1 = new Dog('fulai', 'white')
console.log(d1);
d1.showName()

3. 组合继承

  • 继承属性 -- 构造函数继承

  • 继承方法 -- 原型继承

// 子类
function Dog(name, color) {
  Animal.call(this, name) // 继承属性 -- 构造函数继承
  this.color = color
}
// 继承方法 -- 原型继承
Dog.prototype = new Animal()
Dog.prototype.constructor = Dog

let d1 = new Dog('fulai', 'white')
console.log(d1);
d1.showName()

3 ES6中的类与继承

3.1 定义类

1. class -- 语法糖

class People {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  showName() {
    console.log(this.name);
  }
}
let p1 = new People('zhouzhou', 12)
console.log(p1);

2. 定义最顶层属性

  • 通过 get set 定义最顶层属性
class People {
  constructor(name, age) {
    this.name = name
    this.age = age
    this._sex = -1
  }

  get sex() {
    return this._sex
  }
  set sex(val) {
    // this.sex = val // 死循环
    this._sex = val
  }
  showName() {
    console.log(this.name);
  }
}
let p1 = new People('zhouzhou', 12)

p1.sex = 1
console.log(p1.sex);
为什么定义顶层属性:解决业务需求操作(可以对属性的读写做拦截操作)
class People {
  constructor(name, age) {
    this.name = name
    this.age = age
    this._sex = -1
  }

  get sex() {
    if(this._sex === 1) {
      return 'male'
    } else if(this._sex === 0) {
      return 'female'
    } else {
      return 'error'
    }
  }
  set sex(val) { // 1 male 0 female
    if(val === 0 || val === 1) {
      this._sex = val
    }
  }
  showName() {
    console.log(this.name);
  }
}
let p1 = new People('zhouzhou', 12)

// p1.sex = 1
// p1.sex = 0

console.log(p1.sex);

3. 定义静态方法

es6明确规定,class 中没有静态属性

因为 typeof 类function, 所以可以像 es5 一样定义静态属性

  • static 定义静态方法

  • 静态方法能被子类继承,但不能在实例上调用

class People {
  constructor(name, age) {
    this.name = name
    this.age = age
    this._sex = -1
  }
  // 静态方法
  static getCount() {
    return 5
  }
}
// 静态属性
People.count = 9
console.log(People.count); // 9
console.log(typeof People); // function
console.log(People.getCount()); // 5

class Coder extends People {
  constructor(name, age, company) {
    super(name, age) // 继承父类属性
    this.company = company
  }
}
console.log(Coder.getCount());

3.2 继承

  • extends 实现继承

  • super 继承超类属性

class People {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  showName() {
    console.log(this.name);
  }
}
let p1 = new People('zhouzhou', 12)
console.log(p1);

class Coder extends People {
  constructor(name, age, company) {
    super(name, age) // 继承父类属性
    this.company = company
  }
  showCompany() {
    console.log(this.company);
  }
}
let c1 = new Coder('zhangsan', 12, 'imooc')
console.log(c1);
c1.showName()

4 新的原始数据类型 Symbol

  • 一种新的原始数据类型

4.1 声明方式

1. 直接声明

let s1 = Symbol()
let s2 = Symbol()
console.log(s1);
console.log(s2);
console.log(s1 === s2); // false

2. 字符串作为参数 -- 作为描述 description

let s1 = Symbol('foo')
let s2 = Symbol('foo')
console.log(s1);
console.log(s2);
console.log(s1 === s2); // false

3. 对象作为参数 -- 作为描述 description

const obj = {
  name: 'imooc',
  toString() {
    return this.name
  }
}
let s = Symbol(obj)
console.log(s);

4. 通过 for() 声明

let s1 = Symbol.for('foo')
let s2 = Symbol.for('foo')
console.log(s1 === s2); // true
  • 通过 for() 声明的变量是全局环境下的
function foo() {
  return Symbol.for('foo')
}
const x = foo()
const y = Symbol.for('foo')
console.log(x === y);
  • Symbol.keyFor() 检测当前变量是否全局登记过
const s1 = Symbol('foo')
console.log(Symbol.keyFor(s1)); // undefined

const s2 = Symbol.for('foo')
console.log(Symbol.keyFor(s2)); // foo

4.2 应用场景

1. 避免属性名覆盖

const stu1 = '李四'
const stu2 = '李四'
const grade = {
  [stu1]: { address: 'yyy', tel: '222' },
  [stu2]: { address: 'zzz', tel: '333' },
}
console.log(grade); // grade = { '李四': { address: 'zzz', tel: '333' } }
const stu1 = Symbol('李四')
const stu2 = Symbol('李四')
const grade = {
  [stu1]: { address: 'yyy', tel: '222' },
  [stu2]: { address: 'zzz', tel: '333' },
}
console.log(grade);

2. 消除魔术字符串

const shapeType = {
  triangle: Symbol(),
  circle: Symbol()
}
function getArea(shape) {
  let area = 0
  switch(shape) {
    case shapeType.triangle:
      area = 1
      break
    case shapeType.circle:
      area = 2
      break
  }
  return area
}
console.log(getArea(shapeType.triangle));
console.log(getArea(shapeType.circle));

4.3 遍历对象

  1. for..in 无法遍历到 Symbol 属性
for(let key in user) {
  console.log(key);
}
  1. for..of + Object.keys() 无法遍历到 Symbol 属性
for(let key of Object.keys(user)) {
  console.log(key);
}
  1. for..of + Object.getOwnPropertySymbols() 只能遍历到 Symbol 属性
for(let key of Object.getOwnPropertySymbols(user)) {
  console.log(key);
}
  1. for..of + Reflect.ownKeys() 能遍历到普通属性 + Symbol 属性
for(let key of Reflect.ownKeys(user)) {
  console.log(key);
}

5 新的数据结构 Set

5.1 定义及操作方法

  • set 成员值唯一
let s = new Set([1, 2, 3, 2])
console.log(s); // { 1, 2, 3}
s.add('imooc').add('es')
s.delete(2)
// s.clear()
console.log(s.has('imooc')); // true
console.log(s.has('imooc1')); // false
console.log(s.size); // 4

5.2 遍历

s.forEach(item => console.log(item))

for(let item of s) {
  console.log(item);
}

for(let item of s.keys()) {
  console.log(item);
}

for(let item of s.values()) {
  console.log(item);
}

// key跟value同名
for(let item of s.entries()) {
  console.log(item, item[0], item[1]);
}

5.3 应用场景

1. 数组去重

let arr = [1, 2, 3, 4, 2, 3]
let s = [...new Set(arr)]

2. 合并去重

let arr1 = [1, 2, 3, 4]
let arr2 = [2, 3, 4, 5, 6]
// let s = [...new Set([...arr1, ...arr2])]
let s = Array.from(new Set([...arr1, ...arr2]))

3. 交集

let arr1 = [1, 2, 3, 4]
let arr2 = [2, 3, 4, 5, 6]
let s1 = new Set(arr1)
let s2 = new Set(arr2)
let res = [...new Set(arr1.filter(item => s2.has(item)))]

4. 差集

let arr1 = [1, 2, 3, 4]
let arr2 = [2, 3, 4, 5, 6]
let s1 = new Set(arr1)
let s2 = new Set(arr2)
let arr3 = new Set(arr1.filter(item => !s2.has(item)))
let arr4 = new Set(arr2.filter(item => !s1.has(item)))

5.4 WeakSet

  • WeakSet 只能添加对象

1. 定义及操作方法

let ws = new WeakSet()
const obj1 = { name: 'imooc' }
const obj2 = { age: 5 }
ws.add(obj1)
ws.add(obj2)
ws.delete(obj1)
console.log(ws); // WeakSet {{age: 5}}
console.log(ws.has(obj2)); // true

2. 不能遍历

3. 垃圾回收机制(GC)

垃圾回收机制 GC +1 +1

  • WeakSet 弱引用,对 WeakSet 的引用不会被记录到垃圾回收机制

6 新的数据结构 Map

6.1 定义及操作

let m = new Map()
let obj = {
  name: 'imooc'
}
m.set(obj, 'es')
console.log(m); // Map {Object => "es"}
console.log(m.get(obj)); // true
m.delete(obj)
console.log(m); // Map size: 0

let map = new Map([
  ['name', 'imooc'],
  ['age', 5]
])
console.log(map); // Map(2) {'name' => 'imooc', 'age' => 5}
map.set('name', 'zhangsan')
console.log(map); // Map(2) {'name' => 'zhangsan', 'age' => 5}

6.2 遍历

map.forEach((value, key) => console.log(value, key))
for(let [key, value] of map) {
  console.log(key, value);
}
for(let key of map.keys()) {
  console.log(key);
}
for(let value of map.values()) {
  console.log(value);
}
for(let [key, value] of map.entries()) {
  console.log(key, value);
}

6.3 WeakMap

  • WeakMap 键名只能是引用数据类型

1. 定义及操作方法

let wm = new WeakMap()
wm.set([1], 2)
wm.set({ name: 'imooc' }, 'es')
console.log(wm);

2. 不支持 delete 和遍历

3. 垃圾回收机制(GC)

垃圾回收机制 GC +1 +1

  • WeakMap 弱引用,对 WeakMap 的引用不会被记录到垃圾回收机制

7 字符串的扩展

7.1 unicode

// unicode
// es6 \uxxxx 码点 0000~ffff
// \u20bb7 -> \u20bb+7
// \u{20bb7}
// \u{41} -> A

console.log('\z' === 'z'); // true
console.log('\172' === 'z'); // true
// \HHH (8进制数)
console.log('\172'.toString() === 'z'); // true
// \xHH (16进制数)
console.log('\x7A' === 'z'); // true
console.log('\u007A' === 'z'); // true
console.log('\u{7A}' === 'z'); // true

7.2 模板字符串

1. es5

const isLargeScreen = () => {
  return true
}
let class1 = 'icon'
class1 += isLargeScreen() ? ' icon-big' : ' icon-small'

2. es6

const isLargeScreen = () => {
  return true
}
const class2 = `icon icon-${isLargeScreen() ? 'big' : 'small'}`

7.3 带标签的模板字符串

const foo = (a,b,c,d) => {
  // raw:原始字符串
  console.log(a); // ['这是', ',他的年龄是', '岁', raw: Array(3)]
  console.log(b); // zhouzou
  console.log(c); // 12
  console.log(d); // undefined
}
const name = 'zhouzou'
const age = 12
foo`这是${name},他的年龄是${age}岁`

7.4 String.fromCodePoint(unicode)

  • 增加码点范围
String.fromCharCode(0x20bb7) // es5 ஷ fromCharCode只能识别6位数
String.fromCodePoint(0x20bb7) // es6 𠮷

7.5 常见方法

const str = 'imooc'
str.indexOf('mo'); // 1
str.includes('mo'); // true
str.startsWith('im'); // true
str.endsWith('mooc'); // true
const newStr = str.repeat(10)

8 正则表达式的扩展

  • es5:i(忽略大小写) m(多行匹配) g(全局匹配)

8.1 y 修饰符 -- 粘连修饰符

const str = 'aaa_aa_a'
const reg1 = /a+/g // 每次匹配剩余的
const reg2 = /a+/y // 剩余的第一个开始匹配

console.log(reg1.exec(str)); // ["aaa", ...]
console.log(reg2.exec(str)); // ["aaa", ...]

// 剩余的'_aa_a'匹配
console.log(reg1.exec(str)); // ["aa", ...]
console.log(reg2.exec(str)); // null

console.log(reg1.exec(str)); // ["a", ...]
console.log(reg2.exec(str)); // ["aaa", ...]

8.2 u 修饰符 -- unicode

  • \u0000~\uffff
const str = '\uD842\uDFB7' // 表示一个字符
console.log(/^\uD842/.test(str)); // true es5
console.log(/^\uD842/u.test(str)); // false es6

// . 除换行符外的任意单个字符
console.log(/^.$/.test(str)); // false
console.log(/^.$/u.test(str)); // true

console.log(/\u{61}/.test('a')); // false
console.log(/\u{61}/u.test('a')); // true

console.log(/𠮷{2}/.test('𠮷𠮷')); // false
console.log(/𠮷{2}/u.test('𠮷𠮷')); // true

9 数值的扩展

9.1 进制数转换

1. 十进制 -> 二进制

  • toString(2)
const a = 5
console.log(a.toString(2)); // 101

2. 二进制 -> 十进制

  • parseInt(num, 2)
const b = 101
console.log(parseInt(b, 2)); // 5

9.2 进制数前缀

  • 0B 二进制 0O 八进制
const a = 0B0101
console.log(a); // 5

const b = 0O777
console.log(b); // 511

9.3 Number.isFinite()

  • 只对数字进行判断,非数字的字符类型都是 false
console.log(Number.isFinite(5)); // true
console.log(Number.isFinite(0.5)); // true
console.log(Number.isFinite(Infinity)); // false
console.log(Number.isFinite('imooc')); // false
console.log(Number.isFinite(true)); // false

9.4 Number.isNaN()

// NaN: Not a Number
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN(15)); // false

9.5 Number.parseInt() Number.parseFloat()

console.log(Number.parseInt(5.5)); // 5
console.log(window.parseInt(5.5)); // 5
console.log(Number.parseFloat(5.5)); // 5.5
console.log(window.parseFloat(5.5)); // 5.5

9.6 Number.isInteger()

console.log(Number.isInteger(5)); // true
console.log(Number.isInteger(5.5)); // false

9.7 0.1 + 0.2 !== 0.3

  • 当前 0.1 转为二进制数被存储在电脑时,会有精度缺失(实际上存储的是近似值)

IEEE754 双精度标准 -- 存储数字的方法

  • 35 -> 00100011
  • 0.375 -> 0.011
  • 0.1 -> 0.000110011....
console.log(0.1000000000000001);
console.log(0.10000000000000001);
console.log(0.10000000000000001 === 0.1); // true

9.8 Math 新增方法

1. 数值的最大值 Number.MAX_SAFE_INTEGER 最小值 MIN_SAFE_INTEGER

  • Math.pow(2, 53)
  • Math.pow(-2, 53)
const max = Math.pow(2, 53)
console.log(max); // 9007199254740992
console.log(max+1); // 9007199254740992
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.MAX_SAFE_INTEGER === max-1);
console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991
console.log(Number.isSafeInteger(Number.MAX_SAFE_INTEGER+1)); // false

2. Math.trunc()

  • 去除小数部分保留整数
console.log(Math.trunc(5.5)); // 5 
console.log(Math.trunc(-5.5)); // -5 
console.log(Math.trunc(true)); // 1 
console.log(Math.trunc(false)); // 0 
console.log(Math.trunc(NaN)); // NaN
console.log(Math.trunc(undefined)); // NaN

console.log(Number.parseInt(5.5)); // 5
console.log(Number.parseInt(-5.5)); // -5
console.log(Number.parseInt(true)); // NaN

3. Math.sign()

  • 判断当前数值是正数 负数 还是0
console.log(Math.sign(5)); // 1
console.log(Math.sign(-5)); // -1
console.log(Math.sign(0)); // 0
console.log(Math.sign(NaN)); // NaN
console.log(Math.sign(true)); // 1
console.log(Math.sign(false)); // 0

4. Math.cbrt()

  • 计算一个数值的立方根
console.log(Math.cbrt(8)); // 2
console.log(Math.cbrt('imooc')); // NaN

10 代理 Proxy

10.1 实现代理

1. ES5

let obj = {}
let newVal = ''
Object.defineProperty(obj, 'name', {
  get() {
    return newVal
  },
  set(val) {
    newVal = val
  }
})
obj.name = 'es'
console.log(obj);

2. ES6

// ES6 Proxy
let obj = {}
let p = new Proxy(obj, {})
p.name = 'imooc'
console.log(p.name); // imooc
for(let key in obj) {
  console.log(key);
}

10.2 使用 Proxy 实现操作拦截

// _password 是私有属性 不允许访问和修改
let user = {
  name: 'zouzou',
  age: 12,
  _password: '***'
}

1. get 拦截对象属性的读取

user = new Proxy(user, {
  get(target, prop) {
    if(prop.startsWith('_')) {
      throw new Error('不可访问')
    } else {
      return target[prop]
    }
  }
})
console.log(user.age);
try {
  console.log(user._password);
} catch(e) {
  console.log(e.message);
}

2. set 拦截对象属性的设置

  • 返回一个布尔值
user = new Proxy(user, {
  get(target, prop) {
    if(prop.startsWith('_')) {
      throw new Error('不可访问')
    } else {
      return target[prop]
    }
  },
  set(target, prop, val) {
    if(prop.startsWith('_')) {
      throw new Error('不可访问')
    } else {
      target[prop] = val
      return true
    }
  }
})
user.age = 18
console.log(user.age);
try {
  user._password = 'xxx'
} catch(e) {
  console.log(e.message);
}

3. has 拦截 propKey in proxy 的操作

  • 返回一个布尔值
let range = {
  start: 1,
  end: 5
}
range = new Proxy(range, {
  has(target, prop) {
    return prop >= target.start && prop <= target.end
  }
})
console.log(2 in range);

4. deleteProperty 拦截对象属性的删除

  • 返回一个布尔值
user = new Proxy(user, {
  get(target, prop) {
    if(prop.startsWith('_')) {
      throw new Error('不可访问')
    } else {
      return target[prop]
    }
  },
  deleteProperty(target, prop) {
    // 拦截删除
    if(prop.startsWith('_')) {
      throw new Error('不可删除')
    } else {
      delete target[prop]
      return true
    }
  }
})
try {
  // delete user.age
  delete user._password
} catch(e) {
  console.log(e.message); 
}
console.log(user.age);

5. ownKeys 遍历拦截

  • 对当前对象的遍历方法进行拦截操作
  1. Object.getOwnPropertyNames()
  2. Object.getOwnPropertySymbols()
  3. Object.keys()
  4. for..in..
user = new Proxy(user, {
  ownKeys(target) {
    return Object.keys(target).filter(key => !key.startsWith('_'))
  }
})

for(let key in user) {
  console.log(key);
}

6. apply 函数调用 + call + apply 操作拦截

  • 拦截之后,修改函数的返回值
let sum = (...args) => {
  let num = 0
  args.forEach(item => {
    num += item
  })
  return num
}
sum = new Proxy(sum, {
  apply(target, ctx, args) {
    return target(...args) * 2
  }
})
console.log(sum(1, 2)); // 6
console.log(sum.call(null, 1, 2, 3)); // 12
console.log(sum.apply(null, [1, 2, 3])); // 12

7. construct 拦截 new 操作

  • 返回一个对象
let User = class {
  constructor(name) {
    this.name = name
  }
}
User = new Proxy(User, {
  construct(target, args, newTarget) {
    console.log('construct');
    return new target(...args)
  }
})
console.log(new User('imooc'))

11 反射 Reflect

  • Reflect 的目的

1. 将 Object 的属性转移到 Reflect 上

  • ES6 使得语法更加规范
let obj = {}
let newVal = ''
Reflect.defineProperty(obj, 'name', {
  get() {
    return newVal
  },
  set(val) {
    console.log('set');
    newVal = val
  }
})
obj.name = 'es'
console.log(obj.name);

2. 修改某些 Object 方法的返回结果,使其变得更合理

try {
  Object.defineProperty() // 无返回值
} catch(e) {

}
if(Reflect.defineProperty()) { // 返回值 boolean

} else {}

3. 让 Object 操作变成函数行为

  • 命令式操作 -> 函数式操作(更加友好)
console.log('assign' in Object); // true
console.log(Reflect.has(Object, 'assign')); // true

4. Reflect 对象的方法与 Proxy 对象的方法一一对应

let user = {
  name: 'zouzou',
  age: 12,
  _password: '***'
}
user = new Proxy(user, {
  get(target, prop) {
    if(prop.startsWith('_')) {
      throw new Error('不可访问')
    } else {
      // return target[prop]
      return Reflect.get(target, prop)
    }
  },
  set(target, prop, val) {
    if(prop.startsWith('_')) {
      throw new Error('不可访问')
    } else {
      // target[prop] = val
      Reflect.set(target, prop, val)
      return true
    }
  },
  deleteProperty(target, prop) {
    // 拦截删除
    if(prop.startsWith('_')) {
      throw new Error('不可删除')
    } else {
      // delete target[prop]
      Reflect.deleteProperty(target, prop)
      return true
    }
  },
  ownKeys(target) {
    // return Object.keys(target).filter(key => !key.startsWith('_'))
    return Reflect.ownKeys(target).filter(key => !key.startsWith('_'))
  }
})
console.log(user.age);
try {
  console.log(user._password);
} catch(e) {
  console.log(e.message);
}

user.age = 13
console.log(user.age);
try {
  user._password = 'xxx'
} catch(e) {
  console.log(e.message);
}

delete user.age
console.log(user.age);

for(let key in user) {
  console.log(key);
}
let sum = (...args) => {
  let num = 0
  args.forEach(item => {
    num += item
  })
  return num
}
sum = new Proxy(sum, {
  apply(target, ctx, args) {
    // return target(...args) * 2
    return Reflect.apply(target, target, [...args]) * 2
  }
})
console.log(sum(1, 2)); // 6
console.log(sum.call(null, 1, 2, 3)); // 12
console.log(sum.apply(null, [1, 2, 3])); // 12
posted on 2022-05-18 17:05  pleaseAnswer  阅读(15)  评论(0编辑  收藏  举报