对象

基本概念

  • 对象是属性和方法的集合
  • 将复杂功能隐藏在内部,更改对象内部的复杂逻辑不会对外部调用造成影响即 抽象
  • 继承 是通过代码复用减少冗余代码
  • 根据不同形态的对象产生不同结果即 多态
// 字面量形式声明
let obj = {
    AAA: '萧俊介',
    BBB: function () {
        return this.name;
    }
}
// 属性与方法简写
let AAA = "萧俊介";
let objB = {
    AAA,
    BBB() {
        return this.name;
    }
};

对象方法

  • 定义在对象中的函数我们称为方法
  • 对象和函数、数组一样是引用类型,即复制只会复制引用地址。
  • 对象做为函数参数使用时也不会产生完全赋值,内外共用一个对象。
  • 对象的比较是对内存地址的比较所以使用 ===== 一样。
  • this 指当前对象的引用,始终建议在代码内部使用 this 而不要使用对象名
  • 不同对象的 this 只指向当前对象
  • 和函数一样可以使用 ... 语法

解构赋值

  • 解构是一种更简洁的赋值特性,可以理解为分解一个数据的结构。
let { name: n, url: u } = { name: '俊介 A', url: 'www.alrcly.com' }
let { name, url } = { name: '俊介 B', url: 'www.alrcly.com' };
const hd = {
    name: '后盾人',
    lessons: {
        title: 'JS'
    }
}
const { name, lessons: { title: title } } = hd;
const { name, url, user = '向军大叔' } = { name: '后盾人', url: 'houdunren.com' };

操作属性

  • 可以使用点语法获取,也可以使用 [] 获取
  • 如果属性名不是合法变量名就必须使用扩号的形式
  • 对象和方法的属性可以动态的添加或删除
obj.name
obj["name"]
obj.show = function () {
    return `show`;
};
delete obj.show;

//  hasOwnProperty 检测对象自身是否包含指定的属性,不检测原型链上继承的属性
obj.hasOwnProperty('name')

// 使用 in 可以在原型对象上检测是否包含指定的属性
"XXX" in obj

// 使用 Object.getOwnPropertyNames 可以获取对象的属性名集合
Object.getOwnPropertyNames(obj)

// 使用 Object.assign 静态方法从一个或多个对象复制属性,到第一个对象参数
// 可以使用展开语法代替 Object.assign
Object.assign(obj, { f: 1 }, { m: 9 })

// 对象属性可以通过表达式计算定义,这在动态设置属性或执行属性方法时很好用。
let id = 0;
const user = {
    [`id-${++id}`]: id,
    [`id-${++id}`]: id,
    [`id-${++id}`]: id
};

遍历对象

  • 使用系统提供的 API 可以方便获取对象属性与值
const obj = {}
Object.keys(obj); // 获取对象里的 Key
Object.values(obj); // 获取对象里的 Value
Object.entries(obj); // 获取对象里的键值对
// in 循环
for (let key in obj) {
    console.log(key, hd[key]);
}
// of 循环,不能操作对象,只能通过 keys、values、entries 间接实现
for (const item of Object.values(obj)) {
    console.log(item);
}

对象拷贝

  • 对象赋值时复制的内存地址,所以一个对象的改变直接影响另一个
  • 浅拷贝:对象本身和第一层是值传递,第二层还是复制的内存地址
  • 深拷贝:整个都是新的
// 使用 for/in 执行对象拷贝 (浅拷贝)
let oldObj = { name: "俊介" };
let newObj = {};
for (const key in oldObj) {
    newObj[key] = oldObj[key];
}
// 使用 Object.assign({}, oldObj) 执行对象拷贝 (浅拷贝)
let oldObjC = { name: "俊介" };
let newObjD = Object.assign({}, oldObjC)
// 使用 { ...oldObj } 执行对象拷贝 (浅拷贝)
let oldObjE = { name: "俊介" };
let newObjF = { ...oldObjE };
// 使用递归函数(深拷贝)
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;
}

函数构建

工厂函数

  • 在函数中返回对象的函数称为工厂函数
  • 减少重复创建相同类型对象的代码
  • 修改工厂函数的方法影响所有同类对象
  • 使用字面量创建对象需要复制属性与方法结构
function user(name) {
    return {
        name,
        show() {
            console.log(this.name);
        }
    };
}
const alrcly = user("萧俊介");
alrcly.show();

构造函数

  • 和工厂函数相似构造函数也用于创建对象,它的上下文为新的对象实例
  • 构造函数名每个单词首字母大写即 Pascal 命名规范
  • this 指当前创建的对象
  • 不需要返回 this 系统会自动完成
  • 需要使用 new 关键词生成对象
  • 在严格模式下方法中的 this 值为 undefined ,这是为了防止无意的修改 window 对象
function Student(name) {
    this.name = name;
    this.show = function () {
        console.log(this.name);
    };
    //不需要返回,系统会自动返回
    // return this;
}
const lemen = new Student("柠檬酱");
lemen.show();
const alrcly = new Student("俊介君");
alrcly.show();

内置构造

  • JS 中大部分数据类型都是通过构造函数创建的
  • 在 JS 中函数也是一个对象
  • 函数是由系统内置的 Function 构造函数创建的
let obj = new Object()
let num = new Number()
let str = new String()
let bool = new Boolean()
let data = new Data()
let reg = new RegExp()
  • 字面量创建的对象,内部也是调用了 Object 构造函数
   // 设置一个函数
    function hd() { }
    // constructor 是对象上的一个属性,指出对象的构造函数是什么
    // 打印发现这个函数是由 Function() 构造的
    console.log(hd.constructor)
    // 可以直接使用 Function() 来构建匿名函数
    // 参数 A 为变量,B 为代码块
    let UserA = new Function("name", `return name`)
    // 实例化一个对象 hd
    // 然后用 constructor 调用 hd 的构造函数方法
    // 加上()等于是直接在使用 constructor 返回的 hd 的构造函数方法了
    let UserB = new hd.constructor("name", `return name`)

抽象特性

  • 通过闭包特性将对象进行抽象处理,而不是通过关键字
function User(name, age) {
    // 封装成对象变量,使其不挂载到 this 上,屏蔽了外界访问
    let data = { name, age };
    // 利用闭包特性访问到 data 的数据
    this.show = function () {
        return `${data.name}${this.info()}`;
    };
    // 封装成对象变量,使其不挂载到 this 上,屏蔽了外界访问
    let info = function () {
        return data.age > 50 ? "中年人" : "年轻人";
    };
}
let jj = new User("萧俊介", 22);
console.log(jj.show());

属性特征

JS 中可以对属性的「属性描述符」进行控制,「属性描述符」共有六个。

  • configurable: true:属性是否可以删除或重新配置
  • enumerable: true:属性是否可遍历/隐藏
  • value: 123:该属性对应的值
  • writable: true:属性的值是否可以写修改
  • get() :属性的 getter 函数
  • set():属性的 setter 函数

查看对象某个属性的描述

  • 使用 Object.getOwnPropertyDescriptor 查看对象某个属性的描述
let obj = {
  aaa:123,
  bbb:456
}
Object.getOwnPropertyDescriptor(obj,"aaa")

查看对象所有属性的描述

  • 使用 Object.getOwnPropertyDescriptors 查看对象所有属性的描述
let obj = {
  aaa:123,
  bbb:456
}
Object.getOwnPropertyDescriptors(obj)

修改属性特性

  • 使用 Object.defineProperty 方法修改属性特性
let obj = {
    aaa: 123,
    bbb: 456
}
Object.defineProperty(obj, 'aaa', {
    value: 987,
    writable: false,
    enumerable: false,
    configurable: false
})
Object.getOwnPropertyDescriptors(obj)

一次修改多个属性

  • 使用 Object.defineProperties 可以一次设置多个属性
let obj = {
    aaa: 123,
    bbb: 456
}
Object.definePropertys(obj, {
    'aaa': {
        value: 987,
        writable: false,
        enumerable: false,
        configurable: false
    },
    'bbb': {
        value: 789,
        writable: false,
        enumerable: false,
        configurable: false
    }
})
Object.getOwnPropertyDescriptors(obj)

禁止向对象添加新属性

  • 使用 Object.preventExtensions() 禁止向对象添加新属性

判断是否能向对象中添加属性

  • 使用 Object.isExtensible() 判断是否能向对象中添加属性
let obj = {
    aaa: 123,
    bbb: 456
}
Object.preventExtensions(obj) // 禁止向对象添加属性
obj.ccc = "394" // 会报错
let isAdd = Object.isExtensible(obj) // 判断是否能向对象中添加属性
console.log(isAdd)

封闭对象(可以改值)

  • 使用 Object.seal() 方法封闭一个对象,阻止添加新属性并将所有现有属性标记为 configurable: false
  • 使用 Object.isSealed() 如果对象是密封的则返回 true,属性都具有 configurable: false
let obj = {
    aaa: 123,
    bbb: 456
}
Object.seal(obj) // 封闭对象
let ddd = Object.isSealed(obj) // 对象是否封闭
console.log(ddd);

冻结对象(不能改值)

  • 使用 Object.freeze 冻结对象后不允许添加、删除、修改属性,writableconfigurable 都标记为 false
  • 使用 Object.isFrozen() 方法判断一个对象是否被冻结
let obj = {
    aaa: 123,
    bbb: 456
}
Object.freeze(obj) // 冻结对象
let ddd = Object.isFrozen(obj) // 对象是否冻结
console.log(ddd);

获得/设置属性值

  • get Ter() 获得属性值,set Ter() 设置属性,这是 JS 提供的存取器特性即使用函数来管理属性
    • 用于避免错误的赋值
    • 需要动态监测值的改变
    • 属性只能在访问器和普通属性任选其一,不能共同存在
    • 访问器的优先级高于普通的操作方式
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 = 'houdunren'
console.log(Request.token); 

利用私有属性模拟抽象特性

使用 defineProperties 可以模拟定义私有属性,从而使用面向对象的抽象特性。

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 hd = new User("萧俊介", 33);
console.log(hd.name);
hd.name = "俊介 12";
console.log(hd.name);

代理拦截

代理(拦截器)是对象的访问控制,setter/getter 是对单个对象属性的控制,而代理是对整个对象的控制。

  • 读写属性时代码更简洁
  • 对象的多个属性控制统一交给代理完成
  • 严格模式下 set 必须返回布尔值

使用方法

语法

new Proxy(target,handler)

参数说明

target

即目标对象

handler

handler 是一个对象,声明了代理 target 的指定行为

代理对象

如果代理以对象方式执行时,会执行代理中定义 get/set 方法。

const alrlcy = { name: "萧俊介", age: '24' }
const proxy = new Proxy(alrlcy, {
    get(target, propKey, receiver) {
        //target:原始目标对象
        //propKey:当前的 Key
        //receiver:表示原始操作行为所在对象
        switch (propKey) {
            case 'name':
                console.log(target[propKey])
                break;
            case 'age':
                console.log(target[propKey])
                break;
        }
    },
    set(target, propKey, value, receiver) {
        //target:原始目标对象
        //propKey:当前的 Key
        //value:设置的值
        //receiver:表示原始操作行为所在对象
        obj[property] = value
        return true
    }
})

代理函数

如果代理以函数方式执行时,会执行代理中定义 apply 方法。

function factorial(num) {
    return num == 1 ? 1 : num * factorial(num - 1);
}
let factorialProxy = new Proxy(factorial, {
    apply(target, object, args) {
        //target:原始目标对象
        //object:this 指针
        //args:数组形式的参数
        return target.call(object, args)
    }
});
console.log(factorialProxy(3))

JSON

  • JSON 是一种轻量级的数据交换格式,易于人阅读和编写。
  • 使用 JSON 数据格式是替换 xml 的最佳方式,主流语言都很好的支持 JSON 格式。所以 JSON 也是前后台传输数据的主要格式。
  • JSON 标准中要求使用双引号包裹属性,虽然有些语言不强制,但使用双引号可避免多程序间传输发生错误语言错误的发生。

序列化

语法

JSON.stringify(value[, replacer[, space]])

参数说明

value:

必需,要转换的 JavaScript 值(通常为对象或数组)。

replacer:

可选,用于转换结果的函数或数组。

如果 replacer 为函数,则 JSON.stringify 将调用该函数,并传入每个成员的键和值。使用返回值而不是原始值。如果此函数返回 undefined,则排除成员。根对象的键是一个空字符串:""。

如果 replacer 是一个数组,则仅转换该数组中具有键值的成员。成员的转换顺序与键在数组中的顺序一样。

space:

可选,文本添加缩进、空格和换行符,如果 space 是一个数字,则返回值文本在每个级别缩进指定数目的空格,如果 space 大于 10,则文本缩进 10 个空格。space 也可以使用非数字,如:\t。

返回值

返回包含 JSON 文本的字符串。

自定义 toJSON

对象里可以声明 toJson 函数来实现自定义 JSON

let hd = {
    "title": "俊介历险记",
    "url": "www.alrcly.com",
    "teacher": {
        "name": "萧俊介",
    },
    toJSON(){
        return {
            "title": this.url,
            "name": this.teacher.name
        };
    }
}
console.log(JSON.stringify(hd));

反序列化

语法

JSON.parse(text[, reviver])

参数说明

text:

必需,一个有效的 JSON 字符串。

reviver:

可选,一个转换结果的函数,将为对象的每个成员调用此函数。

返回值

返回包含 JSON 文本的字符串。