对象和类


对象

  1. JavaScript 中的对象有字面形式(比如 var a = { .. })和构造形式(比如 var a = new Array(..))。字面形式更常用,不过有时候构造形式可以提供更多选项。

  2. 许多人都以为“JavaScript 中万物都是对象”,这是错误的。对象是 6 个(或者是 7 个,取决于你的观点)基础类型之一。对象有包括 function 在内的子类型,不同子类型具有不同 的行为,比如内部标签 [object Array] 表示这是对象的子类型数组。

  3. 对象就是键 / 值对的集合。可以通过 .propName 或者 [“propName”] 语法来获取属性值。访 问属性时,引擎实际上会调用内部的默认 [[Get]] 操作(在设置属性值时是 [[Put]]), [[Get]] 操作会检查对象本身是否包含这个属性,如果没找到的话还会查找 [[Prototype]] 链。

  4. 属性的特性可以通过属性描述符来控制,比如 writable 和 configurable。此外,可以使用 Object.preventExtensions(..)、Object.seal(..) 和 Object.freeze(..) 来设置对象(及其 属性)的不可变性级别。

  5. 属性不一定包含值——它们可能是具备 getter/setter 的“访问描述符”。此外,属性可以是 可枚举或者不可枚举的,这决定了它们是否会出现在 for..in 循环中。

  6. 你可以使用 ES6 的 for..of 语法来遍历数据结构(数组、对象,等等)中的值,for..of 会寻找内置或者自定义的 @@iterator 对象并调用它的 next() 方法来遍历数据值

对象可以通过两种形式定义:声明(文字)形式和构造形式。

var myObj = {     key: value     // ... };
var myObj = new Object(); 
myObj.key = value;
  • 唯一的区别是,在文字声明中你可以添加多个 键 / 值对,但是在构造形式中你必须逐个添加属性。字面量方式更常用。

类型

对象是 JavaScript 的基础。在 JavaScript 中一共有六种主要类型(术语是“语言类型”):

  • string

  • number

  • boolean

  • null

  • undefined

  • object

注意:简单基本类型(string、boolean、number、null 和 undefined)本身并不是对象。 null 有时会被当作一种对象类型,但是这其实只是语言本身的一个 bug,即对 null 执行 typeof null 时会返回字符串 “object”。1 实际上,null 本身是基本类型。

内置对象

JavaScript 中还有一些对象子类型,通常被称为内置对象

  • String
  • Number
  • Boolean
  • Object
  • Function
  • Array
  • Date
  • RegExp
  • Error
var strPrimitive = "I am a string"; 
console.log( strPrimitive.length ); // 13 
console.log( strPrimitive.charAt( 3 ) ); // "m"
  • 使用以上两种方法,我们都可以直接在字符串字面量上访问属性或者方法,之所以可以这 样做,是因为引擎自动把字面量转换成 String 对象,所以可以访问属性和方法。

内容

对象的内容是由一些存储在特定命名位置的(任意类型的)值组成的, 我们称之为属性

var myObject = {   
    a: 2 
}; 
 myObject.a; // 2  
myObject["a"]; // 2
  • 这两种语法的主要区别在于 . 操作符要求属性名满足标识符的命名规范,而 [“..”] 语法 可以接受任意 UTF-8/Unicode 字符串(有些不是一个有效 的标识符属性名)作为属性名或者属性是一个变量时还有就是ES6 增加的可计算属性名使用 [ ] 包裹一个表达式来当作属性名。如myObject[prefix + name]

对象复制

  • 浅拷贝(shallow copy):只复制指向某个对象的指针,而不复制对象本身,新旧对象共享一块内存;   
  • 深拷贝(deep copy):复制并创建一个一摸一样的对象,不共享内存,修改新对象,旧对象保持不变。

ES6 定义了 Object.assign(..) 方 法来实现浅复制。Object.assign(..) 方法的第一个参数是目标对象,之后还可以跟一个 或多个源对象。它会遍历一个或多个源对象的所有可枚举(enumerable) 的自有键(owned key)并把它们复制(使用 = 操作符赋值)到目标对象,最 后返回目标对象

var newObj = Object.assign( {}, myObject ); 

newObj.a; // 2 newObj.b === anotherObject; // true  
newObj.c === anotherArray; // true 
newObj.d === anotherFunction; // true

属性描述符

var myObject = {      a:2 }; 

Object.getOwnPropertyDescriptor( myObject, "a" );  
    // { 
    //    value: 2,
    //    writable: true,
    //    enumerable: true,
    //    configurable: true 
    // }
  • 普通的对象属性对应的属性描述符(也被称为“数据描述符”,因为它 只保存一个数据值)可不仅仅只是一个 2。它还包含另外三个特性:writable(可写)、 enumerable(可枚举)和 configurable(可配置)。

可以使用 Object.defineProperty(..) 来添加一个新属性或者修改一个已有属性(如果它是 configurable)并对特性进行设置

var myObject = {}; 
Object.defineProperty( myObject, "a", {
    value: 2, 
    writable: true,   
    configurable: true, 
    enumerable: true } );  

myObject.a; // 2
  • 注意:把 configurable 修改成 false 是单向操作,无法撤销!除了无法修改,configurable:false 还会禁止删除这个属性。

不变性

  1. 对象常量 结合 writable:false 和 configurable:false 就可以创建一个真正的常量属性(不可修改、 重定义或者删除)
  2. 禁止扩展 如果你想禁止一个对象添加新属性并且保留已有属性,可以使用 Object.prevent Extensions(..)
  3. 密封 Object.seal(..) 会创建一个“密封”的对象,这个方法实际上会在一个现有对象上调用 Object.preventExtensions(..) 并把所有现有属性标记为 configurable:false。
    所以,密封之后不仅不能添加新属性,也不能重新配置或者删除任何现有属性(虽然可以 修改属性的值)
  4. 冻结 Object.freeze(..) 会创建一个冻结对象,这个方法实际上会在一个现有对象上调用 Object.seal(..) 并把所有“数据访问”属性标记为 writable:false,这样就无法修改它们 的值。
    这个方法是你可以应用在对象上的级别最高的不可变性,它会禁止对于对象本身及其任意 直接属性的修改

[[Get]]

  1. 对象默认的内置 [[Get]] 操作首先在对象中查找是否有名称相同的属性, 如果找到就会返回这个属性的值。
  2. 如果没有找到名称相同的属性,按照 [[Get]] 算法的定义会执行另外一种非常重要的行为,就是遍历可能存在的 [[Prototype]] 链, 也就是原型链。
  3. 如果无论如何都没有找到名称相同的属性,那 [[Get]] 操作会返回值 undefined

[[Put]]

如果已经存在这个属性,[[Put]] 算法大致会检查下面这些内容。

  1. 属性是否是访问描述符?如果是并且存在 setter 就调用 setter。
  2. 属性的数据描述符中 writable 是否是 false ?如果是,在非严格模式下静默失败,在 严格模式下抛出 TypeError 异常。
  3. 如果都不是,将该值设置为属性的值。

Getter和Setter

在 ES5 中可以使用 getter 和 setter 部分改写默认操作,但是只能应用在单个属性上,无法 应用在整个对象上。getter 是一个隐藏函数,会在获取属性值时调用。setter 也是一个隐藏 函数,会在设置属性值时调用。
当你给一个属性定义 getter、setter 或者两者都有时,这个属性会被定义为“访问描述 符”(和“数据描述符”相对)。对于访问描述符来说,JavaScript 会忽略它们的 value 和 writable 特性,取而代之的是关心 set 和 get(还有 configurable 和 enumerable)特性。

存在性

不访问属性值的情况下判断对象中是否存在这个属性

var myObject = {      a:2 }; 

("a" in myObject); // true
("b" in myObject); // false  

myObject.hasOwnProperty( "a" ); // true 
myObject.hasOwnProperty( "b" ); // false
  • in 操作符会检查属性是否在对象及其 [[Prototype]] 原型链中。
  • 看起来 in 操作符可以检查容器内是否有某个值,但是它实际上检查的是某 个属性名是否存在。对于数组来说这个区别非常重要,4 in [2, 4, 6] 的结 果并不是你期待的 True,因为 [2, 4, 6] 这个数组中包含的属性名是 0、1、 2,没有 4。
  • 相比之下, hasOwnProperty(..) 只会检查属性是否在 myObject 对象中,不会检查 [[Prototype]] 链。
  • 所有的普通对象都可以通过对于 Object.prototype 的委托来访问 hasOwnProperty(..),但是有的对象可能没有连接到 Object.prototype(通过 Object. create(null) )。在这种情况下,形如 myObejct.hasOwnProperty(..) 就会失败。
  • 这时可以使用一种更加强硬的方法来进行判断:Object.prototype.hasOwnProperty. call(myObject,”a”),它借用基础的 hasOwnProperty(..) 方法并把它显式绑定到 myObject 上。

  1. 类是一种设计模式。许多语言提供了对于面向类软件设计的原生语法。JavaScript 也有类 似的语法,但是和其他语言中的类完全不同。
  2. 类意味着复制。
  3. 传统的类被实例化时,它的行为会被复制到实例中。类被继承时,行为也会被复制到子类 中。
    多态(在继承链的不同层次名称相同但是功能不同的函数)看起来似乎是从子类引用父 类,但是本质上引用的其实是复制的结果。
  4. JavaScript 并不会(像类那样)自动创建对象的副本。
  5. 混入模式(无论显式还是隐式)可以用来模拟类的复制行为,但是通常会产生丑陋并且脆 弱的语法,比如显式伪多态(OtherObj.methodName.call(this, …)),这会让代码更加难 懂并且难以维护。
  6. 此外,显式混入实际上无法完全模拟类的复制行为,因为对象(和函数!别忘了函数也 是对象)只能复制引用,无法复制被引用的对象或者函数本身。忽视这一点会导致许多 问题。
  7. 总地来说,在 JavaScript 中模拟类是得不偿失的,虽然能解决当前的问题,但是可能会埋 下更多的隐患。

文章作者: XiaoQi
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 XiaoQi !
 上一篇
类型和语法 类型和语法
1.类型 JavaScript 有 七 种 内 置 类 型:null、undefined、boolean、number、string、object 和 symbol,可以使用 typeof 运算符来查看。 变量没有类型,但它们持有的值有类
2020-08-03
下一篇 
闭包 闭包
作用域闭包 闭包就好像从 JavaScript 中分离出来的一个充满神秘色彩的未开化世界,只有最勇敢的人 才能够到达那里。但实际上它只是一个标准,显然就是关于如何在函数作为值按需传递的 词法环境中书写代码的。 当函数可以记住并访问所在的词
2020-08-01
  目录