第十章 面向对象基础
深浅拷贝
浅拷贝
- 对象:
- Object.assign()
- ...
- 数组;
- Array.from
- ...
- 其它:
- JSON.stringify()
深拷贝
- 可能存在循环引用的问题,需要额外处理
- 基于递归
基础
函数编程与面向对象编程
面向对象在复杂情况下>函数式编程
实际上是对象中封装了所有功能需要,以降低理解难度(某种意义上)。
js
let search = {
grade: [{ lesson: "math", score: 80 }, { lesson: "english", score: 99 }, { lesson: "japanese", score: 50 }],
user: "yang",
average() {
let total = this.grade.reduce((a, b) => a + b.score , 0)
// return `${typeof this.grade}
return `${this.user}的平均成绩是${total / this.grade.length}`
}
}
console.log(search.average());
类似于以上代码即为面向对象的编程思想。
减少意大利面式的流水账代码。
属性操作
js
读取
1 通过点语法: 读取---对象.属性
2 通过中括号: 读取---对象.["属性名"]
3比如:
let user = {
name : "yang",
"my age" : 18
}
读取的时候无法通过点语法读取。用于中括号读取
删除
delete obj.arrtribute.
★对象引用传址
js
function show(change){
change.attirbute1 = "text"
return change;
}
let pro_change = {name:'yang'};
console.log(show(pro_change))
传址操作会改变唯一的地址指向
展开语法参数合并
js
function upload(param){
let data = {
type : "*.jpeg,*.png",
size : 800
}
data = {...data,...param};
console.log(data);
}
console.log(upload({type:"*.wav"}))
解构语法
应用场景:复制&赋值
对元素的解构处理
后期配合源码深入理解。
误区:
不一定非要把全部参数都使用结构来接收和释放,我们往往忽视了使用对部分进行解构,具体问题具体分析地灵活运用结构。
需要说明的是,一般对数据进行解构,我们往往是想要其中的值,所以进行输出的时候是对值输出而不是索引或关键字。
js
解构赋值不太一样,是前边的数据赋值给后边的变量
let demo = {name:"yang",age:30};
let {name:name,age:age} = demo;
此句子是将右边的demo调用给name,前边的name赋值给右边新生成的变量name。前方的age赋值给右边新生成的age。
复合结构语法:
let defined = {
name:"pre",
lesson:{
l1:"math",
l2:"english"
}};
let {name,lesson:{l1,l2}} = defined;
console.log(name,l1,l2)
js
特性:
拆分赋值:
let data = {name:"new name",age : "new age"};
let {name : m,age:n} = data;
console.log(m,n)
简写:
let data = {name:"new name",age : "new age"};
let {name,age} = data;
console.log(m,n)
使用这种特性,应用在函数里:
function show2 ({name,age}){
console.log(name,age);
}
show2({name:"yang",age:80});
传参和形参需要一致。
问题:只能在拥有数据的前提下才能使用。
严格模式下的解构
js
"use strict"
let user = { lang: "ch", price: "none" };
({lang,price} = user);
console.log(lang,price)
使用严格模式下,{lang,price}未定义,报错。
声明即可。
★标准与简写
js
规则:
在js中,如果属性和值相同,可以简写。
标准:
let defined = {name:"pre",price:80};
let {name:name,price:price} = defined;
简写:
let defined = {name:"pre",price:80};
let {name,price} = defined;
对象与数组的解构
js
两周使用方法都类似
对象:
let defined = {name:"pre",price:80};
let {name:name,price:price} = defined;
数组解构:
let arr= ["one","two","three"];
let [a,b,c,d="default"] = arr;
console.log(a,b,c,d)
小案例:默认值合并
js
使用结构语法进行默认值合并:
let div = document.getElementById("demo");
function change_element(options){
let {width=300,height=300,backgroundColor="red"} = options;
div.style.width = width + "px";
div.style.height = height+ "px";
div.style.backgroundColor = backgroundColor;
}
change_element({width:80});
★对象属性
js
添加:
obj.attribute = blablabla;
let user = {};
user.name= "yang";
user["age"] = 80;
console.log(user)
需要说明的是,使用中括号添加属性的时候里边的属性名必须为字符串。
因为如果是类似这种:user[age] = num;中括号里的不是字符串的时候,js会寻找age变量进行赋值。这时候就不是添加属性了。
删除:
delete.obj.attribute;
delete user.name;
delete user.age;
console.log(user)
面向对象中的继承,在js中即原型链
★原型与原型链
简单理解,某个数据的原型就是其数据的父亲。
js
数组本身的固有属性只有length,而数组的原型里拥有多种方法。
检测:(检测是否含有某种属性)
obj.hasOwnProperty("attribute");
检测某种属性存在于数组中:(检测自身和原型中是否存在属性)
attribute in arr
使用这种方式检测不仅检测本身,还会检测原型中的方法
设置某个对象的原型:
Object.setPrototypeOf(a,b) 将b设置为a的原型
练习:使用原型检测进行判断查询
function set_proto(obj,proto){
return Object.setPrototypeOf(obj,proto);
}
let obj = {name:"yang"};
let proto = {pre_name:"yo"}
console.log(set_proto(obj,proto));
function test(obj,attr){
if (!obj.hasOwnProperty(attr) == true) {
throw new Error(`you must contain ${attr}`);
}else{
return obj.hasOwnProperty(attr);
}
}
console.log(test(obj,"name"))
计算属性
通过计算属性的方式动态改变属性:
js
练习:1 将数组转化为对象
2 将对象中的key值添加范式后缀
//场景模拟: 从后台抓取数据,全部为数组。
//业务需求:需要转换为对象,并且重新命名所有数据的key值。
//思路实现:1 数据获取 2 reduce处理 3 字面量转化key值
let data = [{lesson:"math",Math:"hard"},{lesson:"english",English:"easy"},{lesson:"Video",Video:"normal"},{lesson:"Code",Code:"hard"}];
let change_obj= data.reduce((item,cur,index)=>{
item[`${cur["lesson"]}-${index + 1}`] = cur; //这里忽视了字符和字符串的问题,导致前缀无法正常显示
return item;
},{})
let res = JSON.stringify(change_obj,null,2);
console.log(res);
数据合并:
js
Object.assign({obj1},{obj2})
==>{obj1:obj1.value,obj2.key:obj2.value...}
js
练习:属性添加
//需求分析:创建一个函数,函数体内有一些类型为{}的数据,通过对象参数传递将参数属性添加到数据中。最后通过JSON方法进行字符串转义
function add_attribute(param){
let data = {
name:"yang"
}
data = JSON.stringify(Object.assign(data,param),null,2);
return data;
}
console.log(add_attribute({age:80}))
★Objcet方法
js
合并属性
Object.assign(a,b);
获取键
Object.keys();
获取值
Objcet.values();
获取键+值
Object.entries();
获取原型
Object.getPrototypeOf()
判断属性:
Object.hasOwnProperty() 内部为string
获取某个对象属性特征(多个对象语句末尾加s)
getOwnPropertyDescriptor(obj,value)
修改对象属性:
Objcet.defineProperty(obj,value,options{})
批量修改属性:
Object.defineProperties(obj,{
name:{
value
writable
enumerable
configuarble
}
age:{
...
}
})
使用批量修改,属性赋予就会更加灵活。
返回的JSON字符串为:
value 属性值
writable 是否可写
enumerable 是否可遍历(通过object.keys是否能遍历)
configurable 是否可删除/是否可重新配置
相对应的,for in 与 for of 也可以用来操作。其中,for of用来操作可迭代对象。
一定有变通的方法
js
一看到数组,就应该想到可以遍历
DOM for of 练习:
let data = [{lesson:"math",Math:"hard"},{lesson:"english",English:"easy"},{lesson:"Video",Video:"normal"},{lesson:"Code",Code:"hard"}];
let ul = document.createElement("ul");
for (const v of data) {
let li = document.createElement("li");
li.innerHTML = `课程名为${v.lesson}`
ul.appendChild(li);
}
document.body.appendChild(ul);
浅拷贝
js
对单一层次的对象进行拷贝:
1. 通过普通调用完成浅拷贝
let resource_data = {
name:"yang"
}
let rechange_data = {
name:resource_data.name
}
console.log(rechange_data);
这样就通过调用的形式进行了一次浅拷贝
2.通过for in 循环数据进行浅拷贝
let resource_data = {
name:"yang"
}
let rechange_data2 = {}
for (const key in resource_data) {
rechange_data2[key] = resource_data[key];
}
console.log(rechange_data2
这样就通过for in对原数据循环,进行了全部的拷贝。
3.通过Object.assign()进行浅拷贝
let rechange_data3 = Object.assign({},resource_data)
4.通过点语法进行浅拷贝
let rechange_data4 = {...resource_data};
★深拷贝
js
基础理解:深拷贝就是完完全全将数据拷贝出来,赋值到一个变量上,且对引用类型修改的时候,彼此独立不受影响。
所以深拷贝的重点是对对象进行迭代操作。
进行判断:如果为object,则将object重新传入函数中重新进行拷贝。如果不是object,直接进行浅拷贝。
let data1 = {
name: "yang",
user: {
name:"young"
}
}
let data2 = {
name : data1.name,
user: data1.user
}
data1.name = "nope"
data1.user.name = "yes"
console.log(data1);
console.log(data2);
data1中的name为值类型,data2引用data1的值,传递值。
data1中的user为引用类型,data2和data1共享一个内存。
js
迭代实现深拷贝(单纯对象操作)
let data = {
name: "yang",
user: {
name:"young"
}
}
function copy(element) {
let res = {};
for (const key in element) {
res[key] =
typeof(element[key]) == "object"? copy(element[key]) : element[key];
}
return res;
}
let result = copy(data);
data.name = "new name";
data.user.name = "new user name";
console.log(JSON.stringify(data,null,2))
console.log(JSON.stringify(result,null,2));
因为是单纯的对象操作,所以当有数组数据在原数据中时,数组会被改成对象类型。
解决方法:
part1: 通过instanceof == Array /Object进行原型判断
part2: 通过.entries将循环中数据赋值结构化
let complex_data = {
name: "yang",
user: {
name:"young"
},
apply:[1,2,3]
}
function deep_copy(element) {
let fixed_data = element instanceof Array ? [] : {};
for (const [k, v] of Object.entries(element)) {
fixed_data[k]=
typeof v == "object" ? deep_copy(v) : v;
}
return fixed_data;
}
let res = JSON.stringify(deep_copy(complex_data,null,2));
console.log(complex_data);
console.log(res);
实现深拷贝
函数“工厂”
js
通过构造函数创建数据,重要的是搞明白return在函数中的意义。
简单练习:
function pet_name(){
return {
name:"demo_name",
age:80,
random_rename(){
const name_list = ["doggie","pappi","siilsy","conly"];
console.log(name_list[Math.round(Math.random()*(name_list.length-1))]);
},
show(){
console.log(`${this.name}的年龄为${this.age}`)
},
all() {
this.random_rename();
this.show();
}
}
}
let text = pet_name();
console.log(text.name,text.age);
console.log(text.all());
★数据的构造函数创建
对象在使用构造函数创建时,会立刻指向到构造函数的原型对象。
js
数据实际上都是通过构造函数生产出来的。
通过构造函数创建:
new Object()
new Number()
new String()
new Boolean()
new Date()
new RegExp()
.valueOf()
let User = new Function("name","age=1",`
this.name = name;
this.age = age;
this.show = function(){
console.log(this.name);
}
`)
let yh = new User("yang");
yh.show();
js
封装:
let complex_data = {
name: "yang",
user: {
name:"young"
},
apply:[1,2,3],
show(){
return `${this.name},${this.user.name},${this.apply}`
}
}
js
抽象:
内部的方法和属性不希望被外部改变的时候
function User(name,age) {
let user = {name,age};
let info = function () {
return user.age > 40?"old":"young";
}
this.go = function () {
console.log(user.name,info());
}
}
let yang = new User("yang",19);
yang.info = function(){
return "";
}
yang.go();
对函数进行抽象,目的是增强安全性和健壮性。让代码在内部循环,在外调用的基础上实现内封闭。
★属性
js
长度是不能进行遍历的操作。
所以一些属性也是可以进行隐藏。
通过设置属性特征对属性进行控制
获取属性特征Object.getOwnPropertyDescriptor(obj,value)
设置新属性:
Object.defineProperty(new_variable,new_atribute,{
value 修改值
writable 修改boolean
enumerable 修改boolean
configurable 修改boolean
})
当enumerable为false时,无法遍历。即一些通过迭代遍历的对象都无法显示。
在严格模式下,设置false的时候再次引用赋值会报错。
所以严格模式下练习效果是比较好的。
练习:
let data = {
name: "yang",
age : 30
}
console.log(
JSON.stringify(data,null,2)
)
Object.defineProperty(data,"name",{
value:"ggg"
});
console.log(data.name)
js
禁止添加属性API:
Object.prventExtensions(obj)
使用之后形如:obj.name= 'xxx'等属性添加就不会生效。
在严格模式下,使用了preventExtensions后,仍然有添加属性的代码来添加属性会报错。
封闭对象的API
js
Objcet.seal(obj) 封闭对象
Object.isSealed(obj) 判断是否处于封闭状态
封闭就是:
不能添加属性
不能删除对象
不能修改特征
使用场景:
对数据进行保护,禁止数据修改。
以常量const命名的对象例:
const attr = {
name: "yang",
age: 19
};
Object.seal(attr);
console.log(JSON.stringify(Object.getOwnPropertyDescriptors(attr),null,2));
js
冻结:
Object.freeze(obj)
会将对象里的writable和configurable都变为false
判断是否冻结:
Object.isFrozen()
★访问器
对数据进行把关。如果满足条件->使用;不满足条件->不使用。
类似于门卫监察,更形象地说,是低压手术室的入口。
优先级最高,只要使用了对象,首先会执行set里的语句。
js
使用访问器的目的是对条件进行筛选。
对单一对象进行访问设置。
使用方式:
set function(){} 设置入
get function(){} 设置出
使用之后在区域外可以使用设置属性的方式设置。
const resource = {
data: { name: "yang", age: 60 },
set age(value) {
if (value > 100 || value < 10 || typeof (value) != 'number') {
throw new Error("请输入正确的年龄")
}
this.data.age = value;
},
get age(){
return this.data.age + "岁";
}
}
resource.age = 50;
console.log(resource.age)
js
使用访问器伪造属性:
将属性绑定到函数上,调取伪属性。
const M_resource = {
lesson: [
{ name: "english", price: 800 },
{ name: "math", price: 1500 },
{ name: "computer", price: 5000 }
],
get total() {
return this.lesson.reduce((pre,cur)=>
pre + cur.price
,0)
}
}
console.log(M_resource.total);
这样相当于通过访问器创建了一个属性,成为伪属性。
在调用的时候和对属性调用是一样的
js
批量设置属性:
const r_data = {
name: "yang",
age: 80,
set in_data(value) {
// [this.name,this.age] = res;
[this.name, this.age] = value.split(",");
},
get in_data() {
return `${this.name}-${this.age}`
}
}
r_data.in_data = "young,23";
r_data.in_data = "any,58"
console.log(r_data.in_data);
需要注意的是,不同对象需要不同的批量处理。这里的批量是指对一个对象下的多个属性处理。
js
token 读写处理
/*
核心步骤:
1 set token =》本地电脑上储存数据
2 get token =》获取本地数据 如果本地无数据,弹窗通知
localStorage.setItem
localStorage.getItem
*/
let Request = {
set token(content) {
localStorage.setItem("token", content);
},
get token() {
let token = localStorage.getItem("token");
if (!token) {
alert("Back to login");
}
return token;
}
};
Request.token = "65asf12as1f32";
console.log(Request.token);
与前边的练习不同的是,token不是在对象里放数据,而是在磁盘里放数据。直接在磁盘里读取。
要善于利用这些小特性,让自己的代码变得更加优雅
js
优先级:
访问器>传统属性赋值
const nv = {
name : "yang",
age : 38,
set in_data(value){
this.data.name = value;
},
get in_data(){
return this.data.name;
}
}
nv.name = "young";
console.log(nv.name)
解决(暂时不完全理解)
const DATA = Symbol();
const nv = {
[DATA] : {name},
age : 38,
set in_data(value){
this[DATA].name = value;
},
get in_data(){
return this[DATA].name;
}
}
nv.name = "young";
nv[Symbol()].name = "young"
外界无法通过获取Symbol函数来修改Symbol里的值。
**不明白为什么nv[DATA].name = xxx可以修改**
js
构造函数使用访问器
//构造函数定义
//1 function 参数2:user,age
//2 set get
//3 Object.defineProperties(obj,{})
function text(name,age){
let vairable = {name,age};
Object.defineProperties(this,{
name:{
set(value){
if(value.trim() == "" || value.length == 0){
throw new Error("error");
};
data.name = value;
},
get(){
return vairable.name
}
},
age: {
set(value){
if(value <10 || value > 100 || typeof value != 'number'){
throw new Error("error");
};
data.age = value;
},
get(){
return vairable.age;
}
}
})
};
let user = new text("yang",20);
console.log(user.name,user.age)
类方法使用构造函数
语法糖
const DATA = Symbol();
class Factor {
constructor(name, age) {
this[DATA] = { name, age };
}
set name(value) {
if (value.trim() == "" || value.length == 0) {
throw new Error("error");
};
this[DATA].name = value;
}
get name() {
return this[DATA].name
}
set age(value) {
if (value < 10 || value > 100 || typeof value != 'number') {
throw new Error("error");
};
this[DATA].age = value;
}
get age() {
return this[DATA].age;
}
};
let us = new Factor("yang",80);
console.log(us.name,us.age)
★proxy
访问器是对单个属性控制
对象代理对整个对象进行控制
不直接访问数据,通过代理访问数据
js
基本语法:
let proxy = new Proxy(obj,{
get(obj,property){},
set(obj,property,value){}
})
obj为原始对象,property为属性名
value为传输的值
js
get和set拦截对象
const data = {name:"yang",age:18};
const proxy = new Proxy(data,{
set (obj,property,value){
obj[property] = value;
return true;
},
get (obj,property) {
return obj[property];
}
});
console.log(proxy.name)
proxy.name = "yangyang"
console.log(proxy.name)
js
proxy+apply函数拦截
apply拦截函数
function factorial(num) {
return num = 1 ? 1 : num * factorial(num - 1);
}
let t_proxy = new Proxy(factorial, {
apply(func, obj, args) {
console.time("run");
func.apply(this, args);
console.timeEnd("run");
}
})
t_proxy.apply({}, [5])
console.log(t_proxy);
js
数组拦截:
const arr = [{lesson:"english",catagory:"fir"},{lesson:"math",catagory:"sec"},{lesson:"management",catagory:"thi"},{lesson:"japanese",catagory:"fou"}];
let pro = new Proxy(arr,{
get(arr,key){
const lesson = arr[key].lesson;
const len = 3;
arr[key].lesson =
lesson.length > len? lesson.substr(0,len) + ".".repeat(3):lesson;
return arr[key];
}
})
console.log(JSON.stringify(pro[2],null,2));
双向绑定
js
Vuejs数据绑定容器更新
//双向代理
//html部分
<input type="text" name="text">
<input type="text" name="text">
<h3 n_bind="text">there</h3>
<br>
<br>
<input type="text" name="demo">
<input type="text" name="demo">
<h3 n_bind="demo">here</h3>
//js部分
function View() {
let proxy = new Proxy({}, {
get(obj, property) {
},
set(obj, property, value) {
document.querySelectorAll(`[name="${property}"]`)
.forEach(function(item){
item.value = value;
});
document.querySelectorAll(`[n_bind="${property}"]`)
.forEach(function(item){
item.innerHTML = value;
})
return true;
},
})
this.init = function () {
const inputs = document.querySelectorAll("[name]");
inputs.forEach((item) => {
item.addEventListener("keyup", function () {
proxy[this.getAttribute("name")] = this.value;
})
})
}
};
new View().init();
使用代理实现数据绑定。
js
//自定义验证组件 //逻辑太乱了跳过
class Validate{
max(value,len){
return value.length < len;
}
min(value,len){
return value.length>len;
}
isNumber(value){
return value == "number"
}
}
function ProxyFactory(target) {
return new Proxy(target, {
get(obj, key) {
return target[key];
},
set(obj, key, el) {
const rule = el.getAttribute("rule");
const validate = new Validate();
let state = rule.split(",").every(rule=>{
const info = rule.split(":").every(rule=>{return true});
console.log(info)
return validate[info[0]](el.value,info[1])
return true;
})
console.log(rule)
return true;
}
})
}
const obj = ProxyFactory(document.querySelectorAll("[name]"));
obj.forEach((item,i)=>{
item.addEventListener('keyup',function(){
obj[i] = this;
})
});
★JSON
不同语言与不同语言,不同网络与不同网络,不同主机与不同主机之间的数据连接通用格式。
原有XML ==>现使用JSON
js
JSON格式:
键也要有双引号
{
"key":"value",
"key":"value",
"key":"value",
"key":"value",
"key":"value"
}
js
转换:
数组与对象均可转换为JSON对象
1、js转JSON :
JSON.stringify(obj,保存的属性null的话选择全部用数组表示且内容为字符串,制表位长度); //为字符串类型
2、JSON转js:
//两种转换
JSON.parse(obj,(key,value)=>{});
let data = {
name:"yang",
age: 50,
location: "hz",
}
let JSON_data = JSON.stringify(data,null,2);
console.log(JSON_data)
let js_data = JSON.parse(JSON_data,(key,value)=>{
if(key == "name"){
value = "[咕咕]" + value;
}
return value
})
console.log(js_data)
3、定制序列化:
自定义返回需求
在传参的时候传递toJSON:function(){}方法
let data = {
name:"yang",
age: 50,
location: "hz",
toJSON(){
return {};
}
}
let JSON_data = JSON.stringify(data);
console.log(JSON_data)
此时返回{}