对象新特性

本章将详细介绍ES6及后续版本中引入的对象新特性和语法糖,包括属性简写、计算属性名、对象解构、扩展运算符等,这些特性让对象操作更加简洁和强大。

13.1 属性简写

ES6允许在对象字面量中使用属性简写语法,当属性名和变量名相同时可以省略重复的部分。

基本属性简写

// ES5写法
let name = "张三";
let age = 25;
let person = {
    name: name,
    age: age
};

// ES6属性简写
let name = "张三";
let age = 25;
let person = {
    name,
    age
};

console.log(person); // {name: "张三", age: 25}

方法简写

// ES5写法
let calculator = {
    add: function(a, b) {
        return a + b;
    },
    multiply: function(a, b) {
        return a * b;
    }
};

// ES6方法简写
let calculator = {
    add(a, b) {
        return a + b;
    },
    multiply(a, b) {
        return a * b;
    }
};

console.log(calculator.add(5, 3)); // 8

13.2 计算属性名

ES6允许在对象字面量中使用表达式作为属性名,通过方括号[]包裹。

基本计算属性名

// 使用变量作为属性名
let propName = "userName";
let user = {
    [propName]: "张三"
};
console.log(user.userName); // "张三"

// 使用表达式作为属性名
let prefix = "user";
let user = {
    [prefix + "Name"]: "张三",
    [prefix + "Age"]: 25
};
console.log(user.userName); // "张三"
console.log(user.userAge); // 25

复杂计算属性名

// 使用函数调用作为属性名
function getPropertyName() {
    return "dynamic" + Math.random();
}

let obj = {
    [getPropertyName()]: "动态属性值"
};

// 使用Symbol作为属性名
let sym = Symbol("id");
let obj = {
    [sym]: 123
};
console.log(obj[sym]); // 123

13.3 对象解构

对象解构是ES6引入的一种从对象中提取属性值并赋给变量的语法。

基本对象解构

let person = {
    name: "张三",
    age: 25,
    city: "北京"
};

// 基本解构
let {name, age} = person;
console.log(name); // "张三"
console.log(age); // 25

// 重命名变量
let {name: fullName, age: years} = person;
console.log(fullName); // "张三"
console.log(years); // 25

// 默认值
let {name, country = "中国"} = person;
console.log(country); // "中国"

嵌套对象解构

let user = {
    name: "张三",
    address: {
        city: "北京",
        district: "朝阳区"
    },
    hobbies: ["读书", "游泳"]
};

// 嵌套解构
let {address: {city, district}, hobbies: [firstHobby]} = user;
console.log(city); // "北京"
console.log(district); // "朝阳区"
console.log(firstHobby); // "读书"

// 混合解构
let {name, address: {city: userCity}} = user;
console.log(name); // "张三"
console.log(userCity); // "北京"

13.4 扩展运算符

扩展运算符(...)允许对象在期望多个属性的位置展开。

对象扩展

// 对象合并
let obj1 = {a: 1, b: 2};
let obj2 = {c: 3, d: 4};
let combined = {...obj1, ...obj2};
console.log(combined); // {a: 1, b: 2, c: 3, d: 4}

// 对象复制
let original = {name: "张三", age: 25};
let copy = {...original};
console.log(copy); // {name: "张三", age: 25}

// 属性覆盖
let user = {name: "张三", age: 25};
let updatedUser = {...user, age: 26};
console.log(updatedUser); // {name: "张三", age: 26}

// 添加新属性
let newUser = {...user, city: "北京", country: "中国"};
console.log(newUser); // {name: "张三", age: 25, city: "北京", country: "中国"}

数组扩展

// 数组合并
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let combined = [...arr1, ...arr2];
console.log(combined); // [1, 2, 3, 4, 5, 6]

// 数组复制
let original = [1, 2, 3];
let copy = [...original];
console.log(copy); // [1, 2, 3]

// 将数组展开作为函数参数
function add(a, b, c) {
    return a + b + c;
}
let numbers = [1, 2, 3];
let result = add(...numbers);
console.log(result); // 6

13.5 Object.assign()

Object.assign()方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。

// 基本用法
let target = {a: 1, b: 2};
let source = {b: 4, c: 5};
Object.assign(target, source);
console.log(target); // {a: 1, b: 4, c: 5}

// 多个源对象
let target = {a: 1};
let source1 = {b: 2};
let source2 = {c: 3};
Object.assign(target, source1, source2);
console.log(target); // {a: 1, b: 2, c: 3}

// 对象克隆
let original = {name: "张三", age: 25};
let clone = Object.assign({}, original);
console.log(clone); // {name: "张三", age: 25}

// 浅拷贝
let obj1 = {a: 1, b: {c: 2}};
let obj2 = Object.assign({}, obj1);
obj2.b.c = 3;
console.log(obj1.b.c); // 3 (因为是浅拷贝)

13.6 对象属性遍历方法

ES6及后续版本增加了新的对象属性遍历方法。

Object.values()

let person = {
    name: "张三",
    age: 25,
    city: "北京"
};

let values = Object.values(person);
console.log(values); // ["张三", 25, "北京"]

Object.entries()

let person = {
    name: "张三",
    age: 25,
    city: "北京"
};

let entries = Object.entries(person);
console.log(entries); 
// [["name", "张三"], ["age", 25], ["city", "北京"]]

// 转换为Map
let map = new Map(Object.entries(person));
console.log(map); // Map(3) {"name" => "张三", "age" => 25, "city" => "北京"}

Object.getOwnPropertyDescriptors()

let obj = {
    name: "张三",
    get age() {
        return 25;
    }
};

let descriptors = Object.getOwnPropertyDescriptors(obj);
console.log(descriptors);
/*
{
  name: {value: "张三", writable: true, enumerable: true, configurable: true},
  age: {get: ƒ, set: undefined, enumerable: true, configurable: true}
}
*/

13.7 对象属性描述符扩展

ES6及后续版本对对象属性描述符进行了扩展。

Object.getOwnPropertyDescriptors()

// 获取所有自有属性的描述符
let obj = {
    name: "张三",
    get age() {
        return 25;
    },
    set age(value) {
        // setter
    }
};

let descriptors = Object.getOwnPropertyDescriptors(obj);
console.log(descriptors);

Object.getOwnPropertyNames() vs Object.keys()

let obj = {};
Object.defineProperty(obj, "hidden", {
    value: "隐藏属性",
    enumerable: false
});
obj.visible = "可见属性";

// Object.keys()只返回可枚举属性
console.log(Object.keys(obj)); // ["visible"]

// Object.getOwnPropertyNames()返回所有自有属性
console.log(Object.getOwnPropertyNames(obj)); // ["hidden", "visible"]

13.8 对象原型相关扩展

ES6对对象原型操作进行了扩展。

Object.setPrototypeOf()

let obj = {};
let proto = {
    greet() {
        return "你好";
    }
};

// 设置对象的原型
Object.setPrototypeOf(obj, proto);
console.log(obj.greet()); // "你好"

Object.getPrototypeOf()

let obj = {};
let proto = {name: "原型对象"};
Object.setPrototypeOf(obj, proto);

// 获取对象的原型
let retrievedProto = Object.getPrototypeOf(obj);
console.log(retrievedProto.name); // "原型对象"

13.9 对象保护扩展

ES6及后续版本增加了新的对象保护方法。

Object.is()

// Object.is()比===更严格
console.log(Object.is(0, -0)); // false
console.log(0 === -0); // true

console.log(Object.is(NaN, NaN)); // true
console.log(NaN === NaN); // false

console.log(Object.is(null, undefined)); // false
console.log(null === undefined); // false

13.10 实际应用示例

配置对象处理

// 使用对象新特性处理配置
function createComponent(options = {}) {
    // 默认配置
    const defaults = {
        width: 100,
        height: 100,
        color: "#000000",
        backgroundColor: "#ffffff"
    };
    
    // 合并配置
    const config = {...defaults, ...options};
    
    // 解构常用属性
    const {width, height, color} = config;
    
    console.log(`创建${width}x${height}的组件,颜色: ${color}`);
    return config;
}

createComponent({width: 200, color: "#ff0000"});
createComponent(); // 使用默认配置

数据处理函数

// 使用对象新特性处理数据
const processData = (data, {sortBy = 'id', order = 'asc'} = {}) => {
    return [...data]
        .sort((a, b) => {
            if (order === 'asc') {
                return a[sortBy] > b[sortBy] ? 1 : -1;
            } else {
                return a[sortBy] < b[sortBy] ? 1 : -1;
            }
        });
};

const users = [
    {id: 3, name: "张三", age: 25},
    {id: 1, name: "李四", age: 30},
    {id: 2, name: "王五", age: 20}
];

console.log(processData(users)); // 按id升序排列
console.log(processData(users, {sortBy: 'age', order: 'desc'})); // 按age降序排列

模块模式

// 使用对象新特性实现模块
const apiClient = (() => {
    // 私有变量
    let baseUrl = "https://api.example.com";
    let headers = {
        "Content-Type": "application/json"
    };
    
    // 公共方法
    return {
        // 方法简写
        async get(endpoint) {
            const response = await fetch(`${baseUrl}${endpoint}`, {
                method: "GET",
                headers
            });
            return response.json();
        },
        
        async post(endpoint, data) {
            const response = await fetch(`${baseUrl}${endpoint}`, {
                method: "POST",
                headers,
                body: JSON.stringify(data)
            });
            return response.json();
        },
        
        // 配置方法
        config({url, ...newHeaders}) {
            if (url) baseUrl = url;
            headers = {...headers, ...newHeaders};
            return this; // 支持链式调用
        }
    };
})();

// 使用示例
// apiClient.config({url: "https://new-api.com"}).get("/users");

提示:对象新特性让JavaScript对象操作更加简洁和强大。合理使用这些特性可以写出更优雅、更易读的代码。但要注意浏览器兼容性,在需要支持旧版本浏览器时可能需要使用Babel等工具进行转换。