1. 程式人生 > 實用技巧 >JavaScript Object物件

JavaScript Object物件

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() 凍結物件後不允許新增、刪除、修改屬性,writableconfigurable都標記為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:指定儲存的屬性

  引數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>