前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入理解javascript对象

深入理解javascript对象

作者头像
javascript艺术
发布2022-11-22 15:26:25
3520
发布2022-11-22 15:26:25
举报
文章被收录于专栏:javascript艺术javascript艺术

理解对象

对象被定义为一组属性的无序集合,对象就是一组没有特定顺序的值。对象的每个value值都由一个key来标识,一个key映射一个value值。

1、Object

创建对象:

/*
*创建了一个名为 person 的对象,而且有三个属性(name、age 和 job)和一个方法
(sayName())
*sayName()方法会显示 this.name 的值
*/
let person = new Object(); 
person.name = "Nicholas"; 
person.age = 29; 
person.job = "Software Engineer"; 
person.sayName = function() { 
 console.log(this.name); 
};

// 或者
let person = { 
 name: "Nicholas", 
 age: 29, 
 job: "Software Engineer", 
 sayName() { 
 console.log(this.name); 
 } 
};
2、属性的类型

属性分两种:数据属性访问器属性

(1)、数据属性

数据属性包含一个保存数据值的位置。值会从这个位置读取,也会写入到这个位置。数据属性有 4 个特性:

Configurable: 表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特 性,以及是否可以把它改为访问器属性。默认情况下值为 true。

Enumerable: 表示属性是否可以通过 for-in 循环返回。默认值为 true。

Writable: 表示属性的值是否可以被修改。默认值为 true。

Value: 包含属性实际的值。读取和写入属性值的位置。默认值为undefined。

要修改属性的默认特性,就必须使用 Object.defineProperty()方法。这个方法接收 3 个参数:要给其添加属性的对象、属性的名称和一个描述符对象。

let person = {}; 
Object.defineProperty(person, "name", { 
 writable: false, 
 value: "Nicholas" 
}); 
console.log(person.name); // "Nicholas" 
person.name = "Greg"; 
console.log(person.name); // "Nicholas"
(2)、访问器属性

访问器属性不包含数据值。相反,它们包含一个获取(getter)函数和一个设置(setter)函数

Configurable: 表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特 性,以及是否可以把它改为数据属性。默认值为 true。

Enumerable: 表示属性是否可以通过 for-in 循环返回。默认值为 true。

Get: 获取函数,在读取属性时调用。默认值为 undefined。

Set: 设置函数,在写入属性时调用。默认值为 undefined。

访问器属性是不能直接定义的,必须使用 Object.defineProperty()

let book = { 
 year_: 2017, 
 edition: 1
}; 
Object.defineProperty(book, "year", { 
 get() { 
 return this.year_; 
 }, 
 set(newValue) { 
 if (newValue > 2017) { 
 this.year_ = newValue; 
 this.edition += newValue - 2017; 
 } 
 } 
}); 
book.year = 2018; 
console.log(book.edition); // 2
Object.define.Properties()

ECMAScript 提供了 Object.define.Properties()方法。这个方法可以通过多个描述符一次性定义多个属性。

let book = {};
      Object.defineProperties(book, {
        year_: {
          value: 2017
        },
        edition: {
          value: 1
        },
        year: {
          get() {
            return this.year_;
          },
          set(newValue) {
            if (newValue > 2017) {
              this.year_ = newValue;
              this.edition += newValue - 2017;
            }
          }
        }
      })
Object.getOwnPropertyDescriptor()

使用 Object.getOwnPropertyDescriptor()方法可以取得指定属性的属性描述符。

let book = {}; 
Object.defineProperties(book, { 
   year_: { 
     value: 2017 
   }, 
   edition: { 
     value: 1 
   }, 
   year: { 
   get: function() { 
     return this.year_; 
   }, 
   set: function(newValue){ 
     if (newValue > 2017) { 
       this.year_ = newValue; 
       this.edition += newValue - 2017; 
     } 
   } 
 } 
});
合并对象Object.assign()

Object.assign()实际上对每个源对象执行的是浅复制。如果多个源对象都有相同的属性,则使 用最后一个复制的值。

let dest, src, result; 
/** 
 * 简单复制
 */ 
dest = {}; 
src = { id: 'src' }; 
result = Object.assign(dest, src); 
// Object.assign 修改目标对象
// 也会返回修改后的目标对象
console.log(dest === result); // true 
console.log(dest !== src); // true 
console.log(result); // { id: src } 
console.log(dest); // { id: src }
属性值简写

ECMAScript 6 为定义和操作对象新增了很多极其有用的语法糖特性。

// 原始写法
let name = 'Matt'; 
let person = { 
 name: name 
}; 
console.log(person); // { name: 'Matt' }

// 等价于
let name = 'Matt'; 
let person = { 
 name 
}; 
console.log(person); // { name: 'Matt' }
可计算属性

可计算属性,在对象字面量中完成动态属性赋值。

const nameKey = 'name'; 
const ageKey = 'age'; 
const jobKey = 'job'; 
let person = { 
 [nameKey]: 'Matt', 
 [ageKey]: 27, 
 [jobKey]: 'Software engineer' 
}; 
console.log(person); // { name: 'Matt', age: 27, job: 'Software engineer' }
对象解构

ECMAScript 6 新增了对象解构语法,可以在一条语句中使用嵌套数据实现一个或多个赋值操作。简单地说,对象解构就是使用与对象匹配的结构来实现对象属性赋值。

let person = { 
 name: 'Matt', 
 age: 27 
}; 
let { name, age } = person; 
console.log(name); // Matt 
console.log(age); // 27
创建对象
工厂模式

函数 createPerson()接收 3 个参数,根据这几个参数构建了一个包含 Person 信息的对象。可以用不同的参数多次调用这个函数,每次都会返回包含 3 个属性和 1 个方法的对象。这种工厂模式虽 然可以解决创建多个类似对象的问题,但没有解决对象标识问题(即新创建的对象是什么类型)。

 let o = new Object(); 
 o.name = name; 
 o.age = age; 
 o.job = job; 
 o.sayName = function() { 
 console.log(this.name); 
 }; 
 return o; 
} 
let person1 = createPerson("Nicholas", 29, "Software Engineer"); 
let person2 = createPerson("Greg", 27, "Doctor");
构造函数模式

以函数的形式为自己的对象类型定义属性和方法。

function Person(name, age, job){ 
 this.name = name; 
 this.age = age; 
 this.job = job; 
 this.sayName = function() { 
   console.log(this.name); 
 }; 
} 
let person1 = new Person("Nicholas", 29, "Software Engineer"); 
let person2 = new Person("Greg", 27, "Doctor"); 
person1.sayName(); // Nicholas 
person2.sayName(); // Greg
原型模式

所有属性和 sayName()方法都直接添加到了 Person 的 prototype 属性上,与构造函数模 式不同,使用这种原型模式定义的属性和方法是由所有实例共享的。

function Person() {} 
Person.prototype.name = "Nicholas"; 
Person.prototype.age = 29; 
Person.prototype.job = "Software Engineer"; 
Person.prototype.sayName = function() { 
 console.log(this.name); 
}; 
let person1 = new Person(); 
person1.sayName(); // "Nicholas" 
let person2 = new Person(); 
person2.sayName(); // "Nicholas" 
console.log(person1.sayName == person2.sayName); // true
原型

无论何时,只要创建一个函数,就会按照特定的规则为这个函数创建一个 prototype 属性(指向 原型对象)。默认情况下,所有原型对象自动获得一个名为 constructor 的属性,指回与之关联的构造函数。对前面的例子而言:

Person.prototype.constructor 指向 Person。

Person.prototype.__proto__ 通过这个属性可以访问对象的原型。

 * 构造函数可以是函数表达式
 * 也可以是函数声明,因此以下两种形式都可以:
 * function Person() {} 
 * let Person = function() {} 
 */ 
function Person() {} 
/** 
 * 声明之后,构造函数就有了一个
 * 与之关联的原型对象:
 */ 
console.log(typeof Person.prototype); 
console.log(Person.prototype); 
// { 
// constructor: f Person(), 
// __proto__: Object
// }

 * 如前所述,构造函数有一个 prototype 属性
 * 引用其原型对象,而这个原型对象也有一个
 * constructor 属性,引用这个构造函数
 * 换句话说,两者循环引用:
 */ 
console.log(Person.prototype.constructor === Person); // true 
/** 
 * 正常的原型链都会终止于 Object 的原型对象
 * Object 原型的原型是 null 
 */ 
console.log(Person.prototype.__proto__ === Object.prototype); // true 
console.log(Person.prototype.__proto__.constructor === Object); // true 
console.log(Person.prototype.__proto__.__proto__ === null); // true 
console.log(Person.prototype.__proto__); 
// { 
// constructor: f Object(), 
// toString: ... 
// hasOwnProperty: ... 
// isPrototypeOf: ... 
// ... 
// }

 person2 = new Person(); 
/** 
 * 构造函数、原型对象和实例
 * 是 3 个完全不同的对象:
 */ 
console.log(person1 !== Person); // true 
console.log(person1 !== Person.prototype); // true 
console.log(Person.prototype !== Person); // true 
/** 
 * 实例通过__proto__链接到原型对象,
 * 它实际上指向隐藏特性[[Prototype]] 
 * 
 * 构造函数通过 prototype 属性链接到原型对象
 * 
 * 实例与构造函数没有直接联系,与原型对象有直接联系
 */ 
console.log(person1.__proto__ === Person.prototype); // true 
conosle.log(person1.__proto__.constructor === Person); // true 
/** 
 * 同一个构造函数创建的两个实例
 * 共享同一个原型对象:
 */ 
console.log(person1.__proto__ === person2.__proto__); // true
原型层级

在通过对象访问属性时,会按照这个属性的名称开始搜索。搜索开始于对象实例本身。如果在这个 实例上发现了给定的名称,则返回该名称对应的值。如果没有找到这个属性,则搜索会沿着指针进入原型对象,然后在原型对象上找到属性后,再返回对应的值。

function Person() {} 
Person.prototype.name = "Nicholas"; 
Person.prototype.age = 29; 
Person.prototype.job = "Software Engineer"; 
Person.prototype.sayName = function() { 
 console.log(this.name); 
}; 
let person1 = new Person(); 
let person2 = new Person(); 
person1.name = "Greg"; 
console.log(person1.name); // "Greg",来自实例
console.log(person2.name); // "Nicholas",来自原型
总结:构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型有一个属性指回构造函数,而实例有一个内部指针指向原型
继承

实现继承主要是通过原型链实现的。

每个构造函数都有一个原型对象,原型有一个属性指回构造函数,而实例有一个内部指针指向原型。如果原型是另一个类型的实例呢?那就意味着这个原型本身有一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。这就是原型链的基本构想。

简单来说:子类想要继承父类的属性和方法,可以将其原型对象指向父类的实例,根据原型链就可以使用到父类的方法和属性

// 代码定义了两个类型:SuperType 和 SubType。
// 父构造函数SuperType
function SuperType() { 
 this.property = true;
} 
// 父构造函数SuperType的原型中,定义个getSuperValue属性
SuperType.prototype.getSuperValue = function() { 
 return this.property; 
}; 
// 定义个子构造函数SubType
function SubType() { 
 this.subproperty = false; 
} 
// 这两个类型的主要区别是 SubType 通过创建 SuperType 的实例并将其赋值给自己的原型 子SubType.prototype 实现了对 父SuperType 的继承。
SubType.prototype = new SuperType(); 
// 子构造函数SubType继承父构造函数SuperType 的原型
SubType.prototype.getSubValue = function () {
     return this.subproperty; 
}; 
let instance = new SubType(); 
console.log(instance.getSuperValue()); // true

这个例子中实现继承的关键,是 SubType 没有使用默认原型,而是将其替换成了一个新的对象。这个新的对象恰好是 SuperType 的实例。这样一来,SubType 的实例不仅能从 SuperType 的实例中继承属性和方法,而且还与 SuperType 的原型挂上了钩。

原型链虽然是实现继承的强大工具,但它也有问题。主要问题出现在原型中包含引用值的时候,**原型中包含的引用值会在所有实例间共享 **

盗用构造函数

为了解决原型包含引用值导致的继承问题,一种叫作“盗用构造函数” 基本思想:在子类构造函数中调用父类构造函数。因为毕竟函数就是在特定上下文中执行代码的简单对象,所以可以使用apply()和 call()方法以新创建的对象为上下文执行构造函数。

function SuperType() { 
 this.colors = ["red", "blue", "green"]; 
} 
function SubType() { 
 // 继承 SuperType 
 SuperType.call(this); 
} 
let instance1 = new SubType(); 
instance1.colors.push("black"); 
console.log(instance1.colors); // "red,blue,green,black" 
let instance2 = new SubType(); 
console.log(instance2.colors); // "red,blue,green"

通过使用 call()(或 apply())方法,SuperType构造函数在为 SubType 的实例创建的新对象的上下文中执行了。这相当于新的 SubType 对象上运行了SuperType()函数中的所有初始化代码。结果就是每个实例都会有自己的 colors 属性。

// 盗用构造函数传递参数
function SuperType(name){ 
 this.name = name; 
} 
function SubType() { 
 // 继承 SuperType 并传参
 SuperType.call(this, "Nicholas"); 
 // 实例属性
 this.age = 29; 
} 
let instance = new SubType(); 
console.log(instance.name); // "Nicholas"; 
console.log(instance.age); // 29

盗用构造函数的问题:盗用构造函数的主要缺点,也是使用构造函数模式自定义类型的问题:必须在构造函数中定义方法,因此函数不能重用。此外,子类也不能访问父类原型上定义的方法,因此所有类型只能使用构造函数模 式。由于存在这些问题,盗用构造函数基本上也不能单独使用。

组合继承

组合继承综合了原型链和盗用构造函数。基本的思路:使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。

function SuperType(name){ 
 this.name = name; 
 this.colors = ["red", "blue", "green"]; 
} 
SuperType.prototype.sayName = function() { 
 console.log(this.name); 
}; 
function SubType(name, age){ 
 // 继承属性
 SuperType.call(this, name); 
 this.age = age; 
} 
// 继承方法
SubType.prototype = new SuperType(); 
SubType.prototype.sayAge = function() { 
 console.log(this.age); 
}; 
let instance1 = new SubType("Nicholas", 29); 
instance1.colors.push("black"); 
console.log(instance1.colors); // "red,blue,green,black" 
instance1.sayName(); // "Nicholas"; 
instance1.sayAge(); // 29
原型式继承

即使不自定义类型也可以通过原型实现对象之间的信息共享。

function object(o) { 
 function F() {} 
 F.prototype = o; 
 return new F(); 
} 
let person = { 
 name: "Nicholas", 
 friends: ["Shelby", "Court", "Van"] 
}; 
let anotherPerson = object(person); 
anotherPerson.name = "Greg"; 
anotherPerson.friends.push("Rob");
let yetAnotherPerson = object(person); 
yetAnotherPerson.name = "Linda"; 
yetAnotherPerson.friends.push("Barbie"); 
console.log(person.friends); // "Shelby,Court,Van,Rob,Barbie"
子类想要继承父类的属性和方法,可以将其原型对象指向父类的实例,根据原型链就可以使用到父类的方法和属性

参考自《javascript高级程序设计》

javascript艺术

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-08-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 javascript艺术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 理解对象
    • 1、Object
      • 2、属性的类型
        • 创建对象
          • 原型
            • 总结:构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型有一个属性指回构造函数,而实例有一个内部指针指向原型
              • 继承
                • 子类想要继承父类的属性和方法,可以将其原型对象指向父类的实例,根据原型链就可以使用到父类的方法和属性
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档