面向对象和面向过程
面向过程,考虑的是需求要拆解多少个步骤来实现;
面向对象,考虑的是这个需求里面有多少个对象,他们有哪些属性是必须的,行为之间的关系是如何的;
javascript 准确的说是基于对象(object- based)的语言
类与对象
类是对象的模版,定义了同一组对象共有的属性和方法,类可以有父类和子类。而对象就是对类进行实例化之后的结果,换句话说对象就是我们new出来的的实例。
构造函数内如果定义方法的话,最大的问题就是每次都会执行new Function的操作
例如:this.showName = function(alb){return a+b} 就等于 this. showName = new Function(a, b, ‘return a+b’)
正确的做法是应该把方法定义在原型(prototype)上。
静态属性和静态方法使用场景
比如定一个instanceCount的静态变量
再定一个getInstanceCount的静态方法
在constructor 中行进this.instanceCount ++
这样每次类被实例化的时候instanceCount的值就会+1,通过getInstanceCount则可以获得当前类已经被实例化了多少次
ES5 继承的几种方式
只继承父类的属性(构造函数继承),在子类的构造函数中调用父类的call方法把this传过去,例如
function Dog(name, color){ Animal.call(this, name) }
如果要继承父类的方法需要把子类的原型指向父类实例,并且把原型的构造函数指回来,这种方式叫原型继承,例如:
Dog.prototype = new Animal() console.log(Dog.prototype.constructor) // function Animal() {this.name = 'animal'} Dog.prototype.constructor = Dog // function Dog(name, color) {Animal.call(this, name)}
如果将上面的两种方法组合起来使用,则可以同时继承属性和方法,所以叫组合继承
ES6的继承
es6 使用extends继承class
在子类的constructor中使用super(name)的方式继承父类的属性,类似es5的构造函数继承
es6中定义顶层的属性的方式使用get语法 ,例如
get sex(){ return ‘male’ } let p1 = new Person(‘xx’)后
通过p1.sex获得
这种顶层属性不能被外部直接修改,比如p1.sex=111会报错
如果要改变sex的值,则需要使用set语法
优点是可以在属性设置和获取的时候做前置的拦截操作,例如:
constructor(){ this._sex = -1 } get sex(){ if(this._sex ===1) return ‘male’ if(this._sex === 0) return ‘female’ } // sex的值仅接受0和1 set sex(val){ if(val ===0 || val ===1) this._sex = val }
class只能用static定义的静态方法,不能定义属性,父类的静态方法可以被子类调用,但是不可以在实例下调用。
如何非要在类上面定义静态属性,可以直接写Person.count = 5 但是不推荐
Symbol
Symbol不是对象,不能被new,也不能添加属性和方法。
let s1 = Symbol(‘foo’) // 参数可以添加描述,可以调用s1.description来输出描述 let s2 = Symbol(obj) // 如果参数是对象则会自动调用对象下的toString(),可以可以手动在对象下面自定义一个toString方法让它自动被调用
如果使用Symbol.for定义的Symbol则可以相等。
let s1 = Symbol.for(‘foo’) 在全局定义个key为foo的Symbol,可以使用Symbol.keyfor(s1) 打印Symbol全局的key值
使用场景一,解决对象的key重名后覆盖问题
const stu1 = Symbol(‘tom’) const stu2 = Symbol(‘tom’) const data = { [stu1]: {address:’xxx’}, [stu2]: {address:’xxx’}, }
场景二,使用Symbol来模拟私有属性
const s1 = Symbol(‘a’) constructor(){ this[s1] = 111 } for in ,Obejct.keys(),Object.getOwnPropertySymbols都不能遍历Symbol属性 只有 用for(let key of Reflect.ownKeys(user)) 可以 场景三,魔术字符串 const shapeType = { triangle: Symbol(), // 无需给定具体的字符串,比如:’triangle’ circle: Symbol() } switch (shape){ case shapeType.triangle: case shapeType.circle: … }
SET
基本语法
let s = new Set([1,1,2]) // [1,2] s.add(5).delete(2).clear().size s.has(1) // false
set 可以被forEach和for of遍历
求数组的交集
let s1 = new Set(arr1) let s2 = new Set(arr2) let result = new Set(s1.filtter(item => s2.has(item)))
weakSet // 只能添加对象
let ws = new weakSet() ws.add(1) // 报错
weakSet不能使用forEach
弱引用,不会被垃圾回收机计数,当引用的对象消失后weakSet也会消失
map
map的key 跟对象不同,可以传对象和数组
常用的方法
set
get
has
size
delete
创建map的基本语法
new Map([‘name’,’tom’],[‘age’,12])
使用forEach for of 进行遍历
map优势
size has 方法更便捷性能更好 key不会跟原型的属性冲突
weakMap 的key只支持引用类型 不能遍历也没有size方法
应用场景
let el = document.get elements by tag name() let els = new map() els.set(el,’desc’)
用map存dom元素用key来遍历管理很方便
由于弱引用的机制 还能防治内存泄露
字符串
es5的unicode 取值范围是0000-ffff
es6 使用/u{}的方式增加了取值范围
\hhh 8进制字符
\x12 16进制字符
for of 遍历字符串
模版字符串可以直接换行 无需写+和\n
模版字符还支持函数方式,例如foo’aaa${123}bbb’
es6新增的方法
includes
startsWith
endsWith
repeat(7)
正则表达式
es5中正则中的修饰符
i 忽略大小写
m 多行
g 全局
es6新增两个修饰符
y 粘连修饰符
匹配失败后会返回到起始位置
u unicode 修饰符
数值
b0和 0o来表示十进制和8进制
5/0 会得到无穷大 infinity
‘a’ / 5 会得到 NaN not a number
Number.isFinite() 传入 字符串或布尔时也会返回false
Number.isNaN 注意此方法通常判断值是否是一个数字
es6中会把一些全局的方法放到对应的模块中,例如parseInt parseFloat
Number.isInteger() 是否是整数
Number.trunc 取整
Math.sign 判断是正数还是0还是负数
Math.cbrt 立方根
代理 Proxy
es5中对对象进行拦截使用 Object.defineProperty()
使用Proxy拦截对象,实现私有属性(下划线开头)禁止访问
let user = { name:’xxx’, age: 18, _gender: ‘male’ } user = new Proxy(user,{ get(target, key) { if(key.startsWith(‘_’){ throw new Error(‘不可访问’) } else { return target[key] } }, set(target, key, val) { if(key.startsWith(‘_’){ throw new Error(‘不可访问’) } else { return target[key] = val } } has (target, key) { if(key[0] === "_") { // key.startsWith(‘_’) return false; } return key in target; } deleteProperty(key){ if(key.startsWith(‘_’){ throw new Error(‘不可删除’) } else { delete target[key] return ture } } ownKeys(target){ return Object.keys(target).filter(key => !key.startsWith(‘_’)) } }) 使用Proxy拦截函数调用 let sum = (…args) =>{ let num = 0 args.forEach(item => { num += item }) return num } sum = new Proxy(sum, { // 当前函数,上下文this,参数 apply(target, ctx, agrs) { return target(…args) * 2 } }) // 使用Proxy拦截类的构造函数 let User = class { constructor (name){ this.name = name } } User = new Proxy(User, { // 当前类,参数,目标类 constructor(target, args, newTarget){ return new target(…args) } })
反射 Reflect
Reflect 设计的目的
1. 将Object属于语言内部的方法放到Reflect身上
2. 修改某些Object方法的返回结果
3. 让Object操作变成函数行为
4. Reflect对象和Proxy的对象方法一一对应