对象
对象基础知识
对象是包括属性与方法的数据类型,JS 中大部分类型都是对象如 String / Number / Math / RegExp / Date 等等。
传统的函数编程会有错综复杂的依赖很容易创造意大利式面条代码,使用对象编程的代码结构清晰,也减少了函数的参数传递,也不用担心函数名的覆盖。
let name = '杜一刀'
let grade = [
{ lesson: 'js', score: 99 },
{ lesson: 'mysql', score: 85 },
]
function average(grade, name) {
const total = grade.reduce((t, a) => t + a.score, 0)
return name + ':' + total / grade.length + '分'
}
console.log(average(grade, name))let user = {
name: '刀刀',
grade: [
{ title: 'vue', score: 80 },
{ title: 'axios', score: 85 },
],
average() {
return `${this.name}'s grade is ${
this.grade.reduce((total, obj) => (total += obj.score), 0) /
this.grade.length
}`
},
}
console.log(user.average())OOP
- 对象是属性和方法的集合即封装
- 将复杂功能隐藏在内部,只开放给外部少量方法,更改对象内部的复杂逻辑不会对外部调用造成影响即抽象
- 继承是通过代码复用减少冗余代码
- 根据不同形态的对象产生不同结果即多态
对象基本声明
使用字面量形式声明对象是最简单的方式,属性与方法可以简写。字面量形式实质上在系统内部也是使用构造函数 new Object 创建的,后面会详细介绍构造函数。
let obj = {
name: '刀刀',
get: function () {
return this.name
},
}
console.log(obj.get()) //刀刀let name = '刀刀'
let obj = {
name,
get() {
return this.name
},
}
console.log(obj.get()) //刀刀let hd = {}
let daodao = new Object()
console.log(hd, daodao)
console.log(hd.constructor)
console.log(daodao.constructor)对象操作属性
使用 . 操作属性更简洁,[] 主要用于通过变量定义属性的场景,如果属性名不是合法变量名就必须使用扩号的形式了。
let user = {
name: '杜一刀',
}
console.log(user.name)console.log(user['name'])let user = {}
user['my-age'] = 28
console.log(user['my-age'])对象和方法的属性可以动态的添加或删除。
const hd = {
name: '刀刀',
}
hd.age = '10'
hd.show = function () {
return `${this.name}已经${this.age}岁了`
}
console.log(hd.show())
console.log(hd) // {name: "刀刀", age: "10", show: f}
delete hd.show
delete hd.age
console.log(hd) // {name: "刀刀"}
console.log(hd.age) //undefined对象方法
定义在对象中的函数称为方法,下面定义了学生对象,并提供了计算平均成绩的方法
let lisi = {
name: '李四',
age: 22,
grade: {
math: 99,
english: 67,
},
//平均成绩
avgGrade: function () {
let total = 0
for (const key in this.grade) {
total += this.grade[key]
}
return total / this.propertyCount('grade')
},
//获取属性数量
propertyCount: function (property) {
let count = 0
for (const key in this[property]) count++
return count
},
}
console.log(lisi.avgGrade())一个学生需要手动创建一个对象,这显然不实际的,下面的构造函数就可以解决这个问题
对象引用特性
- 对象和函数、数组一样是引用类型,即复制只会复制引用地址
- 对象做为函数参数使用时也不会产生完全赋值,内外共用一个对象
- 对象的比较是对内存地址的比较所以使用
==或===一样
let hd = { name: '刀刀' }
let cms = hd
cms.name = 'xiaodao'
console.log(hd.name) //xiaodaolet user = { age: 22 }
function hd(user) {
user.age += 10
}
hd(user)
console.log(user.age) //32let hd = {}
let xj = hd
let cms = {}
console.log(hd == xj) //true
console.log(hd === xj) //true
console.log(hd === cms) //false对象中的 this
this 指当前对象的引用,始终建议在代码内部使用 this 而不要使用对象名,不同对象的 this 只指向当前对象。
下例是不使用 this 时发生的错误场景
- 删除了
xj变量,但在函数体内还在使用xj变量造成错误 - 使用
this后始终指向到引用地址,就不会有这个问题
let xj = {
name: '杜一刀',
show() {
return xj.name
},
}
let hd = xj
xj = null
console.log(hd.show()) // Errorlet xj = {
name: '杜一刀',
show() {
return this.name
},
}
let hd = xj
xj = null
console.log(hd.show()) // 杜一刀对象展开语法
使用 ... 可以展示对象的结构,下面是实现示例:
let hd = { name: '刀刀', web: 'houdunren.com' }
let info = { ...hd, site: 'xiaodao' }
console.log(info)function upload(params) {
let config = {
type: '*.jpeg,*.png',
size: 10000,
}
params = { ...config, ...params }
console.log(params)
}
upload({ size: 999 })对象转换
基础知识
对象直接参与计算时,系统会根据计算的场景在 string / number / default 间转换。
- 如果声明需要字符串类型,调用顺序为
toString > valueOf - 如果场景需要数值类型,调用顺序为
valueOf > toString - 声明不确定时使用
default,大部分对象的default会当数值使用
下面的数值对象会在数学运算时转换为 number
let daodao = new Number(1)
console.log(daodao + 3) //4如果参数字符串运长时会转换为 string
let daodao = new Number(1)
console.log(daodao + '3') //13下面当不确定转换声明时使用 default ,大部分 default 转换使用 number 转换。
let daodao = new Number(1)
console.log(daodao == '1') //trueSymbol.toPrimitive
内部自定义 Symbol.toPrimitive 方法用来处理所有的转换场景
let hd = {
num: 1,
[Symbol.toPrimitive]: function () {
return this.num
},
}
console.log(hd + 3) //4valueOf/toString
可以自定义 valueOf 与 toString 方法用来转换,转换并不限制返回类型。
let hd = {
name: '刀刀',
num: 1,
valueOf: function () {
console.log('valueOf')
return this.num
},
toString: function () {
console.log('toString')
return this.name
},
}
console.log(hd + 3) //valueOf 4
console.log(`${hd}杜一刀`) //toString 刀刀杜一刀解构赋值
解构是一种更简洁的赋值特性,可以理解为分解一个数据的结构,在数组章节已经介绍过。
基本使用
//对象使用
let info = { name: '刀刀', url: 'daodao.com' }
let { name: n, url: u } = info
console.log(n) // 刀刀
//如果属性名与变量相同可以省略属性定义
let { name, url } = { name: '刀刀', url: 'daodao.com' }
console.log(name) // 刀刀function hd() {
return {
name: '刀刀',
url: 'daodao.com',
}
}
let { name: n, url: u } = hd()
console.log(n)'use strict'
function hd({ name, age }) {
console.log(name, age) //杜一刀 18
}
hd({ name: '杜一刀', age: 18 })系统函数解构练习,这没有什么意义只是加深解构印象
const { random } = Math
console.log(random())严格模式
非严格模式可以不使用声明指令,严格模式下必须使用声明。所以建议使用 let 等声明。
// "use strict";
;({ name, url } = { name: '刀刀', url: 'daodao.com' })
console.log(name, url)'use strict'
let { name, url } = { name: '刀刀', url: 'daodao.com' }
console.log(name, url)简洁定义
如果属性名与赋值的变量名相同可以更简洁
let web = { name: '刀刀', url: 'daodao.com' }
let { name, url } = web
console.log(name) //刀刀只赋值部分变量
let [, url] = ['刀刀', 'daodao.com']
console.log(url) //daodao.com
let { name } = { name: '刀刀', url: 'daodao.com' }
console.log(name) //刀刀可以直接使用变量赋值对象属性
let name = '刀刀',
url = 'daodao.com'
//标准写法如下
let hd = { name: name, url: url }
console.log(hd) //{name: "刀刀", url: "daodao.com"}
//如果属性和值变量同名可以写成以下简写形式
let opt = { name, url }
console.log(opt) //{name: "刀刀", url: "daodao.com"}嵌套解构
可以操作多层复杂数据结构
const hd = {
name: '刀刀',
lessons: {
title: 'JS',
},
}
const {
name,
lessons: { title },
} = hd
console.log(name, title) // 刀刀 JS
console.log(lessons) // lessons is not defined默认值
为变量设置默认值
let [name, site = 'xiaodao'] = ['刀刀']
console.log(site) //xiaodao
let { name, url, user = '杜一刀' } = { name: '刀刀', url: 'daodao.com' }
console.log(name, user) // 刀刀, 杜一刀使用默认值特性可以方便的对参数预设
function createElement(options) {
let { width = '200px', height = '100px', backgroundColor = 'red' } = options
const h2 = document.createElement('h2')
h2.style.width = width
h2.style.height = height
h2.style.backgroundColor = backgroundColor
document.body.appendChild(h2)
}
createElement({
backgroundColor: 'green',
})函数参数
function hd([a, b]) {
console.log(a, b)
}
hd(['刀刀', 'duyidao'])function hd({ name, url, user = '杜一刀' }) {
console.log(name, url, user) //刀刀 daodao.com 杜一刀
}
hd({ name: '刀刀', url: 'daodao.com' })function user(name, { sex, age } = {}) {
console.log(name, sex, age) //杜一刀 男 18
}
user('杜一刀', { sex: '男', age: 18 })属性管理
添加属性
可以为对象添加属性
let obj = { name: '刀刀' }
obj.site = 'daodao.com'
console.log(obj)删除属性
使用 delete 可以删除属性(后面介绍的属性特性章节可以保护属性不被删除)
let obj = { name: '刀刀' }
delete obj.name
console.log(obj.name) //undefined检测属性
hasOwnProperty 检测对象自身是否包含指定的属性,不检测原型链上继承的属性。
let obj = { name: '刀刀' }
console.log(obj.hasOwnProperty('name')) //true下面通过数组查看
let arr = ['刀刀']
console.log(arr)
console.log(arr.hasOwnProperty('length')) //true
console.log(arr.hasOwnProperty('concat')) //false
console.log('concat' in arr) //true使用 in 可以在原型对象上检测
let obj = { name: '刀刀' }
let hd = {
web: 'daodao.com',
}
//设置hd为obj的新原型
Object.setPrototypeOf(obj, hd)
console.log(obj)
console.log('web' in obj) //true
console.log(obj.hasOwnProperty('web')) //false获取属性名
使用 Object.getOwnPropertyNames 可以获取对象的属性名集合
let hd = { name: '刀刀', year: 2010 }
const names = Object.getOwnPropertyNames(hd)
console.log(names)
// ["name", "year"]assign
以往我们使用类似 jQuery.extend 等方法设置属性,现在可以使用 Object.assign 静态方法
从一个或多个对象复制属性
'use strict'
let hd = { a: 1, b: 2 }
hd = Object.assign(hd, { f: 1 }, { m: 9 })
console.log(hd) //{a: 1, b: 2, f: 1, m: 9}计算属性
对象属性可以通过表达式计算定义,这在动态设置属性或执行属性方法时很好用。
let id = 0
const user = {
[`id-${id++}`]: id,
[`id-${id++}`]: id,
[`id-${id++}`]: id,
}
console.log(user)使用计算属性为文章定义键名
const lessons = [
{
title: '媒体查询响应式布局',
category: 'css',
},
{
title: 'FLEX 弹性盒模型',
category: 'css',
},
{
title: 'MYSQL多表查询随意操作',
category: 'mysql',
},
]
let lessonObj = lessons.reduce((obj, cur, index) => {
obj[`${cur['category']}-${index}`] = cur
return obj
}, {})
console.log(lessonObj) //{css-0: {…}, css-1: {…}, mysql-2: {…}}
console.log(lessonObj['css-0']) //{title: "媒体查询响应式布局", category: "css"}传值操作
对象是引用类型赋值是传址操作,后面会介绍对象的深、浅拷贝操作
let user = {
name: '刀刀',
}
let hd = {
stu: user,
}
hd.stu.name = 'daodao'
console.log(user) // {name: 'daodao'}
console.log(user.name) // xiaodao遍历对象
获取内容
使用系统提供的 API 可以方便获取对象属性与值
const hd = {
name: '刀刀',
age: 10,
}
console.log(Object.keys(hd)) //["name", "age"]
console.log(Object.values(hd)) //["刀刀", 10]
console.table(Object.entries(hd)) //[["name","刀刀"],["age",10]]for/in
使用 for/in 遍历对象属性
const hd = {
name: '刀刀',
age: 10,
}
for (let key in hd) {
console.log(key, hd[key]) // name 刀刀 ; age 10
}for/of
for/of 用于遍历迭代对象,不能直接操作对象。但 Object 对象的 keys/ 方法返回的是迭代对象。
const hd = {
name: '刀刀',
age: 10,
}
for (const key of hd) {
console.log(key) // hd is not iterable
}
for (const key of Object.keys(hd)) {
console.log(key) // name ; age
}const hd = {
name: '刀刀',
age: 10,
}
for (const key of Object.values(hd)) {
console.log(key) // 刀刀 ; 10
}for (const array of Object.entries(hd)) {
console.log(array) // (2) ['name', '刀刀'] ; (2) ['age', 10]
}for (const [key, value] of Object.entries(hd)) {
console.log(key, value) // name 刀刀 ; age 10
}添加元素 DOM 练习
let lessons = [
{ name: 'js', click: 23 },
{ name: 'node', click: 192 },
]
let ul = document.createElement('ul')
for (const val of lessons) {
let li = document.createElement('li')
li.innerHTML = `课程:${val.name},点击数:${val.click}`
ul.appendChild(li)
}
document.body.appendChild(ul)对象拷贝
对象赋值时复制的内存地址,所以一个对象的改变直接影响另一个
let obj = {
name: '刀刀',
user: {
name: 'xiaodao',
},
}
let a = obj
let b = obj
a.name = 'lisi'
console.log(b.name) //lisi浅拷贝
使用 for/in 执行对象拷贝
let obj = { name: '刀刀' }
let hd = {}
for (const key in obj) {
hd[key] = obj[key]
}
hd.name = 'daodao'
console.log(hd) // {name: 'daodao'}
console.log(obj) // {name: '刀刀'}Object.assign 函数可简单的实现浅拷贝,它是将两个对象的属性叠加后面对象属性会覆盖前面对象同名属性。
let user = {
name: '刀刀',
}
let hd = {
stu: Object.assign({}, user),
}
hd.stu.name = 'daodao'
console.log(user.name) //刀刀使用展示语法也可以实现浅拷贝
let obj = {
name: '刀刀',
}
let hd = { ...obj }
hd.name = 'daodao'
console.log(hd) // {name: 'daodao'}
console.log(obj) // {name: '刀刀'}深拷贝
浅拷贝不会将深层的数据复制
let obj = {
name: '刀刀',
user: {
name: 'xiaodao',
},
}
let a = obj
let b = obj
function copy(object) {
let obj = {}
for (const key in object) {
obj[key] = object[key]
}
return obj
}
let newObj = copy(obj)
newObj.name = 'xiaodao'
newObj.user.name = 'daodao.com'
console.log(newObj)
console.log(obj)是完全的复制一个对象,两个对象是完全独立的对象
let data = {
user: 'name',
can: {
eat: 'good',
sleep: 'well',
},
learn: ['vue', 'js', 'axios'],
}
function copy(params) {
let res = params instanceof Object ? {} : [] // 判断当前是数组还是对象
for (const [key, value] of Object.entries(params)) {
// key为键,value为值。如果值是对象或数组,则递归,再一次执行拷贝函数
res[key] = typeof value === 'object' ? copy(value) : value
}
return res
}
const fn = copy(data)
data.learn[1] = 'react'
console.log(JSON.stringify(fn, null, 2))
// result:
// {
// "user": "name",
// "can": {
// "eat": "good",
// "sleep": "well"
// },
// "learn": {
// "0": "vue",
// "1": "js",
// "2": "axios"
// }
// }
console.log(JSON.stringify(data, null, 2))
// result:
// {
// "user": "name",
// "can": {
// "eat": "good",
// "sleep": "well"
// },
// "learn": [
// "vue",
// "react",
// "axios"
// ]
// }构建函数
对象可以通过内置或自定义的构造函数创建。
工厂函数
在函数中返回对象的函数称为工厂函数,工厂函数有以下优点
- 减少重复创建相同类型对象的代码
- 修改工厂函数的方法影响所有同类对象
使用字面量创建对象需要复制属性与方法结构
const xj = {
name: '杜一刀',
show() {
console.log(this.name)
},
}
const hd = {
name: '刀刀',
show() {
console.log(this.name)
},
}使用工厂函数可以简化这个过程
function stu(name) {
return {
name,
show() {
console.log(this.name)
},
}
}
const lisi = stu('李四')
lisi.show()
const xj = stu('杜一刀')
dd.show()构造函数
和工厂函数相似构造函数也用于创建对象,它的上下文为新的对象实例。
- 构造函数名每个单词首字母大写即
Pascal命名规范 this指当前创建的对象- 不需要返回
this系统会自动完成 - 需要使用
new关键词生成对象
function Student(name) {
this.name = name
this.show = function () {
console.log(this.name)
}
//不需要返回,系统会自动返回
// return this;
}
const lisi = new Student('李四')
lisi.show()
const xj = new Student('杜一刀')
xj.show()如果构造函数返回对象,实例化后的对象将是此对象
function ArrayObject(...values) {
const arr = new Array()
arr.push.apply(arr, values)
arr.string = function (sym = '|') {
return this.join(sym)
}
return arr
}
const array = new ArrayObject(1, 2, 3)
console.log(array)
console.log(array.string('-'))严格模式
在严格模式下方法中的 this 值为 undefined,这是为了防止无意的修改 window 对象
'use strict'
function User() {
this.show = function () {
console.log(this)
}
}
let hd = new User()
hd.show() //User
let xj = hd.show
xj() //undefined内置构造
JS 中大部分数据类型都是通过构造函数创建的。
const num = new Number(99)
console.log(num.valueOf())
const string = new String('刀刀')
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 hd = new Object()
hd.name = '刀刀'
console.log(hd)字面量创建的对象,内部也是调用了 Object 构造函数
const hd = {
name: '刀刀',
}
console.log(hd.constructor) //ƒ Object() { [native code] }
//下面是使用构造函数创建对象
const xiaodao = new Object()
xiaodao.title = '开源内容管理系统'
console.log(xiaodao)对象函数
在 JS 中函数也是一个对象
function hd(name) {}
console.log(hd.toString())
console.log(hd.length)函数是由系统内置的 Function 构造函数创建的
function hd(name) {}
console.log(hd.constructor)下面是使用内置构造函数创建的函数
const User = new Function(
`name`,
`
this.name = name;
this.show = function() {
return this.name;
};
`,
)
const lisi = new User('李四')
console.log(lisi.show())抽象特性
将复杂功能隐藏在内部,只开放给外部少量方法,更改对象内部的复杂逻辑不会对外部调用造成影响即抽象。
下面的手机就是抽象的好例子,只开放几个按钮给用户,复杂的工作封装在手机内部,程序也应该如此。
问题分析
下例将对象属性封装到构造函数内部
function User(name, age) {
this.name = name
this.age = age
this.info = function () {
return this.age > 50 ? '中年人' : '年轻人'
}
this.about = function () {
return `${this.name}是${this.info()}`
}
}
let lisi = new User('李四', 22)
console.log(lisi.about()) // 李四是年轻人
lisi.about = function () {
return '我要污染这个函数'
}
console.log(lisi.about()) // 我要污染这个函数抽象封装
上例中的方法和属性仍然可以在外部访问到,比如 info 方法只是在内部使用,不需要被外部访问到这会破坏程序的内部逻辑。
下面使用闭包特性将对象进行抽象处理
function User(name, age) {
let data = { name, age }
let about = function () {
return data.age > 50 ? '中年人' : '年轻人'
}
this.message = function () {
return `${data.name}是${about()}`
}
}
let lisi = new User('刀刀', 22)
console.log(lisi.message()) // 刀刀是年轻人
lisi.about = function () {
return '我要污染这个函数'
}
console.log(lisi.message()) // 刀刀是年轻人属性特征
JS 中可以对属性的访问特性进行控制。
查看特征
使用 Object.getOwnPropertyDescriptor 查看对象单个属性的描述。参数 2 指定查看哪个属性。
'use strict'
const user = {
name: '杜一刀',
age: 18,
}
let desc = Object.getOwnPropertyDescriptor(user, 'name')
console.log(JSON.stringify(desc, null, 2))使用 Object.getOwnPropertyDescriptors 查看对象所有属性的描述
'use strict'
const user = {
name: '杜一刀',
age: 18,
}
let desc = Object.getOwnPropertyDescriptors(user)
console.log(JSON.stringify(desc, null, 2))属性包括以下四种特性
| 特性 | 说明 | 默认值 |
|---|---|---|
configurable | 能否使用 delete、能否需改属性特性、或能否修改访问器属性 | true |
enumerable | 对象属性是否可通过 for...in 循环,或 Object.keys() 读取 | true |
writable | 对象属性是否可修改 | true |
value | 对象属性的默认值 | undefined |
设置特征
使用 Object.defineProperty 方法修改属性特性,通过下面的设置属性 name 将不能被遍历、删除、修改。
'use strict'
const user = {
name: '杜一刀',
}
Object.defineProperty(user, 'name', {
value: '刀刀',
writable: false,
})
user.name = '杜一刀' // Error'use strict'
const user = {
name: '杜一刀',
}
Object.defineProperty(user, 'name', {
value: '刀刀',
enumerable: false,
})
console.log(Object.keys(user))'use strict'
const user = {
name: '杜一刀',
}
Object.defineProperty(user, 'name', {
value: '刀刀',
configurable: false,
})
delete user.name
console.log(user)Object.defineProperty(user, 'name', {
value: '刀刀',
writable: true,
enumerable: false,
configurable: false,
})使用 Object.defineProperties 可以一次设置多个属性,具体参数和上面介绍的一样。
'use strict'
let user = {}
Object.defineProperties(user, {
name: { value: '杜一刀', writable: false },
age: { value: 18 },
})
console.log(user)
user.name = '刀刀' //TypeError禁止添加
Object.prevenjsensions 禁止向对象添加属性
'use strict'
const user = {
name: '杜一刀',
}
Object.prevenjsensions(user)
user.age = 18 //ErrorObject.isExtensible 判断是否能向对象中添加属性
'use strict'
const user = {
name: '杜一刀',
}
Object.prevenjsensions(user)
console.log(Object.isExtensible(user)) //false封闭对象
Object.seal() 方法封闭一个对象,阻止添加新属性并将所有现有属性标记为 configurable: false 。
'use strict'
const user = {
name: '刀刀',
age: 18,
}
console.log(JSON.stringify(Object.getOwnPropertyDescriptors(user), null, 2))
Object.seal(user)
console.log(Object.isSealed(user))
delete user.name //ErrorObject.isSealed 如果对象是密封的则返回 true,属性都具有 configurable: false。
'use strict'
const user = {
name: '杜一刀',
}
Object.seal(user)
console.log(Object.isSealed(user)) //true冻结对象
Object.freeze 冻结对象后不允许添加、删除、修改属性,writable、configurable 都标记为false 。
'use strict'
const user = {
name: '杜一刀',
}
Object.freeze(user)
user.name = '刀刀' //ErrorObject.isFrozen() 方法判断一个对象是否被冻结
'use strict'
const user = {
name: '杜一刀',
}
Object.freeze(user)
console.log(Object.isFrozen(user))属性访问器
getter 方法用于获得属性值,setter 方法用于设置属性,这是 JS 提供的存取器特性即使用函数来管理属性。
- 用于避免错误的赋值
- 需要动态监测值的改变
- 属性只能在访问器和普通属性任选其一,不能共同存在
getter/setter
向对是地用户的年龄数据使用访问器监控控制
'use strict'
const user = {
data: { name: '刀刀', 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 = 99
console.log(user.age)下面使用 getter 设置只读的课程总价
let Lesson = {
lists: [
{ name: 'js', price: 100 },
{ name: 'mysql', price: 212 },
{ name: 'vue.js', price: 98 },
],
get total() {
return this.lists.reduce((t, b) => t + b.price, 0)
},
}
console.log(Lesson.total) //410
Lesson.total = 30 //无效
console.log(Lesson.total) //410下面通过设置站网站名称与网址体验 getter/setter 批量设置属性的使用
const web = {
name: '刀刀',
url: 'daodao.com',
get site() {
return `${this.name} ${this.url}`
},
set site(value) {
;[this.name, this.url] = value.split(',')
},
}
web.site = '刀刀,xiaodao.com'
console.log(web.site)下面是设置 token 储取的示例,将业务逻辑使用 getter/setter 处理更方便,也方便其他业务的复用。
let Request = {
get token() {
let con = localStorage.getItem('token')
if (!con) {
alert('请登录后获取token')
} else {
return con
}
},
set token(con) {
localStorage.setItem('token', con)
},
}
// Request.token = 'daodao'
console.log(Request.token)定义内部私有属性
'use strict'
const user = {
get name() {
return this._name
},
set name(value) {
if (value.length <= 3) {
throw new Error('用户名不能小于三位')
}
this._name = value
},
}
user.name = '刀刀'
console.log(user.name)访问器描述符
使用 defineProperty 可以模拟定义私有属性,从而使用面向对象的抽象特性。
function User(name, age) {
let 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.name
},
set(value) {
if (value.trim() == '') throw new Error('无效的用户名')
data.name = value
},
},
})
}
let hd = new User('刀刀', 33)
console.log(hd.name)
hd.name = '杜一刀'
console.log(hd.name)上面的代码也可以使用语法糖 class 定义
'use strict'
const DATA = Symbol()
class User {
constructor(name, age) {
this[DATA] = { name, age }
}
get name() {
return this[DATA].name
}
set name(value) {
if (value.trim() == '') throw new Error('无效的用户名')
this[DATA].name = value
}
get age() {
return this[DATA].name
}
set age(value) {
if (value.trim() == '') throw new Error('无效的用户名')
this[DATA].name = value
}
}
let hd = new User('刀刀', 33)
console.log(hd.name)
hd.name = '杜一刀1'
console.log(hd.name)
console.log(hd)闭包访问器
下面结合闭包特性对属性进行访问控制
- 下例中访问器定义在函数中,并接收参数 v
- 在
get()中通过闭包返回 v - 在
set()中修改了 v,这会影响get()访问的闭包数据 v
let data = {
name: 'daodao.com',
}
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 = '刀刀'
console.dir(data.name) //刀刀代理拦截
代理(拦截器)是对象的访问控制,setter/getter 是对单个对象属性的控制,而代理是对整个对象的控制。
- 读写属性时代码更简洁
- 对象的多个属性控制统一交给代理完成
- 严格模式下
set必须返回布尔值
使用方法
代理对象时 get 方法有两个参数,set 方法有三个参数,前两个参数相同。
- 参数一:代理的对象
- 参数二:当前使用的属性
- 参数三:传递过来的值
'use strict'
const hd = { name: '刀刀' }
const proxy = new Proxy(hd, {
get(obj, property) {
return obj[property]
},
set(obj, property, value) {
obj[property] = value
return true
},
})
proxy.age = 10
console.log(hd)代理函数
如果代理以函数方式执行时,会执行代理中定义 apply 方法。
- 参数说明:函数,上下文对象,参数
下面使用 apply 计算函数执行时间
function factorial(num) {
return num == 1 ? 1 : num * factorial(num - 1)
}
let proxy = new Proxy(factorial, {
apply(func, obj, args) {
console.time('run')
func.apply(obj, args)
console.timeEnd('run')
},
})
proxy.apply(this, [1, 2, 3])代理数组
代理数组有两个参数:
- 参数一:代理的数组元素
- 参数 2:当前选中的索引(如果没有选择索引就没有该打印)
下例中对数组进行代理,用于截取标题操作
const stringDot = {
get(target, key) {
const title = target[key].title
const len = 5
return title.length > len ? title.substr(0, len) + '.'.repeat(3) : title
},
}
const lessons = [
{
title: '媒体查询响应式布局',
category: 'css',
},
{
title: 'FLEX 弹性盒模型',
category: 'css',
},
{
title: 'MYSQL多表查询随意操作',
category: 'mysql',
},
]
const stringDotProxy = new Proxy(lessons, stringDot)
console.log(stringDotProxy[0])双向绑定
下面通过代理实现Vue等前端框架的数据绑定特性特性。

<body>
<input type="js" v-model="title" />
<input type="js" v-model="title" />
<div v-bind="title"></div>
</body>
<script>
function View() {
//设置代理拦截
let 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;
});
});
};
}
let view = new View().run();表单验证
<style>
body {
padding: 50px;
background: #34495e;
}
input {
border: solid 10px #ddd;
height: 30px;
}
.error {
border: solid 10px red;
}
</style>
<body>
<input type="js" validate rule="max:12,min:3" />
<input type="js" validate rule="max:3,isNumber" />
</body>
<script>
'use strict'
//验证处理类
class Validate {
max(value, len) {
return value.length <= len
}
min(value, len) {
return value.length >= len
}
isNumber(value) {
return /^\d+$/.test(value)
}
}
//代理工厂
function makeProxy(target) {
return new Proxy(target, {
get(target, key) {
return target[key]
},
set(target, key, el) {
const rule = el.getAttribute('rule')
const validate = new Validate()
let 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 = makeProxy(document.querySelectorAll('[validate]'))
nodes.forEach((item, i) => {
item.addEventListener('keyup', function () {
nodes[i] = this
})
})
</script>JSON
json是一种轻量级的数据交换格式,易于人阅读和编写。- 使用
json数据格式是替换xml的最佳方式,主流语言都很好的支持json格式。所以json也是前后台传输数据的主要格式。 json标准中要求使用双引号包裹属性,虽然有些语言不强制,但使用双引号可避免多程序间传输发生错误语言错误的发生。
声明定义
基本结构
let hd = {
title: '刀刀',
url: 'daodao.com',
teacher: {
name: '杜一刀',
},
}
console.log(hd.teacher.name)数组结构
let lessons = [
{
title: '媒体查询响应式布局',
category: 'css',
click: 199,
},
{
title: 'FLEX 弹性盒模型',
category: 'css',
click: 12,
},
{
title: 'MYSQL多表查询随意操作',
category: 'mysql',
click: 89,
},
]
console.log(lessons[0].title)序列化
- 参数 1:要转
json的数组对象 - 参数 2:需要保留的属性,可选
- 参数 3:缩进字符
序列化是将 json 转换为字符串,一般用来向其他语言传输使用。根据第二个参数指定保存的属性。第三个是参数用来控制 TAB 数量,如果字符串则为前导字符。
let hd = {
title: '刀刀',
url: 'daodao.com',
teacher: {
name: '杜一刀',
},
}
console.log(JSON.stringify(hd))
//{"title":"刀刀","url":"daodao.com","teacher":{"name":"杜一刀"}}let hd = {
title: '刀刀',
url: 'daodao.com',
teacher: {
name: '杜一刀',
},
}
console.log(JSON.stringify(hd, ['title', 'url']))
//{"title":"刀刀","url":"daodao.com"}let hd = {
title: '刀刀',
url: 'daodao.com',
teacher: {
name: '杜一刀',
},
}
console.log(JSON.stringify(hd, null, 4))为数据添加 toJSON 方法来自定义返回格式
let hd = {
title: '刀刀',
url: 'daodao.com',
teacher: {
name: '杜一刀',
},
toJSON: function () {
return {
title: this.url,
name: this.teacher.name,
}
},
}
console.log(JSON.stringify(hd)) //{"title":"daodao.com","name":"杜一刀"}反序列化
使用 JSON.parse 将字符串 json 解析成对象。使用第二个参数函数来对返回的数据二次处理。
let hd = {
title: '刀刀',
url: 'daodao.com',
teacher: {
name: '杜一刀',
},
}
let jsonStr = JSON.stringify(hd)
console.log(JSON.parse(jsonStr))let hd = {
title: '刀刀',
url: 'daodao.com',
teacher: {
name: '杜一刀',
},
}
let jsonStr = JSON.stringify(hd)
console.log(
JSON.parse(jsonStr, (key, value) => {
if (key == 'title') {
return `[推荐] ${value}`
}
return value
}),
)Reflect
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法
Reflect并非一个构造函数,所以不能通过new运算符对其进行调用