对象
基础知识
对象是包括属性与方法的数据类型,JS中大部分类型都是对象如 String/Number/Math/RegExp/Date 等等。
传统的函数编程会有错中复杂的依赖很容易创造意大利式面条代码。
- 面向过程编程
const name = 'mkimq'
const grade = [
{
lesson: 'js',
score: 60
},
{
lesson: 'css',
score: 61
}
]
function average(grade, name) {
const total = grade.reduce((t, a) => t + a.score, 0)
return `${name}: ${total / grade.length}分`
}
console.log(average(grade, name))
- 面向对象编程
下面使用对象编程的代码结构清晰,也减少了函数的参数传递,也不用担心函数名的覆盖
const user = {
name: 'mkimq',
grade: [
{
lesson: 'js',
score: 60
},
{
lesson: 'css',
score: 61
}
],
average() {
const total = this.grade.reduce((t, a) => t + a.score, 0)
return `${this.name}: ${total / this.grade.length}分`
}
}
console.log(user.average())
OOP
- 对象是属性和方法的集合即封装
- 将复杂功能隐藏在内部,只开放给外部少量方法,更改对象内部的复杂逻辑不会对外部调用造成影响即抽象
- 继承是通过代码复用减少冗余代码
- 根据不同形态的对象产生不同结果即多态
基本声明
使用字面量形式声明对象是最简单的方式
const obj = {
name: 'mkimq',
get: function() {
return this.name
}
}
console.log(obj.get())
属性与方法简写
const name = 'mkimq'
const obj = {
name,
get() {
return this.name
}
}
console.log(obj.get())
其实字面量形式在系统内部也是使用构造函数 new Object创建的。
let mk = {}
let mk2 = new Object()
console.log(mk, mk2)
console.log(mk.constructor)
console.log(mk2.constructor)
操作属性
使用点语法获取
const user = {
name: 'mkimq'
}
user.name
使用[]获取
user['name']
可以看出使用.操作属性更简洁,[]主要用于通过变量定义属性的场景 如果属性名不是合法变量名就必须使用[]的形式了
对象和方法的属性可以动态的添加或删除。
const mk = {
name: 'mkimq'
}
mk.age = 18
mk.show = function() {
return `${this.name}已经${this.age}岁了`
}
delete mk.age
delete mk.show
对象方法
定义在对象中的函数我们称为方法。
引用特性
- 对象和函数、数组一样是引用类型,即复制只会复制引用地址。
- 对象做为函数参数使用时也不会产生完全赋值,内外共用一个对象
- 对多的比较是对内存地址的比较所以使用 == 或 === 一样
this
this 指当前对象的引用,始终建议在代码内部使用this 而不要使用对象名,不同对象的this只指向当前对象。
展开语法
使用...可以展示对象的结构
对象转换
基础知识
对象直接参与计算时,系统会根据计算的场景在 string/number/default 间转换。
- 如果声明需要字符串类型,调用顺序为 toString > valueOf
- 如果场景需要数值类型,调用顺序为 valueOf > toString
- 声明不确定时使用 default ,大部分对象的 default 会当数值使用
下面的数值对象会在数学运算时转换为 number
const m = new Number(1)
console.log(m + 3)
如果参数字符串运算时会转换为 string
const m = new Number(1)
console.log(m + '3') // '13'
下面当不确定转换声明时使用 default ,大部分default转换使用 number 转换。
const m = new Number(1)
console.log(m == '1')
Symbol.toPrimitive
内部自定义Symbol.toPrimitive方法用来处理所有的转换场景
const m = {
num: 1,
[Symbol.toPrimitive]: function() {
return this.num
}
}
console.log(m + 3)
valueOf/toString
可以自定义valueOf 与 toString 方法用来转换,转换并不限制返回类型。
const m = {
name: 'mkimq',
num: 1,
valueOf: function () {
console.log('valueOf')
return this.num
},
toString: function() {
console.log('toString')
return this.name
}
}
console.log(m + 3)
console.log(`${m}-mankeung`)
解构赋值
解构是一种更简洁的赋值特性,可以理解为分解一个数据的结构,在数组章节已经介绍过。
- 建设使用 var/let/const 声明
基本使用
下面是基本使用语法
const info = {
name: 'mkimq',
url: 'mkimq.com'
}
const {name, url: site} = info
console.log(name, site)
函数返回值直接解构到变量
function fn() {
return {
name: 'mkimq',
url: 'mkimq.com'
}
}
const {name, url} = fn()
函数传参
function fn({name, age}) {
console.log(name, age)
}
fn({name: 'mkimq', age: 18})
严格模式
非严格模式可以不使用声明指令,严格模式下必须使用声明。所以建议使用 let 等声明。
({name, url} = {name: 'mkimq', url: 'mkimq.com'})
还是建议使用let等赋值声明
"use strict";
const {name, url} = {name: 'mkimq', url: 'mkimq.com'}
简洁定义
如果属性名与赋值的变量名相同可以更简洁
const web = {name: 'mkimq', url: 'mkimq.com'}
const {name, url} = web
只赋值部分变量
const [, url] = ['mkimq', 'mkimq.com']
const {name} = {name: 'mkimq', url: 'mkimq.com'}
可以直接使用变量赋值对象属性
const name = 'mkimq'
const url = 'mkimq.com'
const mk = {name, url}
嵌套解构
可以操作多层复杂数据结构
默认值
为变量设置默认值
const [name, url = 'mkimq.com'] = ['mkimq']
const {name, age = 18} = {name: 'mkimq'}
使用默认值特性可以方便的对参数预设
函数参数
数组参数的使用
function fn([a, b]) {
console.log(a, b)
}
对象参数使用方法
function fn({a, b}) {
console.log(a, b)
}
对象解构传参
function user(name, {sex, age} = {}) {
console.log(name, sex, age)
}
属性管理
添加属性
可以为对象添加属性
const obj = {}
obj.age = 18
删除属性
使用delete 可以删除属性
const obj = {
age: 18
}
delete obj.age
检测属性
hasOwnProperty检测对象自身是否包含指定的属性,不检测原型链上继承的属性。
const obj = {
age: 18
}
console.log(obj.hasOwnProperty('age'))
使用 in 可以在原型对象上检测
const obj = {
age: 18
}
const mk = {
name: 'mkimq'
}
// 设置mk为obj的新原型
Object.setPrototypeOf(obj, mk)
console.log(obj)
console.log('name' in obj)
console.log(obj.hasOwnProperty('name'))
获取属性名
使用 Object.getOwnPropertyNames 可以获取对象的属性名集合
const obj = {
name: 'mkimq',
age: 18
}
const names = Object.getOwnPropertyNames(obj)
console.log(names)
assign
以往我们使用类似jQuery.extend 等方法设置属性,现在可以使用 Object.assign 静态方法
let obj = {}
obj = Object.assign(obj, {name: 'mkimq'}, {age: 18})
console.log(obj)
计算属性
对象属性可以通过表达式计算定义,这在动态设置属性或执行属性方法时很好用。
let id = 0
const user = {
[`id-${id}`]: id,
[`id-${id}`]: id,
[`id-${id}`]: id
}
传值操作
对象是引用类型赋值是传址操作
const user = {
name: 'mkimq'
}
const mk = {
user
}
mk.user.name = 'MKIMQ'
console.log(user.name)
遍历对象
获取内容
使用系统提供的API可以方便获取对象属性与值
const obj = {
name: 'mkimq',
age: 18
}
console.log(Object.keys(obj))
console.log(Object.values(obj))
console.log(Object.entries(obj))
for/in
使用for/in遍历对象属性
const obj = {
name: 'mkimq',
age: 18
}
for (const key in obj) {
console.log(key, obj[key])
}
for/of
for/of用于遍历迭代对象,不能直接操作对象。但Object对象的keys/方法返回的是迭代对象。
const obj = {
name: 'mkimq',
age: 18
}
for (const key of Object.keys(obj)) {
console.log(key)
}
获取所有对象属性
for (const key of Object.values(obj)) {
console.log(key)
}
同时获取属性名与值
for (const arr of Object.entries(obj)) {
console.log(arr)
}
使用扩展语法同时获取属性名与值
for (const [key, value] of Object.entries(obj)) {
console.log(key, value)
}
对象拷贝
对象赋值时复制的内存地址,所以一个对象的改变直接影响另一个
浅拷贝
使用for/in执行对象拷贝
const obj = {
name: 'mkimq',
age: 18
}
const obj2 = {}
for (const key in obj) {
obj2[key] = obj[key]
}
obj2.name = 'MKIMQ'
console.log(obj)
console.log(obj2)
Object.assign 函数可简单的实现浅拷贝,它是将两个对象的属性叠加后面对象属性会覆盖前面对象同名属性。
const obj = {
name: 'mkimq',
age: 18
}
const obj2 = Object.assign({}, obj)
使用展示语法也可以实现浅拷贝
const obj2 = {...obj}
深拷贝
浅拷贝不会将深层的数据复制
const obj = {
name: 'mkimq',
user: {
name: 'mankeung'
}
}
function copy(object) {
let obj = {}
for (const key in object) {
obj[key] = object[key]
}
return obj
}
const newObj = copy(obj)
newObj.name = 'MKIQM'
newObj.user.name = 'MANKEUNG'
console.log(newObj)
console.log(obj)
是完全的复制一个对象,两个对象是完全独立的对象
const obj = {
name: 'mkimq',
user: {
name: 'mankeung'
},
data: []
}
function copy(object) {
let obj = object instanceof Array ? [] : {}
for (const [k, v] of Object.entries(object)) {
obj[k] = typeof v == 'object' ? copy(v) : v
}
return obj
}
const newObj = copy(obj)
newObj.data.push('wwq')
console.log(JSON.stringify(newObj, null, 2))
console.log(JSON.stringify(obj, null, 2))
构建函数
对象可以通过内置或自定义的构造函数创建。
工厂函数
在函数中返回对象的函数称为工厂函数,工厂函数有以下优点
- 减少重复创建相同类型对象的代码
- 修改工厂函数的方法影响所有同类对象
使用字面量创建对象需要复制属性与方法结构
const mk = {
name: 'mk',
show() {
console.log(this.name)
}
}
const lisi = {
name: 'lisi',
show() {
console.log(this.name)
}
}
使用工厂函数可以简化这个过程
function fn(name) {
return {
name,
show() {
console.log(this.name)
}
}
}
const lisi = fn('lisi')
lisi.show()
const mk = fn('mk')
mk.show()
构造函数
和工厂函数相似构造函数也用于创建对象,它的上下文为新的对象实例。
- 构造函数名每个单词首字母大写即Pascal 命名规范
- this指当前创建的对象
- 不需要返回this系统会自动完成
- 需要使用new关键词生成对象
function Student(name) {
this.name = name
this.show = function() {
console.log(this.name)
}
}
const lisi = new Student('lisi')
lisi.show()
如果构造函数返回对象,实例化后的对象将是此对象
function ArrayObject(...values) {
const arr = new Array()
arr.push.apply(arr, values)
arr.string = function(sym = '|') {
return this.join(sym)
}
return arr
}
const arr = new ArrayObject(1, 2, 3)
console.log(arr)
console.log(arr.string('-'))
严格模式
在严格模式下方法中的this值为undefined,这是为了防止无意的修改window对象
"use strict";
function User() {
this.show = function () {
console.log(this)
}
}
let mk = new User()
mk.show() //User
let f = mk.show
f() //undefined
内置构造
JS中大部分数据类型都是通过构造函数创建的。
const num = new Number(99)
console.log(num.valueOf())
const string = new String('mkimq')
console.log(string.valueOf())
const boolean = new Boolean(true)
console.log(boolean.valueOf())
const date = new Date()
console.log(date.valueOf() * 1)
const regexp = new RegExp('\\d+')
console.log(regexp.test(99))
let mk = new Object()
mk.name = 'mkimq'
console.log(mk)
字面量创建的对象,内部也是调用了Object构造函数
对象函数
在JS中函数也是一个对象
function fn(name) {}
console.log(fn.toString())
console.log(fn.length)
函数是由系统内置的 Function 构造函数创建的
下面是使用内置构造函数创建的函数
const User = new Function(`name`,`
this.name = name
this.show = function() {
return this.name
}
`
)
const lisi = new User('lisi')
console.log(lisi.show())
抽象特性
将复杂功能隐藏在内部,只开放给外部少量方法,更改对象内部的复杂逻辑不会对外部调用造成影响即抽象。
属性特征
查看特征
使用 Object.getOwnPropertyDescriptor查看对象属性的描述。
Object.getOwnPropertyDescriptor(user, 'name')
使用 Object.getOwnPropertyDescriptors查看对象所有属性的描述
Object.getOwnPropertyDescriptors(user)
属性包括以下四种特性
特性 | 说明 | 默认值 |
---|---|---|
configurable | 能否使用delete、能否需改属性特性、或能否修改访问器属性 | true |
enumerable | 对象属性是否可通过for-in循环,或Object.keys() 读取 | true |
writable | 对象属性是否可修改 | true |
value | 对象属性的默认值 | undefined |
设置特征
使用Object.defineProperty 方法修改属性特性,通过下面的设置属性name将不能被遍历、删除、修改。
const user = {
name: 'mkimq'
}
Object.defineProperty(user, 'name', {
value: 'MKIMQ',
writable: false,
enumerable: false,
configurable: false
})
// 使用 Object.defineProperties 可以一次设置多个属性,具体参数和上面介绍的一样。
Object.defineProperties(user, {
name: {value: 'MKIMQ'},
age: {value: 18}
})
禁止添加
Object.preventExtensions 禁止向对象添加属性
Object.preventExtensions(user)
user.sex = '男' // error
Object.isExtensible 判断是否能向对象中添加属性
Object.isExtensible(user) // true / false
封闭对象
Object.seal()方法封闭一个对象,阻止添加新属性并将所有现有属性标记为 configurable: false
const user = {
name: 'mkimq',
age: 18
}
Object.seal(user)
console.log(
JSON.stringify(Object.getOwnPropertyDescriptors(user), null, 2)
)
// Object.isSealed 如果对象是密封的则返回 true,属性都具有 configurable: false。
console.log(Object.isSealed(user))
冻结对象
Object.freeze 冻结对象后不允许添加、删除、修改属性,writable、configurable都标记为false
Object.freeze(user)
// Object.isFrozen()方法判断一个对象是否被冻结
console.log(Object.isFrozen(user))
属性访问器
getter方法用于获得属性值,setter方法用于设置属性,这是JS提供的存取器特性即使用函数来管理属性。
- 用于避免错误的赋值
- 需要动态监测值的改变
- 属性只能在访问器和普通属性任选其一,不能共同存在
getter/setter
向对是地用户的年龄数据使用访问器监控控制
const user = {
data: {
name: 'mkimq',
age: null
},
set age(value) {
if (typeof value != 'number' || value > 100 || value < 10) {
throw new Error('年龄格式错误~')
}
this.data.age = value
},
get age() {
return `年龄是:${this.data.age}`
}
}
user.age = 100
console.log(user.age)
下面通过设置站网站名称与网址体验getter/setter批量设置属性的使用
const web = {
name: 'mkimq',
url: 'mkimq.com',
get site() {
return `${this.name}: ${this.url}`
},
set site(value) {
[this.name, this.url] = value.split(',')
}
}
web.site = '百度,baidu.com'
console.log(web.site)
访问器描述符
使用 defineProperty 可以模拟定义私有属性,从而使用面向对象的抽象特性。
function User(name, age) {
const data = {name, age}
Object.defineProperties(this, {
name: {
get() {
return data.name
},
set(value) {
if (value.trim() == '') throw new Error('无效用户~')
data.name = value
}
},
age: {
get() {
return data.age
},
set(value) {
if (value.trim() == '') throw new Error('无效年龄~')
data.age = value
}
}
})
}
const mk = new User('mkimq', 18)
console.log(mk.name, mk.age)
闭包访问器
下面结合闭包特性对属性进行访问控制
- 下例中访问器定义在函数中,并接收参数
- 在get() 中通过闭包返回
- 在set() 中修改了v,这会影响get()访问的闭包数据
const data = {
name: 'mkimq'
}
for (const [key, value] of Object.entries(data)) {
observer(data, key, value)
}
function observer(data, key, v) {
Object.defineProperty(data, key, {
get() {
return v
},
set(newValue) {
v = newValue
}
})
}
data.name = 'MKIMQ'
console.log(data.name)
代理拦截
代理(拦截器)是对象的访问控制,setter/getter 是对单个对象属性的控制,而代理是对整个对象的控制。
- 读写属性时代码更简洁
- 对象的多个属性控制统一交给代理完成
- 严格模式下 set 必须返回布尔值
使用方法
"use strict";
const obj = {
name: 'mkimq'
}
const proxy = new Proxy(obj, {
get(taget, p) {
return taget[p]
},
set(taget, p, value) {
taget[p] = value
return true
}
})
proxy.name = 10
console.log(obj)
代理函数
如果代理以函数方式执行时,会执行代理中定义 apply 方法。
- 参数说明:函数,上下文对象,参数
下面使用 apply 计算函数执行时间
function factorial(num) {
return num == 1 ? 1 : num * factorial(num - 1)
}
const proxy = new Proxy(factorial, {
apply(func, obj, args) {
console.time('run')
const num = func.apply(obj, args)
console.timeEnd('run')
return num
}
})
console.log(proxy.apply(this, [3]))
console.log(proxy(100))
双向绑定
下面通过代理实现vue 等前端框架的数据绑定特性特性。
<body>
<input type="text" v-model="title" />
<input type="text" v-model="title" />
<div v-bind="title"></div>
</body>
function View() {
// 设置代理拦截
const proxy = new Proxy(
{},
{
get(obj, property) {},
set(obj, property, value) {
obj[property] = value
document
.querySelectorAll(`[v-model="${property}"], [v-bind="${property}"]`)
.forEach(el => {
el.innerHTML = value
el.value = value
})
}
}
)
// 初始化绑定元素事件
this.run = function() {
const els = document.querySelectorAll('[v-model]')
els.forEach(item => {
item.addEventListener('keyup', function() {
proxy[this.getAttribute('v-model')] = this.value
})
})
}
}
const view = new View()
view.run()
点击查看效果
表单验证
<body>
<input type="text" validate rule="max:12,min:3" />
<input type="text" validate rule="max:3,isNumber" />
</body>
body {
padding: 50px;
background: #34495e;
}
input {
border: solid 10px #ddd;
height: 30px;
}
.error {
border: solid 10px red;
}
class Validate {
max(value, len) {
return value.length <= len
}
min(value, len) {
return value.length >= len
}
isNumber(value) {
return /^\d+$/.test(value)
}
}
// 代理
function createProxy(target) {
return new Proxy(target, {
get(target, key) {
return target[key]
},
set(target, key, el) {
const rule = el.getAttribute('rule')
const validate = new Validate()
const state = rule.split(',').every(rule => {
const info = rule.split(':')
return validate[info[0]](el.value, info[1])
})
el.classList[state ? 'remove' : 'add']('error')
return true
}
})
}
const nodes = createProxy(document.querySelectorAll('[validate]'))
nodes.forEach((item, i) => {
item.addEventListener('keyup', function() {
nodes[i] = this
})
})
点击查看效果
JSON
json 是一种轻量级的数据交换格式,易于人阅读和编写。
使用json 数据格式是替换 xml 的最佳方式,主流语言都很好的支持json 格式。所以 json 也是前后台传输数据的主要格式。
json 标准中要求使用双引号包裹属性,虽然有些语言不强制,但使用双引号可避免多程序间传输发生错误语言错误的发生。
基本结构
const user = {
name: 'mkimq',
age: 18
}
user.name
- 数组结构
const users = [
{
name: 'mkimq',
age: 18
}
]
- 序列化
- 根据第二个参数指定保存的属性
- 第三个是参数用来控制TAB数量,如果字符串则为前导字符。
JSON.stringify(user)
为数据添加 toJSON 方法来自定义返回格式
const user = {
name: 'mkimq',
age: 18,
toJSON: function() {
return {
n: this.name,
a: this.age
}
}
}
console.log(JSON.stringify(user))
反序列化
JSON.parse(jsonStr)
使用第二个参数函数来对返回的数据二次处理
JSON.parse(jsonStr, (key, value) => {
if (key === 'name') return `[推荐] ${value}`
return value
})
Reflect
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法
- Reflect并非一个构造函数,所以不能通过new运算符对其进行调用
const queuedObservers = new Set()
const observe = fn => queuedObservers.add(fn)
const observable = obj => new Proxy(obj, {
set
})
function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver)
queuedObservers.forEach(observer => observer())
return result
}
//监听
const person = observable({
name: '张三',
age: 20
})
function print() {
console.log(`${person.name}, ${person.age}`)
}
observe(print)
person.name = '李四'