Skip to content

第十章 面向对象基础

深浅拷贝

浅拷贝

  • 对象:
    • 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对象

1js转JSON :
		JSON.stringify(obj,保存的属性null的话选择全部用数组表示且内容为字符串,制表位长度);	//为字符串类型

2JSON转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)
此时返回{}