这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战
Hello,大家好,我是知秋今天给大家的分享的是我最近在看的一本书《你不知道的Javascrip》里面的有一章的内容对象与类的知识。
对象
- JavaScript 中的对象有字面形式(比如 var a = { .. })和构造形式(比如 var a = new Array(..))。字面形式更常用,不过有时候构造形式可以提供更多选项。
- 许多人都以为“JavaScript 中万物都是对象”,这是错误的。对象是 6 个(或者是 7 个,取决于你的观点)基础类型之一。对象有包括 function 在内的子类型,不同子类型具有不同 的行为,比如内部标签 [object Array] 表示这是对象的子类型数组。
- 对象就是键 / 值对的集合。可以通过 .propName 或者 [“propName”] 语法来获取属性值。访 问属性时,引擎实际上会调用内部的默认 [[Get]] 操作(在设置属性值时是 [[Put]]), [[Get]] 操作会检查对象本身是否包含这个属性,如果没找到的话还会查找 [[Prototype]] 链。
- 属性的特性可以通过属性描述符来控制,比如 writable 和 configurable。此外,可以使用 Object.preventExtensions(..)、Object.seal(..) 和 Object.freeze(..) 来设置对象(及其 属性)的不可变性级别。
- 属性不一定包含值——它们可能是具备 getter/setter 的“访问描述符”。此外,属性可以是 可枚举或者不可枚举的,这决定了它们是否会出现在 for..in 循环中。
- 你可以使用 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 还会禁止删除这个属性。
不变性
- 对象常量 结合 writable:false 和 configurable:false 就可以创建一个真正的常量属性(不可修改、 重定义或者删除)
- 禁止扩展 如果你想禁止一个对象添加新属性并且保留已有属性,可以使用 Object.prevent Extensions(..)
- 密封 Object.seal(..) 会创建一个“密封”的对象,这个方法实际上会在一个现有对象上调用 Object.preventExtensions(..) 并把所有现有属性标记为 configurable:false。
所以,密封之后不仅不能添加新属性,也不能重新配置或者删除任何现有属性(虽然可以 修改属性的值) - 冻结 Object.freeze(..) 会创建一个冻结对象,这个方法实际上会在一个现有对象上调用 Object.seal(..) 并把所有“数据访问”属性标记为 writable:false,这样就无法修改它们 的值。
这个方法是你可以应用在对象上的级别最高的不可变性,它会禁止对于对象本身及其任意 直接属性的修改
[[Get]]
- 对象默认的内置 [[Get]] 操作首先在对象中查找是否有名称相同的属性, 如果找到就会返回这个属性的值。
- 如果没有找到名称相同的属性,按照 [[Get]] 算法的定义会执行另外一种非常重要的行为,就是遍历可能存在的 [[Prototype]] 链, 也就是原型链。
- 如果无论如何都没有找到名称相同的属性,那 [[Get]] 操作会返回值 undefined
[[Put]]
如果已经存在这个属性,[[Put]] 算法大致会检查下面这些内容。
- 属性是否是访问描述符?如果是并且存在 setter 就调用 setter。
- 属性的数据描述符中 writable 是否是 false ?如果是,在非严格模式下静默失败,在 严格模式下抛出 TypeError 异常。
- 如果都不是,将该值设置为属性的值。
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 上。
类
- 类是一种设计模式。许多语言提供了对于面向类软件设计的原生语法。JavaScript 也有类 似的语法,但是和其他语言中的类完全不同。
- 类意味着复制。
- 传统的类被实例化时,它的行为会被复制到实例中。类被继承时,行为也会被复制到子类 中。
多态(在继承链的不同层次名称相同但是功能不同的函数)看起来似乎是从子类引用父 类,但是本质上引用的其实是复制的结果。- JavaScript 并不会(像类那样)自动创建对象的副本。
- 混入模式(无论显式还是隐式)可以用来模拟类的复制行为,但是通常会产生丑陋并且脆 弱的语法,比如显式伪多态(OtherObj.methodName.call(this, …)),这会让代码更加难 懂并且难以维护。
- 此外,显式混入实际上无法完全模拟类的复制行为,因为对象(和函数!别忘了函数也 是对象)只能复制引用,无法复制被引用的对象或者函数本身。忽视这一点会导致许多 问题。
- 总地来说,在 JavaScript 中模拟类是得不偿失的,虽然能解决当前的问题,但是可能会埋 下更多的隐患。