Skip to content
imshengli blog
Go back

JavaScript Object:创建方式、原型链与深浅拷贝

JavaScript 对象全面解析:创建方式、属性描述符、原型链、遍历方法对比、解构与深浅拷贝。

· 6 min

总结

JavaScript 原生对象:Object。所有其他对象都继承自Object对象。

Object 对象的方法分两类:

对象创建的多种方式

// 1. 字面量(最常用)
var obj1 = { name: 'Alice', age: 25 };

// 2. new Object()
var obj2 = new Object();
obj2.name = 'Bob';

// 3. Object.create() — 指定原型
var proto = { greet() { return 'Hello, ' + this.name; } };
var obj3 = Object.create(proto);
obj3.name = 'Charlie';
obj3.greet(); // "Hello, Charlie"

// 4. 构造函数
function Person(name) { this.name = name; }
var obj4 = new Person('Dave');

// 5. ES6 class
class Animal { constructor(type) { this.type = type; } }
var obj5 = new Animal('cat');

属性描述符

每个属性都有描述符来控制其行为:

var obj = {};
Object.defineProperty(obj, 'name', {
  value: 'test',
  writable: false,
  enumerable: true,
  configurable: false
});

obj.name = 'changed'; // 严格模式报错,非严格模式静默失败
obj.name; // "test"

Object.getOwnPropertyDescriptor(obj, 'name');
// { value: "test", writable: false, enumerable: true, configurable: false }

访问器属性:

var person = {
  _age: 25,
  get age() { return this._age; },
  set age(val) {
    if (val < 0 || val > 150) throw new Error('Invalid age');
    this._age = val;
  }
};
person.age = 30; // OK
person.age = -1; // Error

原型链

每个对象有内部链接指向原型,层层向上直到 null。

function Foo() { this.x = 1; }
Foo.prototype.y = 2;

var obj = new Foo();
obj.x; // 1(自身属性)
obj.y; // 2(原型上)
obj.toString; // function(来自 Object.prototype)

// 原型链:obj -> Foo.prototype -> Object.prototype -> null
Object.getPrototypeOf(obj) === Foo.prototype; // true
Object.getPrototypeOf(Foo.prototype) === Object.prototype; // true
Object.getPrototypeOf(Object.prototype) === null; // true

对象属性操作

var obj = { a: 1, b: 2 };

// delete:只能删除自身属性
delete obj.a; // true
delete obj.toString; // true(但无效,toString 是继承的)

// in:检查属性是否存在(含继承)
'b' in obj; // true
'toString' in obj; // true

// hasOwnProperty:只检查自身属性
obj.hasOwnProperty('b'); // true
obj.hasOwnProperty('toString'); // false

对象遍历方法对比

var parent = { inherited: true };
var obj = Object.create(parent);
obj.a = 1;
obj.b = 2;
Object.defineProperty(obj, 'c', { value: 3, enumerable: false });
方法自身继承不可枚举返回值
for…in-
Object.keys()键数组
Object.values()值数组
Object.entries()[键,值]
getOwnPropertyNames()键数组
// for...in 遍历自身 + 继承的可枚举属性
for (var key in obj) { console.log(key); } // "a", "b", "inherited"

// 只遍历自身属性
for (var key in obj) {
  if (obj.hasOwnProperty(key)) console.log(key); // "a", "b"
}

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

// 含不可枚举属性
Object.getOwnPropertyNames(obj); // ["a", "b", "c"]

对象解构

var user = { name: 'Alice', age: 25, city: 'Shanghai' };

// 基本解构
var { name, age } = user;

// 重命名
var { name: userName } = user; // userName === 'Alice'

// 默认值
var { country = 'China' } = user;

// 剩余属性
var { name, ...rest } = user; // rest === { age: 25, city: 'Shanghai' }

// 嵌套解构
var data = { info: { title: 'Hello' } };
var { info: { title } } = data; // title === 'Hello'

// 函数参数解构
function greet({ name, greeting = 'Hello' }) {
  return `${greeting}, ${name}!`;
}
greet({ name: 'Bob' }); // "Hello, Bob!"

浅拷贝与深拷贝

var original = { name: 'test', nested: { x: 1 }, arr: [1, 2] };

// 浅拷贝
var shallow1 = Object.assign({}, original);
var shallow2 = { ...original };

// 问题:嵌套对象仍是引用
shallow1.nested.x = 99;
original.nested.x; // 99(被修改了)

// 深拷贝方案1:JSON(不支持 function/undefined/Symbol/循环引用)
var deep1 = JSON.parse(JSON.stringify(original));

// 深拷贝方案2:structuredClone(现代浏览器,支持循环引用)
var deep2 = structuredClone(original);

// 深拷贝方案3:递归实现
function deepClone(obj) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  var clone = Array.isArray(obj) ? [] : {};
  for (var key in obj) {
    if (obj.hasOwnProperty(key)) {
      clone[key] = deepClone(obj[key]);
    }
  }
  return clone;
}

常用静态方法

var obj = { a: 1, b: 2, c: 3 };

// Object.keys / values / entries
Object.keys(obj); // ["a", "b", "c"]
Object.values(obj); // [1, 2, 3]
Object.entries(obj); // [["a",1], ["b",2], ["c",3]]

// Object.assign(浅合并)
Object.assign({}, { a: 1 }, { b: 2 }); // { a: 1, b: 2 }

// Object.freeze / seal / preventExtensions
Object.preventExtensions(obj); // 不能添加新属性
Object.seal(obj); // 不能添加/删除,不能重配置
Object.freeze(obj); // 完全冻结

// Object.create(指定原型)
var bare = Object.create(null); // 无原型的纯净对象
'toString' in bare; // false

实例方法

// toString:判断类型的利器
Object.prototype.toString.call([]); // "[object Array]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(/re/); // "[object RegExp]"

// hasOwnProperty
var obj = { a: 1 };
obj.hasOwnProperty('a'); // true
obj.hasOwnProperty('toString'); // false

// isPrototypeOf
var proto = { x: 1 };
var child = Object.create(proto);
proto.isPrototypeOf(child); // true

// valueOf:类型转换时默认调用
var obj = { valueOf() { return 42; } };
obj + 1; // 43

Share this post on:

Previous Post
HTML5 Blob:二进制操作、文件下载与分片上传
Next Post
JavaScript 面向对象:构造函数、原型链与四种继承