对象属性

属性类型

JavaScript对象属性分为两种类型:数据属性和存取器属性。

数据属性

数据属性包含一个数据值的位置,可以读取和写入。

const obj = {
    name: "东巴文",
    age: 1
};

// name和age都是数据属性

存取器属性

存取器属性不包含数据值,而是包含getter和setter函数。

const obj = {
    firstName: "东",
    lastName: "巴文",
    
    get fullName() {
        return `${this.firstName}${this.lastName}`;
    },
    
    set fullName(value) {
        [this.firstName, this.lastName] = value.split("");
    }
};

console.log(obj.fullName);  // "东巴文"
obj.fullName = "JavaScript";
console.log(obj.firstName);  // "J"

数据属性

数据属性有四个特性(attribute)。

属性特性

特性 说明 默认值
[[Value]] 属性的数据值 undefined
[[Writable]] 是否可写 false
[[Enumerable]] 是否可枚举 false
[[Configurable]] 是否可配置 false

查看属性特性

const obj = { name: "东巴文" };

const descriptor = Object.getOwnPropertyDescriptor(obj, "name");
console.log(descriptor);
// {
//   value: "东巴文",
//   writable: true,
//   enumerable: true,
//   configurable: true
// }

字面量创建的属性

const obj = {
    name: "东巴文"
};

// 字面量创建的属性,特性默认都是true
Object.getOwnPropertyDescriptor(obj, "name");
// { value: "东巴文", writable: true, enumerable: true, configurable: true }

存取器属性

存取器属性有四个特性,但没有[[Value]]和[[Writable]]。

属性特性

特性 说明 默认值
[[Get]] getter函数 undefined
[[Set]] setter函数 undefined
[[Enumerable]] 是否可枚举 false
[[Configurable]] 是否可配置 false

定义存取器

const obj = {
    _age: 0,
    
    get age() {
        console.log("获取age");
        return this._age;
    },
    
    set age(value) {
        console.log("设置age");
        if (value >= 0) {
            this._age = value;
        }
    }
};

obj.age = 10;   // 设置age
console.log(obj.age);  // 获取age 10

只读属性

const obj = {
    _name: "东巴文",
    
    get name() {
        return this._name;
    }
    // 没有setter,只读
};

obj.name = "JavaScript";  // 无效(严格模式报错)

属性描述符

属性描述符是一个对象,描述属性的特性。

获取属性描述符

const obj = { name: "东巴文" };

// 获取单个属性描述符
Object.getOwnPropertyDescriptor(obj, "name");

// 获取所有属性描述符
Object.getOwnPropertyDescriptors(obj);

数据描述符

{
    value: "东巴文",
    writable: true,
    enumerable: true,
    configurable: true
}

存取器描述符

{
    get: function() { return this._name; },
    set: function(value) { this._name = value; },
    enumerable: true,
    configurable: true
}

Object.defineProperty

Object.defineProperty用于定义或修改属性。

基本用法

const obj = {};

Object.defineProperty(obj, "name", {
    value: "东巴文",
    writable: true,
    enumerable: true,
    configurable: true
});

console.log(obj.name);  // "东巴文"

定义数据属性

const obj = {};

Object.defineProperty(obj, "name", {
    value: "东巴文",
    writable: false,      // 不可写
    enumerable: true,
    configurable: false   // 不可配置
});

obj.name = "JavaScript";  // 无效(严格模式报错)
console.log(obj.name);    // "东巴文"

定义存取器属性

const obj = {};

let _age = 0;

Object.defineProperty(obj, "age", {
    get() {
        return _age;
    },
    set(value) {
        if (value >= 0) {
            _age = value;
        }
    },
    enumerable: true,
    configurable: true
});

obj.age = 10;
console.log(obj.age);  // 10

不可写属性

const obj = {};

Object.defineProperty(obj, "PI", {
    value: 3.14159,
    writable: false,
    enumerable: true,
    configurable: false
});

obj.PI = 3.14;  // 无效
console.log(obj.PI);  // 3.14159

不可枚举属性

const obj = {
    name: "东巴文"
};

Object.defineProperty(obj, "secret", {
    value: "秘密",
    enumerable: false
});

console.log(Object.keys(obj));  // ["name"]
console.log(obj.secret);        // "秘密"(可以访问)

不可配置属性

const obj = {};

Object.defineProperty(obj, "name", {
    value: "东巴文",
    configurable: false
});

// 无法删除
delete obj.name;  // false
console.log(obj.name);  // "东巴文"

// 无法重新配置
Object.defineProperty(obj, "name", {
    writable: false
});  // TypeError

Object.defineProperties

Object.defineProperties可以一次定义多个属性。

基本用法

const obj = {};

Object.defineProperties(obj, {
    name: {
        value: "东巴文",
        writable: true,
        enumerable: true,
        configurable: true
    },
    age: {
        value: 1,
        writable: true,
        enumerable: true,
        configurable: true
    },
    fullName: {
        get() {
            return `${this.name}(${this.age})`;
        },
        enumerable: true,
        configurable: true
    }
});

console.log(obj.fullName);  // "东巴文(1)"

创建对象

function createUser(name, age) {
    const obj = {};
    
    Object.defineProperties(obj, {
        name: {
            value: name,
            writable: false,
            enumerable: true,
            configurable: false
        },
        age: {
            value: age,
            writable: true,
            enumerable: true,
            configurable: true
        }
    });
    
    return obj;
}

const user = createUser("东巴文", 1);

getter和setter

getter和setter提供了对属性访问的控制。

对象字面量中的getter/setter

const obj = {
    _price: 0,
    
    get price() {
        console.log("获取价格");
        return this._price;
    },
    
    set price(value) {
        console.log("设置价格");
        if (value >= 0) {
            this._price = value;
        } else {
            console.log("价格不能为负数");
        }
    }
};

obj.price = 100;    // 设置价格
console.log(obj.price);  // 获取价格 100

类中的getter/setter

class Circle {
    constructor(radius) {
        this._radius = radius;
    }
    
    get radius() {
        return this._radius;
    }
    
    set radius(value) {
        if (value > 0) {
            this._radius = value;
        }
    }
    
    get area() {
        return Math.PI * this._radius ** 2;
    }
    
    get circumference() {
        return 2 * Math.PI * this._radius;
    }
}

const circle = new Circle(5);
console.log(circle.area);         // 78.54...
console.log(circle.circumference); // 31.41...

计算属性

const rectangle = {
    width: 10,
    height: 5,
    
    get area() {
        return this.width * this.height;
    },
    
    get perimeter() {
        return 2 * (this.width + this.height);
    }
};

console.log(rectangle.area);       // 50
console.log(rectangle.perimeter);  // 30

数据验证

const user = {
    _email: "",
    
    get email() {
        return this._email;
    },
    
    set email(value) {
        if (value.includes("@")) {
            this._email = value;
        } else {
            throw new Error("无效的邮箱格式");
        }
    }
};

user.email = "test@example.com";  // OK
user.email = "invalid";           // Error

延迟计算

const obj = {
    _data: null,
    _loaded: false,
    
    get data() {
        if (!this._loaded) {
            console.log("加载数据...");
            this._data = this.loadData();
            this._loaded = true;
        }
        return this._data;
    },
    
    loadData() {
        return [1, 2, 3, 4, 5];
    }
};

// 第一次访问时才加载
console.log(obj.data);  // 加载数据... [1, 2, 3, 4, 5]
console.log(obj.data);  // [1, 2, 3, 4, 5](已缓存)

属性操作方法

获取属性名

const obj = {
    name: "东巴文",
    age: 1,
    [Symbol("id")]: 123
};

Object.defineProperty(obj, "secret", {
    value: "秘密",
    enumerable: false
});

// 可枚举的字符串属性
Object.keys(obj);           // ["name", "age"]

// 所有字符串属性(包括不可枚举)
Object.getOwnPropertyNames(obj);  // ["name", "age", "secret"]

// 所有Symbol属性
Object.getOwnPropertySymbols(obj);  // [Symbol(id)]

// 所有属性(字符串+Symbol)
Reflect.ownKeys(obj);  // ["name", "age", "secret", Symbol(id)]

获取属性值

const obj = {
    name: "东巴文",
    age: 1
};

// 获取所有值
Object.values(obj);  // ["东巴文", 1]

// 获取键值对
Object.entries(obj);  // [["name", "东巴文"], ["age", 1]]

检查属性存在

const obj = {
    name: "东巴文",
    age: undefined
};

// in操作符(检查原型链)
"name" in obj;           // true
"toString" in obj;       // true(继承)

// hasOwnProperty(只检查自身)
obj.hasOwnProperty("name");       // true
obj.hasOwnProperty("toString");   // false

// Object.hasOwn(推荐)
Object.hasOwn(obj, "name");       // true
Object.hasOwn(obj, "toString");   // false

// 属性值检查
obj.name !== undefined;  // true
obj.age !== undefined;   // false(注意:age存在但值为undefined)

删除属性

const obj = {
    name: "东巴文",
    age: 1
};

delete obj.age;
console.log(obj);  // { name: "东巴文" }

// 删除不可配置属性返回false
Object.defineProperty(obj, "id", {
    value: 1,
    configurable: false
});

delete obj.id;  // false

属性枚举顺序

ES6规定了属性枚举的顺序。

枚举规则

const obj = {
    [Symbol("first")]: "symbol",
    2: "two",
    1: "one",
    "b": "letter b",
    "a": "letter a",
    [Symbol("second")]: "symbol2"
};

// 顺序:
// 1. 数字键,按数值升序
// 2. 字符串键,按创建顺序
// 3. Symbol键,按创建顺序

console.log(Object.keys(obj));
// ["1", "2", "b", "a"]

console.log(Object.getOwnPropertySymbols(obj));
// [Symbol(first), Symbol(second)]

console.log(Reflect.ownKeys(obj));
// ["1", "2", "b", "a", Symbol(first), Symbol(second)]

下一步

掌握了对象属性后,让我们继续学习:

  1. 对象创建与继承 - 学习继承
  2. ES6类 - 学习ES6类语法
  3. 对象解构与扩展 - 学习对象操作

东巴文(db-w.cn) - 让编程学习更简单

🔑 东巴文寄语:深入理解对象属性是掌握JavaScript对象的关键,理解数据属性和存取器属性的区别,掌握Object.defineProperty的使用,能让你更好地控制对象行为。在 db-w.cn,我们帮你深入每一个细节!