Appearance
对象
基础知识
对象是包括属性与方法的数据类型,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: [
{ lesson: "js", score: 99 },
{ lesson: "mysql", score: 85 }
],
average() {
const total = this.grade.reduce((t, a) => t + a.score, 0);
return this.name + ":" + total / grade.length + "分";
}
};
console.log(user.average());
OOP
- 对象是属性和方法的集合即封装
- 将复杂功能隐藏在内部,只开放给外部少量方法,更改对象内部的复杂逻辑不会对外部调用造成影响即抽象
- 继承是通过代码复用减少冗余代码
- 根据不同形态的对象产生不同结果即多态
基本声明
使用字面量形式声明对象是最简单的方式
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()); //斑马兽
其实字面量形式在系统内部也是使用构造函数 new Object
创建的,后面会详细介绍构造函数。
let bm = {};
let banmashou = new Object();
console.log(bm, banmashou);
console.log(bm.constructor);
console.log(banmashou.constructor);
操作属性
使用点语法获取
let user = {
name: "斑马兽"
};
console.log(user.name);
使用[]
获取
console.log(user["name"]);
可以看出使用.
操作属性更简洁,[]
主要用于通过变量定义属性的场景
let user = {
name: "斑马兽"
};
let property = "name";
console.log(user[property]);
如果属性名不是合法变量名就必须使用扩号的形式了
let user = {};
user["my-age"] = 28;
console.log(user["my-age"]);
对象和方法的属性可以动态的添加或删除。
const bm = {
name: "斑马兽"
};
bm.age = "10";
bm.show = function() {
return `${this.name}已经${this.age}岁了`;
};
console.log(bm.show());
console.log(bm);
delete bm.show;
delete bm.age;
console.log(bm);
console.log(bm.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 bm = { name: "斑马兽" };
let cms = bm;
cms.name = "bmcms";
console.log(bm.name); //bmcms
对象做为函数参数使用时也不会产生完全赋值,内外共用一个对象
let user = { age: 22 };
function bm(user) {
user.age += 10;
}
bm(user);
console.log(user.age); //32
对多的比较是对内存地址的比较所以使用 ==
或 ===
一样
let bm = {};
let cms = {};
let banmashou = bm;
console.log(bm == banmashou); //true
console.log(bm === banmashou); //true
console.log(bm === cms); //false
this
this
指当前对象的引用,始终建议在代码内部使用this
而不要使用对象名,不同对象的 this 只指向当前对象。
下例是不使用 this
时发生的错误场景
- 删除了
bm
变量,但在函数体内还在使用bm
变量造成错误 - 使用
this
后始终指向到引用地址,就不会有这个问题
let bm = {
name: "斑马兽",
show() {
return bm.name;
}
};
let cms = bm;
bm = null;
console.log(cms.show()); //Errors
改用this
后一切正常
let bm = {
name: "斑马兽",
show() {
return this.name;
}
};
let cms = bm;
bm = null;
console.log(cms.show()); //斑马兽
展开语法
使用...
可以展示对象的结构,下面是实现对象合并的示例
let bm = { name: "斑马兽", web: "banmashou.com" };
let info = { ...bm, site: "bmcms" };
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 banmashou = new Number(1);
console.log(banmashou + 3); //4
如果参数字符串运长时会转换为 string
let banmashou = new Number(1);
console.log(banmashou + "3"); //13
下面当不确定转换声明时使用 default
,大部分default
转换使用 number
转换。
let banmashou = new Number(1);
console.log(banmashou == "1"); //true
Symbol.toPrimitive
内部自定义Symbol.toPrimitive
方法用来处理所有的转换场景
let bm = {
num: 1,
[Symbol.toPrimitive]: function() {
return this.num;
}
};
console.log(bm + 3); //4
valueOf/toString
可以自定义valueOf
与 toString
方法用来转换,转换并不限制返回类型。
let bm = {
name: "斑马兽",
num: 1,
valueOf: function() {
console.log("valueOf");
return this.num;
},
toString: function() {
console.log("toString");
return this.name;
}
};
console.log(bm + 3); //valueOf 4
console.log(`${bm}喜欢编程`); //toString 斑马兽喜欢编程
解构赋值
解构是一种更简洁的赋值特性,可以理解为分解一个数据的结构,在数组章节已经介绍过。
- 建设使用
var/let/const
声明
基本使用
下面是基本使用语法
//对象使用
let info = {name:'斑马兽',url:'banmashou.com'};
let {name:n,url:u} = info
console.log(n); // 斑马兽
//如果属性名与变量相同可以省略属性定义
let {name,url} = {name:'斑马兽',url:'banmashou.com'};
console.log(name); // 斑马兽
函数返回值直接解构到变量
function bm() {
return {
name: '斑马兽',
url: 'banmashou.com'
};
}
let {name: n,url: u} = bm();
console.log(n);
函数传参
"use strict";
function bm({ name, age }) {
console.log(name, age); //斑马兽 18
}
bm({ name: "斑马兽", age: 18 });
系统函数解构练习,这没有什么意义只是加深解构印象
const {random} =Math;
console.log(random());
严格模式
非严格模式可以不使用声明指令,严格模式下必须使用声明。所以建议使用 let 等声明。
// "use strict";
({name,url} = {name:'斑马兽',url:'banmashou.com'});
console.log(name, url);
还是建议使用let等赋值声明
"use strict";
let { name, url } = { name: "斑马兽", url: "banmashou.com" };
console.log(name, url);
简洁定义
如果属性名与赋值的变量名相同可以更简洁
let web = { name: "斑马兽",url: "banmashou.com" };
let { name, url } = web;
console.log(name); //斑马兽
只赋值部分变量
let [,url]=['斑马兽','banmashou.com'];
console.log(url);//banmashou.com
let {name}= {name:'斑马兽',url:'banmashou.com'};
console.log(name); //斑马兽
可以直接使用变量赋值对象属性
let name = "斑马兽",url = "banmashou.com";
//标准写法如下
let bm = { name: name, url: url };
console.log(bm); //{name: "斑马兽", url: "banmashou.com"}
//如果属性和值变量同名可以写成以下简写形式
let opt = { name, url };
console.log(opt); //{name: "斑马兽", url: "banmashou.com"}
嵌套解构
可以操作多层复杂数据结构
const bm = {
name:'斑马兽',
lessons:{
title:'JS'
}
}
const {name,lessons:{title}} = bm;
console.log(name,title); //斑马兽 JS
默认值
为变量设置默认值
let [name, site = 'bmcms'] = ['斑马兽'];
console.log(site); //bmcms
let {name,url,user='斑马'}= {name:'斑马兽',url:'banmashou.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 bm([a, b]) {
console.log(a, b);
}
bm(['斑马兽', 'bmcms']);
对象参数使用方法
function bm({name,url,user='斑马'}) {
console.log(name,url,user);
}
bm({name:'斑马兽','url':'banmashou.com'}); //斑马兽 banmashou.com 斑马
对象解构传参
function user(name, { sex, age } = {}) {
console.log(name, sex, age); //斑马兽 男 18
}
user("斑马兽", { sex: "男", age: 18 });
属性管理
添加属性
可以为对象添加属性
let obj = {name: "斑马兽"};
obj.site = "banmashou.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 bm = {
web: "banmashou.com"
};
//设置bm为obj的新原型
Object.setPrototypeOf(obj, bm);
console.log(obj);
console.log("web" in obj); //true
console.log(obj.hasOwnProperty("web")); //false
获取属性名
使用 Object.getOwnPropertyNames
可以获取对象的属性名集合
let bm = { name: '斑马兽', year: 2010 }
const names = Object.getOwnPropertyNames(bm)
console.log(names)
// ["name", "year"]
assign
以往我们使用类似jQuery.extend
等方法设置属性,现在可以使用 Object.assign
静态方法
从一个或多个对象复制属性
"use strict";
let bm = { a: 1, b: 2 };
bm = Object.assign(bm, { f: 1 }, { m: 9 });
console.log(bm); //{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 bm = {
stu: user
};
bm.stu.name = 'bmcms';
console.log(user.name);//bmcms
遍历对象
获取内容
使用系统提供的 API 可以方便获取对象属性与值
const bm = {
name: "斑马兽",
age: 10
};
console.log(Object.keys(bm)); //["name", "age"]
console.log(Object.values(bm)); //["斑马兽", 10]
console.table(Object.entries(bm)); //[["name","斑马兽"],["age",10]]
for/in
使用for/in
遍历对象属性
const bm = {
name: "斑马兽",
age: 10
};
for (let key in bm) {
console.log(key, bm[key]);
}
for/of
for/of
用于遍历迭代对象,不能直接操作对象。但Object
对象的keys/
方法返回的是迭代对象。
const bm = {
name: "斑马兽",
age: 10
};
for (const key of Object.keys(bm)) {
console.log(key);
}
获取所有对象属性
const bm = {
name: "斑马兽",
age: 10
};
for (const key of Object.values(bm)) {
console.log(key);
}
同时获取属性名与值
for (const array of Object.entries(bm)) {
console.log(array);
}
使用扩展语法同时获取属性名与值
for (const [key, value] of Object.entries(bm)) {
console.log(key, value);
}
添加元素 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: 'bmcms'
}
}
let a = obj;
let b = obj;
a.name = 'lisi';
console.log(b.name); //lisi
浅拷贝
使用for/in
执行对象拷贝
let obj = {name: "斑马兽"};
let bm = {};
for (const key in obj) {
bm[key] = obj[key];
}
bm.name = "bmcms";
console.log(bm);
console.log(obj);
Object.assign
函数可简单的实现浅拷贝,它是将两个对象的属性叠加后面对象属性会覆盖前面对象同名属性。
let user = {
name: '斑马兽'
};
let bm = {
stu: Object.assign({}, user)
};
bm.stu.name = 'bmcms';
console.log(user.name);//斑马兽
使用展示语法也可以实现浅拷贝
let obj = {
name: "斑马兽"
};
let bm = { ...obj };
bm.name = "bmcms";
console.log(bm);
console.log(obj);
深拷贝
浅拷贝不会将深层的数据复制
let obj = {
name: '斑马兽',
user: {
name: 'bmcms'
}
}
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 = 'bmcms';
newObj.user.name = 'banmashou.com';
console.log(newObj);
console.log(obj);
是完全的复制一个对象,两个对象是完全独立的对象
let obj = {
name: "斑马兽",
user: {
name: "bmcms"
},
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;
}
let bm = copy(obj);
bm.data.push("斑马");
console.log(JSON.stringify(bm, null, 2));
console.log(JSON.stringify(obj, null, 2));
构建函数
对象可以通过内置或自定义的构造函数创建。
工厂函数
在函数中返回对象的函数称为工厂函数,工厂函数有以下优点
- 减少重复创建相同类型对象的代码
- 修改工厂函数的方法影响所有同类对象
使用字面量创建对象需要复制属性与方法结构
const bms = {
name: "斑马兽",
show() {
console.log(this.name);
}
};
const bm = {
name: "斑马",
show() {
console.log(this.name);
}
};
使用工厂函数可以简化这个过程
function stu(name) {
return {
name,
show() {
console.log(this.name);
}
};
}
const lisi = stu("李四");
lisi.show();
const bm = stu("斑马兽");
bm.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 bm = new Student("斑马兽");
bm.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 bm = new User();
bm.show(); //User
let bms = bm.show;
bms(); //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 bm = new Object();
bm.name = "斑马兽";
console.log(bm);
字面量创建的对象,内部也是调用了Object
构造函数
const bm = {
name: "斑马兽"
};
console.log(bm.constructor); //ƒ Object() { [native code] }
//下面是使用构造函数创建对象
const bmcms = new Object();
bmcms.title = "开源内容管理系统";
console.log(bmcms);
对象函数
在JS
中函数也是一个对象
function bm(name) {}
console.log(bm.toString());
console.log(bm.length);
函数是由系统内置的 Function
构造函数创建的
function bm(name) {}
console.log(bm.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());
抽象封装
上例中的方法和属性仍然可以在外部访问到,比如 info
方法只是在内部使用,不需要被外部访问到这会破坏程序的内部逻辑。
下面使用闭包特性将对象进行抽象处理
function User(name, age) {
let data = { name, age };
let info = function() {
return data.age > 50 ? "中年人" : "年轻人";
};
this.message = function() {
return `${data.name}是${info()}`;
};
}
let lisi = new User("斑马兽", 22);
console.log(lisi.message());
属性特征
JS 中可以对属性的访问特性进行控制。
查看特征
使用 Object.getOwnPropertyDescriptor
查看对象属性的描述。
"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,
enumerable: false,
configurable: false
});
通过执行以下代码对上面配置进行测试,请分别打开注释进行测试
// 不允许修改
// user.name = "斑马兽"; //Error
// 不能遍历
// console.log(Object.keys(user));
//不允许删除
// 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.preventExtensions
禁止向对象添加属性
"use strict";
const user = {
name: "斑马兽"
};
Object.preventExtensions(user);
user.age = 18; //Error
Object.isExtensible
判断是否能向对象中添加属性
"use strict";
const user = {
name: "斑马兽"
};
Object.preventExtensions(user);
console.log(Object.isExtensible(user)); //false
封闭对象
Object.seal()
方法封闭一个对象,阻止添加新属性并将所有现有属性标记为 configurable: false
"use strict";
const user = {
name: "斑马兽",
age: 18
};
Object.seal(user);
console.log(
JSON.stringify(Object.getOwnPropertyDescriptors(user), null, 2)
);
Object.seal(user);
console.log(Object.isSealed(user));
delete user.name; //Error
Object.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 = "斑马兽"; //Error
Object.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: "banmashou.com",
get site() {
return `${this.name} ${this.url}`;
},
set site(value) {
[this.name, this.url] = value.split(",");
}
};
web.site = "斑马兽,bmcms.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 = 'banmashou'
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 bm = new User("斑马兽", 33);
console.log(bm.name);
bm.name = "斑马1";
console.log(bm.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 bm = new User("斑马兽", 33);
console.log(bm.name);
bm.name = "斑马1";
console.log(bm.name);
console.log(bm);
闭包访问器
下面结合闭包特性对属性进行访问控制
- 下例中访问器定义在函数中,并接收参数 v
- 在 get() 中通过闭包返回 v
- 在 set() 中修改了 v,这会影响 get()访问的闭包数据 v
let data = {
name: 'banmashou.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
必须返回布尔值
使用方法
"use strict";
const bm = { name: "斑马兽" };
const proxy = new Proxy(bm, {
get(obj, property) {
return obj[property];
},
set(obj, property, value) {
obj[property] = value;
return true;
}
});
proxy.age = 10;
console.log(bm);
代理函数
如果代理以函数方式执行时,会执行代理中定义 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]);
截取字符
下例中对数组进行代理,用于截取标题操作
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="text" v-model="title" />
<input type="text" 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="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>
JSON
- json 是一种轻量级的数据交换格式,易于人阅读和编写。
- 使用
json
数据格式是替换xml
的最佳方式,主流语言都很好的支持json
格式。所以json
也是前后台传输数据的主要格式。 - json 标准中要求使用双引号包裹属性,虽然有些语言不强制,但使用双引号可避免多程序间传输发生错误语言错误的发生。
声明定义
基本结构
let bm = {
"title": "斑马兽",
"url": "banmashou.com",
"teacher": {
"name": "斑马",
}
}
console.log(bm.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);
序列化
序列化是将 json
转换为字符串,一般用来向其他语言传输使用。
let bm = {
"title": "斑马兽",
"url": "banmashou.com",
"teacher": {
"name": "斑马",
}
}
console.log(JSON.stringify(bm));
//{"title":"斑马兽","url":"banmashou.com","teacher":{"name":"斑马"}}
根据第二个参数指定保存的属性
console.log(JSON.stringify(bm, ['title', 'url']));
//{"title":"斑马兽","url":"banmashou.com"}
第三个是参数用来控制 TAB 数量,如果字符串则为前导字符。
let bm = {
"title": "斑马兽",
"url": "banmashou.com",
"teacher": {
"name": "斑马",
}
}
console.log(JSON.stringify(bm, null, 4));
为数据添加 toJSON 方法来自定义返回格式
let bm = {
"title": "斑马兽",
"url": "banmashou.com",
"teacher": {
"name": "斑马",
},
"toJSON": function () {
return {
"title": this.url,
"name": this.teacher.name
};
}
}
console.log(JSON.stringify(bm)); //{"title":"banmashou.com","name":"斑马"}
反序列化
使用 JSON.parse
将字符串 json
解析成对象
let bm = {
"title": "斑马兽",
"url": "banmashou.com",
"teacher": {
"name": "斑马",
}
}
let jsonStr = JSON.stringify(bm);
console.log(JSON.parse(jsonStr));
使用第二个参数函数来对返回的数据二次处理
let bm = {
title: "斑马兽",
url: "banmashou.com",
teacher: {
name: "斑马"
}
};
let jsonStr = JSON.stringify(bm);
console.log(
JSON.parse(jsonStr, (key, value) => {
if (key == "title") {
return `[推荐] ${value}`;
}
return value;
})
);
Reflect
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法
Reflect
并非一个构造函数,所以不能通过 new 运算符对其进行调用