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 遍历对象
for..in
无法遍历到Symbol
属性
for(let key in user) {
console.log(key);
}
for..of
+Object.keys()
无法遍历到Symbol
属性
for(let key of Object.keys(user)) {
console.log(key);
}
for..of
+Object.getOwnPropertySymbols()
只能遍历到Symbol
属性
for(let key of Object.getOwnPropertySymbols(user)) {
console.log(key);
}
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 遍历拦截
- 对当前对象的遍历方法进行拦截操作
Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
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