JavaScript Object
Object
在JavaScript
中一切都是基于对象来完成的,Js
中大部分类型都是对象,如 String/Number/Math/RegExp/Date
等等。
我们可以自定义对象,自定义对象中包含方法以及属性。
对象最重要的一点就是将功能进行整合,方便操纵数据,在对象中拥有属性以及方法。
在Js
中,主要创建对象的方式有以下两种。
1.使用字面量形式创建对象
2.使用构造函数创建对象
基础知识
OOP
面向对象三大特征,封装继承与多态。
对象是属性和方法的集合即封装
将复杂功能隐藏在内部,只开放给外部少量方法,更改对象内部的复杂逻辑不会对外部调用造成影响即抽象
继承是通过代码复用减少冗余代码
根据不同形态的对象产生不同结果即多态
基本声明
以下将示例将演示使用字面量形式进行对象的创建
注意,字面量对象定义必须是由{}
进行包裹的键值对组合,并且键只能是String
类型或Symbol
类型,在某些情况下可以进行简写。
<script>"use strict"; let obj = { username: "云崖", age: 18, gender: "男", show: function() { console.log(`姓名:${this.username},年龄${this.age},性别:${this.gender}`); }, } obj.show() // 调用方法 </script>
属性方法简写。
<script>"use strict"; let username = "云崖"; let age = 18; let gender = "男"; let show = function () { console.log(`姓名:${this.username},年龄${this.age},性别:${this.gender}`); } let obj = { username, age, gender, show, } obj.show() // 调用方法 </script>
如果键是一个变量名,需要用[]
进行包裹。另外注意在访问时不能使用.
语法进行访问。
<script>"use strict"; let username = Symbol(); let obj = { [username]: "云崖", age: 18, gender: "男", show: function() { console.log(`姓名:${this[username]},年龄${this.age},性别:${this.gender}`); }, } obj.show() // 调用方法 </script>
属性操作
我们可以使用.
语法来对对象进行属性或方法的操作,也可以使用[string]
语法来对对象进行属性或方法的操作。
以下示例将展示使用.
语法来对对象属性或方法进行增删改查的操作。
<script>"use strict"; let obj = { } // 增加/修改 属性或方法 obj.username = "云崖"; obj.age = 18; obj.gender = "男"; obj.show = function () { console.log( `姓名:${this.username},年龄${this.age},性别:${this.gender}`); } // 访问 属性或方法 console.log(obj.username); obj.show(); // 方法调用 // 删除 属性或方法 delete obj.name; delete obj.show; </script>
以下示例将展示使用[string]
语法来对对象属性或方法进行增删改查的操作。
<script>"use strict"; let obj = { } // 增加/修改 属性或方法 obj["username"] = "云崖"; obj["age"] = 18; obj["gender"] = "男"; obj["show"] = function () { console.log( `姓名:${this.username},年龄${this.age},性别:${this.gender}`); } // 访问 属性或方法 console.log(obj["username"]); obj["show"](); // 方法调用 // 删除 属性或方法 delete obj["username"]; delete obj["show"]; </script>
引用特性
对象和函数、数组一样是引用类型,即复制只会复制引用地址。
<script>"use strict"; let obj = { username:"云崖", } let NewObj = obj; console.log(obj === NewObj); // true </script>
当一个对象作为参数进行传递时,也是引用同一个内存地址。
<script>"use strict"; let obj = { username:"云崖", } function show(object){ console.log(object === obj); // true } show(obj); </script>
this指向
在对象中,this
的指向始终为当前对象,但是并不推荐将箭头函数定义为对象方法。
关于this
指向在之前的博客有详细介绍,可以自行去看。
<script>"use strict"; let obj = { username:"云崖", show:function (){ console.log(this); // 当前对象obj }, arrow:()=> console.log(this), // window对象 } obj.show(); obj.arrow(); </script>
展开语法
数组有展开语法,对象当然也有,展开语法就是...
语法。
以下示例将演示使用展开语法进行对象的合并操作。
<script>"use strict"; let obj = { username: "云崖", } let NewObj = { ...obj, age: 18, }; console.log(NewObj); // {username: "云崖", age: 18} </script>
函数参数的使用。
<script>"use strict"; function upload(params) { let config = { // 默认接收格式和大小 type: "*.jpeg,*.png", size: 10000 }; // 进行更新 params = { ...config, ...params }; console.log(params); } upload({ size: 999 }); </script>
对象转换
基础知识
对象直接参与计算时,系统会根据计算的场景在 string/number/default
间转换。
如数组,字符串,数字等,他们的通用转换方法都来自于Object
对象。
如果声明需要字符串类型,调用顺序为
toString > valueOf
如果场景需要数值类型,调用顺序为
valueOf > toString
声明不确定时使用
default
,大部分对象的default
会当数值使用
下面的数值对象会在数学运算时转换为 number
。
<script>"use strict"; let number = new Number(1); console.log(number + 3); //4 </script>
如果参数字符串运长时会转换为 string
。
<script>"use strict"; let number = new Number(1); console.log(number + "3"); //13 </script>
下面当不确定转换声明时使用 default
,大部分default
转换使用 number
转换。
<script>"use strict"; let number = new Number(1); console.log(number == "1"); // true </script>
Symbol.toPrimitive
内部自定义Symbol.toPrimitive()
方法用来处理所有的转换场景。
<script>"use strict"; let obj = { num: 100, [Symbol.toPrimitive]: function () { return this.num; }, // 由于这是一个属性名,所以作为键必须使用[]进行包裹 } console.log(obj + 10); // 110 </script>
valueOf/toString
可以自定义valueOf()
与 toString()
方法用来转换,转换并不限制返回类型。
这里是使用了覆写方法的特性。
<script>"use strict"; let obj = { num: 100, str:"云崖", valueOf: function () { console.log("valueOf"); return this.num; }, toString: function () { console.log("toString"); return this.str; } } console.log(obj + 10); // 110 console.log(`${obj}`); // 云崖 </script>
解构赋值
基本使用
下面是基本使用语法
<script>"use strict"; // 严格模式下需要加上声明,var/let/const // 完整写法 let {name:n,age:a} = {name:"云崖",age:18} console.log(n); // 云崖 console.log(a); // 18 // 简写,前提是接收变量和键名一致 let {name,age} = {name:"云崖",age:18} console.log(name); // 云崖 console.log(age); // 18 </script>
占位使用
可以不写占位符单纯用,
进行分割,也可以使用_
作为占位符。
<script>"use strict"; // 严格模式下需要加上声明,var/let/const // 不写或者使用 _ 做占位符 let {name,_} = {name:"云崖",age:18} console.log(name); // 云崖 </script>
嵌套解构
可以操作多层复杂数据结构
<script>"use strict"; // 严格模式下需要加上声明,var/let/const // 代表取出hobby中的first,变量就是first,注意{} let { name, hobby: { first } } = { name: "云崖", age: 18, hobby: { first: "篮球", last: "足球", } } console.log(name); // 云崖 console.log(first); // 篮球 </script>
默认设值
可以为一个变量设置默认值,如果解构的对象中有该变量作为键名则取消默认值采用对象中键对应的值。
<script>"use strict"; // 严格模式下需要加上声明,var/let/const let { name, hobby: { first }, love = "狂三", } = { name: "云崖", age: 18, hobby: { first: "篮球", last: "足球", } } console.log(name); // 云崖 console.log(first); // 篮球 console.log(love); // 狂三, Object中没有love键,故使用默认值 </script>
函数参数
数组参数的使用
<script>"use strict"; function show([x, y]) { console.log(x); // 我是x console.log(y); // 我是y } show(["我是x","我是y"]); </script>
对象参数的使用
<script>"use strict"; function show(a, { x, y, z = "默认值z" }) { console.log(a); // 我是a console.log(x); // 我是x console.log(y); // 我是y console.log(z); // 默认值z } show("我是a", { x: "我是x", y: "我是y", }); </script>
属性管理
添加属性
可以为对象添加属性。
<script>"use strict"; let obj = {}; obj.username = "云崖"; </script>
删除属性
使用delete
可以删除非保护的属性。
<script>"use strict"; let obj = {username:"云崖",}; delete obj.username; </script>
自身属性检测
使用hasOwnProperty
可以检测对象自身包含的属性或方法,不检测原型链上继承的属性或方法。
<script>"use strict"; let obj = {username:"云崖",}; console.log(obj.hasOwnProperty("username")); // true </script>
原型继承检测
使用in
可以在原型对象上进行检测。
<script>"use strict"; let father = {username:"云崖",}; let son = {}; Object.setPrototypeOf(son,father); // 设置father为son的新原型 console.log(son.hasOwnProperty(son,father)); // false console.log("username" in son); // true </script>
属性复制
类似于jQuery.extend()
,我们可以使用Object.assign
来从一个或多个对象进行属性复制,方法添加等。
<script>"use strict"; let obj = { username: "云崖", age: 18, }; let hobby = { first: "篮球", last: "足球", }; Object.assign(obj, { gender: "男", }, hobby) // 使obj具有gender与hobby中的所有属性 console.log(obj); // {username: "云崖", age: 18, gender: "男", first: "篮球", last: "足球"} </script>
计算属性
对象属性可以通过表达式计算定义,这在动态设置属性或执行属性方法时很好用。
<script>"use strict"; let id = 1; let obj = { [`id-${id++}`]:id, [`id-${id++}`]:id, [`id-${id++}`]:id, [`id-${id++}`]:id, }; console.log(obj); </script>
传址操作
对象是引用类型赋值是传址操作,后面会介绍对象的深、浅拷贝操作。
<script>"use strict"; let obj = { username: "云崖", }; let hobby = { first: "篮球", last: "足球", }; obj.hobby = hobby; console.log(obj.hobby); // {first: "篮球", last: "足球"} obj.hobby.first = "排球"; console.log(hobby); // {first: "排球", last: "足球"} 可以看到原对象跟随引用一起改变 </script>
遍历对象
迭代器方法
使用系统提供的API可以方便获取对象属性与值,返回值均是Array
类型。
<script>"use strict"; let obj = { username: "云崖", age: 18, gender: "男", } console.log(Object.keys(obj)); // 获取键 (3) ["username", "age", "gender"] console.log(Object.values(obj)); // 获取值 (3) ["云崖", 18, "男"] console.log(Object.entries(obj)); // 键值对 (3) [["username", "云崖"],["age", 18],["gender", "男"]] </script>
for/in
使用for/in
拿到的始终是key
。
<script>"use strict"; let obj = { username: "云崖", age: 18, gender: "男", } for (let key in obj) { console.log(key); // username age gender } </script>
for/of
for/of
用于遍历迭代对象,不能直接操作对象。但Object
对象的keys/values/entries
方法返回的是迭代对象。
<script>"use strict"; let obj = { username: "云崖", age: 18, gender: "男", } for (let [key,value] of Object.entries(obj)) { console.log(key); // username age gender console.log(value); // 云崖 18 男 } </script>
对象拷贝
基本上所有的拷贝对于引用类型来说都是属于浅拷贝,关于深浅拷贝在Python
基础中已经详细介绍,不熟悉的可以去看一看。
浅拷贝
使用for/in
执行对象拷贝。
<script>"use strict"; let obj = { username: "云崖", age: 18, gender: "男", hobby: { first: "篮球", last: "足球", } } let newObj = {}; for (let key in obj) { newObj[key] = obj[key]; } obj.username = "YunYa"; obj.hobby.first = "排球"; console.log(obj); // {username: "YunYa", age: 18, gender: "男", hobby: {first: "排球", last: "足球"}} console.log(newObj); // {username: "云崖", age: 18, gender: "男", hobby: {first: "排球", last: "足球"}} // 可以看出是浅拷贝,值类型不发生变化,但是可变类型会跟随发生变化 </script>
Object.assign()
方法可简单的实现浅拷贝,它是将两个对象的属性叠加后面对象属性会覆盖前面对象同名属性。
<script>"use strict"; let obj = { username: "云崖", age: 18, gender: "男", hobby: { first: "篮球", last: "足球", } } let newObj = {}; Object.assign(newObj,obj); obj.username = "YunYa"; obj.hobby.first = "排球"; console.log(obj); // {username: "YunYa", age: 18, gender: "男", hobby: {first: "排球", last: "足球"}} console.log(newObj); // {username: "云崖", age: 18, gender: "男", hobby: {first: "排球", last: "足球"}} // 可以看出是浅拷贝,值类型不发生变化,但是可变类型会跟随发生变化 </script>
使用展开语法也可以实现浅拷贝。
<script>"use strict"; let obj = { username: "云崖", age: 18, gender: "男", hobby: { first: "篮球", last: "足球", } } let newObj = {...obj}; obj.username = "YunYa"; obj.hobby.first = "排球"; console.log(obj); // {username: "YunYa", age: 18, gender: "男", hobby: {first: "排球", last: "足球"}} console.log(newObj); // {username: "云崖", age: 18, gender: "男", hobby: {first: "排球", last: "足球"}} // 可以看出是浅拷贝,值类型不发生变化,但是可变类型会跟随发生变化 </script>
深拷贝
下面将演示使用深拷贝函数进行拷贝,深拷贝是将原拷贝对象全部拷贝出来,是一份独立的个体。
<script>"use strict"; let obj = { username: "云崖", age: 18, gender: "男", hobby: { first: "篮球", last: "足球", } } function deepCopy(object) { let obj = object instanceof Array ? [] : {}; for (const [k, v] of Object.entries(object)) { obj[k] = typeof v == "object" ? deepCopy(v) : v; } return obj; } let newObj = deepCopy(obj); obj.username = "YunYa"; obj.hobby.first = "排球"; console.log(obj); // {username: "YunYa", age: 18, gender: "男", hobby: {first: "排球", last: "足球"}} console.log(newObj); // {username: "云崖", age: 18, gender: "男", hobby: {first: "篮球", last: "足球"}} // 深拷贝,无论多少层的引用对象都是独立内存空间。 </script>
构建函数
对象可以通过内置或自定义的构造函数创建。
工厂函数
在函数中返回对象的函数称为工厂函数,工厂函数有以下优点:
减少重复创建相同类型对象的代码
修改工厂函数的方法影响所有同类对象
如果不使用工厂函数,我们通过字面量创建对象会变得十分繁琐。
<script>"use strict"; let u1 = { username: "云崖", show: function () { return this.username; } }; let u2 = { username: "Yunya", show: function () { return this.username; } }; console.log(u1.show()); // 云崖 console.log(u2.show()); // Yunya </script>
工厂的作用就是重复加工,我们可以多次利用该函数返回相似的对象。
<script>"use strict"; function user(username) { return { username, show: function () { return this.username; }, } } let u1 = user("云崖"); console.log(u1.show()); // 云崖 let u2 = user("Yunya"); console.log(u2.show()); // Yunya </script>
构造函数
构造函数的作用也是用于创建对象,它的上下文为新的对象实例。
构造函数名每个单词首字母大写即
Pascal
命名规范
this
指当前创建的对象不需要返回
this
系统会自动完成需要使用
new
关键词生成对象
<script>"use strict"; function User(username, age, gender) { this.username = username; this.age = age; this.gender = gender; this.show = function () { return `姓名:${this.username}\n年龄:${this.age}\n性别:${this.gender}`; }; } let u1 = new User("云崖", 18, "男"); // 使用new来实例化出对象 let u2 = new User("Yunya", 18, "男"); console.log(u1.show()); console.log(u2.show()); </script>
严格模式
在严格模式下方法中的this
值为undefined
,这是为了防止无意的修改window
对象。
this
的指向在于调用者所处的环境,如下所示。
<script>"use strict"; function User() { this.show = function () { console.log(this); }; } let u1 = new User(); u1.show(); // User对象。 let u2 = u1.show; u2(); // 严格模式下为undefined,这是由于u2所处环境在window下 </script>
内置构造
其实之前学习过的使用构造函数创建对象的方式非常多,如创建Array
对象,一句话,但凡是用new
创建出的对象都是使用构造函数创建出来的。
<script>"use strict"; const num = new Number(99); console.log(num.valueOf()); const string = new String("hello,world"); 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 obj = new Object(); obj.name = "hello,wrold"; console.log(obj); </script>
字面量创建的对象,内部也是调用了Object
构造函数。
<script>"use strict"; // 字面量创建会隐式调用构造函数 let str_obj = { username:"云崖", }; console.log(str_obj.constructor); // ƒ Object() { [native code] } // 显式使用构造函数创建 let con_obj = new Object(); con_obj.username = "Yunya"; console.log(con_obj.constructor); // ƒ Object() { [native code] } // 可以看到无论是用字面量或new都是使用Object的构造函数进行创建 </script>
抽象特性
抽象可以理解为对内开放,对外隐藏。
即内部可以随意调用但是外部只能通过接口进行调用。
封装解释
不应该在外部进行直接访问。
<script>"use strict"; function User(username, age, gender) { this.username = username; this.age = age; this.gender = gender; this.getGender = function () { return this.gender; }; } let u1 = new User("云崖", 18, "男"); console.log(u1.getGender()); // 外部可以通过接口调用 console.log(u1.gender); // 外部不应该直接调用,故此需要做封装 </script>
抽象封装
没有绝对意义上的封装,但是我们可以提高外部访问的代价。
<script>"use strict"; function User(username, age, gender) { this.data = { // 进行封装 username, age, gender, } this.getGender = function () { return this.data.gender; }; } let u1 = new User("云崖", 18, "男"); console.log(u1.getGender()); // 外部可以通过接口调用 console.log(u1.gender); // 外部访问不到了 undefined </script>
属性特征
属性特征包括是否可以删除,是否可以修改,是否可以被循环,属性默认值等。
我们可以利用一些接口方法为一个属性进行详细的设置。
特性 | 说明 | 默认值 |
---|---|---|
configurable | 能否使用delete、能否需改属性特性、或能否修改访问器属性 | true |
enumerable | 对象属性是否可通过for-in循环,或Object.keys() 读取 | true |
writable | 对象属性是否可修改 | true |
value | 对象属性的默认值 | undefined |
查看特征
使用Object.getOwnPropertyDescriptor()
方法可查看对象下某一属性的特征。
<script>"use strict"; function User(username, age, gender) { this.username = username; this.age = age; this.gender = gender; this.show = function () { return `姓名:${this.username}\n年龄:${this.age}\n性别:${this.gender}`; }; } let u1 = new User("云崖", 18, "男"); console.log(Object.getOwnPropertyDescriptor(u1, "username")); // 查看u1的username属性特征,注意是string格式 // {value: "云崖", writable: true, enumerable: true, configurable: true} </script>
使用 Object.getOwnPropertyDescriptors()
方法查看对象中所有属性的特征。
<script>"use strict"; function User(username, age, gender) { this.username = username; this.age = age; this.gender = gender; this.show = function () { return `姓名:${this.username}\n年龄:${this.age}\n性别:${this.gender}`; }; } let u1 = new User("云崖", 18, "男"); console.log(Object.getOwnPropertyDescriptors(u1)); // 查看u1下所有属性/方法的特征 // {username: {…}, age: {…}, gender: {…}, show: {…}} // age: {value: 18, writable: true, enumerable: true, configurable: true} // gender: {value: "男", writable: true, enumerable: true, configurable: true} // show: {writable: true, enumerable: true, configurable: true, value: ƒ} // username: {value: "云崖", writable: true, enumerable: true, configurable: true} </script>
设置特征
使用Object.defineProperty()
方法修改属性特性,通过下面的设置属性gender
将不能被遍历、删除、修改。
<script>"use strict"; function User(username, age, gender) { this.username = username; this.age = age; this.gender = gender; this.show = function () { return `姓名:${this.username}\n年龄:${this.age}\n性别:${this.gender}`; }; // 为实例化对象的gender属性进行特征配置 Object.defineProperty(this, "gender", { value: "male", // 此处修改value比上面实例化的优先级更高 writable: false, // 不可修改 enumerable: false, // 不可遍历 configurable: false, // 不可再进行特征配置,不可删除,不可修改访问属性的设置 }); } let u1 = new User("云崖", 18, "男"); console.log(u1.gender); // male 说明特征配置的优先级较高 // 不可修改,严格模式下抛出异常 u1.gender = "男"; // 不可遍历 console.log(Object.keys(u1)); // (3) ["username", "age", "show"] // 不可删除,严格模式下抛出异常 delete u1.gender // 不可再进行特征配置,严格模式下抛出异常 Object.defineProperty(u1, "gender", { writable: true, // 不可修改 }); </script>
使用 Object.defineProperties()
可以一次设置多个属性,具体参数和上面介绍的一样。
<script>"use strict"; function User(username, age, gender) { this.username = username; this.age = age; this.gender = gender; this.show = function () { return `姓名:${this.username}\n年龄:${this.age}\n性别:${this.gender}`; }; // 为实例的属性进行特征配置 Object.defineProperties(this, { gender: { value: "male", // 此处修改value比上面实例化的优先级更高 writable: false, // 不可修改 enumerable: false, // 不可遍历 configurable: false, // 不可再进行特征配置,不可删除,不可修改访问属性的设置 }, // 注意逗号 username: { value: "Yunya", writable: false, // 不可修改 enumerable: false, // 不可遍历 configurable: false, // 不可再进行特征配置,不可删除,不可修改访问属性的设置 }, }); } let u1 = new User("云崖", 18, "男"); console.log(u1.username); // Yunya console.log(u1.gender); // male </script>
禁止添加
Object.preventExtensions()
禁止向对象添加新属性
<script>"use strict"; function User(username, age, gender) { this.username = username; this.age = age; this.gender = gender; this.show = function () { return `姓名:${this.username}\n年龄:${this.age}\n性别:${this.gender}`; }; // 该对象的实例禁止添加新属性 Object.preventExtensions(this); } let u1 = new User("云崖", 18, "男"); // 严格模式下抛出异常 u1.hobby = "篮球"; </script>
Object.isExtensible()
判断是否能向对象中添加属性
<script>"use strict"; function User(username, age, gender) { this.username = username; this.age = age; this.gender = gender; this.show = function () { return `姓名:${this.username}\n年龄:${this.age}\n性别:${this.gender}`; }; // 该对象的实例禁止添加新属性 Object.preventExtensions(this); } let u1 = new User("云崖", 18, "男"); // 判断是否可以添加新属性 console.log(Object.isExtensible(u1)); // false </script>
封闭对象
Object.seal()
方法封闭一个对象,阻止添加新属性并将所有现有属性标记为 configurable: false
。
<script>"use strict"; function User(username, age, gender) { this.username = username; this.age = age; this.gender = gender; this.show = function () { return `姓名:${this.username}\n年龄:${this.age}\n性别:${this.gender}`; }; // 封闭实例对象,不准删,不准改,不准修改访问器属性 Object.seal(this); } let u1 = new User("云崖", 18, "男"); // 判断是否可以添加新属性 console.log(Object.isExtensible(u1)); // false // 不准删,删除在严格模式下抛出异常 delete u1.username; </script>
Object.isSealed()
如果对象是密封的则返回 true
。
<script>"use strict"; function User(username, age, gender) { this.username = username; this.age = age; this.gender = gender; this.show = function () { return `姓名:${this.username}\n年龄:${this.age}\n性别:${this.gender}`; }; // 封闭实例对象,不准删,不准改,不准修改访问器属性 Object.seal(this); } let u1 = new User("云崖", 18, "男"); // 判断是否是封闭对象 console.log(Object.isSealed(u1)); // true </script>
冻结对象
Object.freeze()
冻结对象后不允许添加、删除、修改属性,writable
、configurable
都标记为false
。
<script>"use strict"; function User(username, age, gender) { this.username = username; this.age = age; this.gender = gender; this.show = function () { return `姓名:${this.username}\n年龄:${this.age}\n性别:${this.gender}`; }; // 冻结实例对象 Object.freeze(this); } let u1 = new User("云崖", 18, "男"); // 严格模式下抛出异常 delete u1.username; </script>
Object.isFrozen()
方法判断一个对象是否被冻结。
<script>"use strict"; function User(username, age, gender) { this.username = username; this.age = age; this.gender = gender; this.show = function () { return `姓名:${this.username}\n年龄:${this.age}\n性别:${this.gender}`; }; // 冻结实例对象 Object.freeze(this); } let u1 = new User("云崖", 18, "男"); // 判断对象是否被冻结 console.log(Object.isFrozen(u1)); // true </script>
属性访问器
getter()
方法用于获得属性值,setter()
方法用于设置属性,这是Js
提供的存取器特性即使用函数来管理属性。
用于避免错误的赋值
需要动态监测值的改变
属性只能在访问器和普通属性任选其一,不能共同存在
getter/setter
如下示例将展示如何控制访问年龄以及设置年龄。
<script>"use strict"; function User(username, age, gender) { return { data: { username, age, gender, }, set age(value) { // value是设置值 if (typeof value != "number" || value > 100 || value < 10) { throw new Error("年龄格式错误"); } // this.age = value; 会陷入无限递归 this.data.age = value; }, get age() { return `年龄是:${this.data.age}`; }, } } let u1 = User("云崖", 18, "男"); console.log(u1.age); // 年龄是:18 u1.age = "100"; // Uncaught Error: 年龄格式错误 </script>
以下示例将演示将一个方法作为属性调用返回。
<script>"use strict"; 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; //无效 </script>
访问器描述符
使用defineProperties
可以模拟定义私有属性,从而使用面向对象的抽象特性。
构造函数中的访问器描述符。
<script>"use strict"; function User(username, age, gender) { this.data = { username, age, gender }; Object.defineProperties(this,{ username: { get() { return this.data.username; }, set(value) { if (value.trim() == "") throw new Error("无效的用户名"); this.data.name = value; }, }, age: { get() { return this.data.age; }, set(value) { if (typeof value != "number" || value > 100 || value < 10) { throw new Error("年龄格式错误"); } this.data.age = value; }, }, }); } let u1 = new User("云崖", 18, "男"); console.log(u1.age); u1.age = "100"; // Uncaught Error: 年龄格式错误 </script>
class
语法糖定义,这样结构更加清晰。
<script>"use strict"; class User { constructor(username, age, gender) { // 构造函数 this.data = { username, age, gender }; } get username() { return this.data.username; } set username(value) { if (value.trim() == "") throw new Error("无效的用户名"); this.data.name = value; } get age() { return this.data.age; } set age(value) { if (typeof value != "number" || value > 100 || value < 10) { throw new Error("年龄格式错误"); } this.data.age = value; } } let u1 = new User("云崖", 18, "男"); console.log(u1.age); u1.age = "100"; // Uncaught Error: 年龄格式错误 </script>
代理拦截
代理(拦截器)是对象的访问控制,setter/getter
是对单个对象属性的控制,而代理是对整个对象的控制。
读写属性时代码更简洁
对象的多个属性控制统一交给代理完成
严格模式下
set
必须返回布尔值
使用方法
通过实例化出Proxy
对象作为拦截器,接收两个参数,第一个参数是对象,第二个参数是访问时和设置时。
<script>"use strict"; let obj = { username: "云崖", }; const proxy = new Proxy(obj, { get(obj, property) { // 访问obj对象属性时执行的方法 console.log("run...proxy.get()"); return obj[property]; }, set(obj,property,value){ // 为obj对象设置属性时执行的方法 console.log("run...proxy.set()"); obj[property] = value; return true; // 代表设置成功 }, }); console.log(proxy.username); // run...proxy.get() 云崖 proxy.age = 18; // run...proxy.set() </script>
代理函数
如果代理以函数方式执行时,会执行代理中定义 apply
方法。
参数说明:函数,上下文对象,参数
下面使用 apply()
方法计算函数执行时间。
Ps:你可以把它当做装饰器使用。
<script>"use strict"; 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]); // 参数传递必须是数组。 </script>
双向绑定
下面通过代理实现vue
等前端框架的数据绑定特性特性。
<!DOCTYPE html> <html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style></style> </head><body><input type="text" v-model="title" /> <input type="text" v-model="title" /> <div v-bind="title"></div> </body> <script>"use strict"; 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(); </script></html>
表单验证
使用代理来进行表单验证,验证规则写在标签属性中。
<!DOCTYPE html> <html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> body { padding: 50px; background: #34495e; } input { border: solid 10px #ddd; height: 30px; } .error { border: solid 10px red; } </style> </head><body><input type="text" validate rule="max:12,min:3" /> <input type="text" 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></html>
JSON
-
json
是一种轻量级的数据交换格式,易于人阅读和编写。 -
使用
json
数据格式是替换xml
的最佳方式,主流语言都很好的支持json
格式。所以json
也是前后台传输数据的主要格式。 -
json
标准中要求使用双引号包裹属性,虽然有些语言不强制,但使用双引号可避免多程序间传输发生错误语言错误的发生。
序列化
序列化是将 json
转换为字符串,一般用来向其他语言传输使用。
使用JSON.stringify()
方法可将对象转换为json
字符串。
参数1:序列化的对象
参数2:指定保存的属性,如果为null则代表所有。
参数3:控制
table
的格式数量,是几个空格。
<script>"use strict"; let obj = {username:"云崖",age:18,gender:"男"}; let json_obj = JSON.stringify(obj,["username","age"],4); console.log(json_obj); /* 只要username,age,四个空格。 { "username": "云崖", "age": 18 } */</script>
反序列化
将json
字符串转换为Js
数据类型,使用Json.parse()
进行操作。
使用第二个参数函数来对返回的数据二次处理。
<script>"use strict"; let obj = { username: "云崖", age: 18, gender: "男" }; let json_obj = JSON.stringify(obj); // json字符串 let new_obj = JSON.parse(json_obj, (key, value) => { if (key == "gender") { return "男子汉"; } return value; }); console.log(new_obj); // {username: "云崖", age: 18, gender: "男子汉"} </script>