重新学习 JavaScript 中的 对象 (Object)

简介: 重新学习 JavaScript 中的 对象 (Object)

语法

// 声明形式(文字)
var myObj = {
  key: value,
};

// 构造形式
var myObj2 = new Object();
myObj2.key = value;

类型

主要类型:

  • 基本类型

    • string
    • number
    • boolean
    • null
    • undefined
  • 引用类型

    • object

内置对象

  • String
  • Number
  • Boolean
  • Object
  • Function
  • Array
  • Date
  • RegExp
  • Error

这些内置对象从表现形式来说很像其他语言中的类型(type)或者类(class),比如 Java 中的 String 类。但是在 JavaScript 中,它们实际上只是一些内置函数。这些内置函数可以当作构造函数来使用,从而可以构造一个对应子类型的新对象。

var strPrimitive = "string"; // 字面量
typeof strPrimitive; // "string"
strPrimitive instanceof String; // false

var strObj = new String("string");
typeof strObj; // "object"
strObj instanceof String; // true

Object.prototype.toString.call(strObj); // "[object String]"

对象的内容

在引擎内部,这些值的存储方式是多种多样的,一般并不会存在对象容器内部。存储在对象容器内部的是这些属性的名称,它们就像指针(从技术角度来说就是引用)一样,指向这些值真正的存储位置。

var myObj = {
  a: 2
};
myObj.a; // 2
myObj['a']; // 2

在对象中,属性名永远都是字符串。如果你使用string (字面量)以外的其他值作为属性名,那它首先会被转换为一个字符串。即使是数字也不例外。

var myObj = {};

myObj[true] = "true";
myObj[1] = "1";
myObj[myObj] = "myObj";

myObj['true']; // "true"
myObj['1']; // "1"
myObj['[object Object]']; // "myObj"

可计算属性名

var prefix = 'foo';

var myObj = {
  [prefix + 'bar']: 'hello',
  [prefix + 'baz']: 'world'
};

myObj['foobar']; // "hello"
myObj['foobaz']; // "world"

属性与方法

由于函数很容易被认为是属于某个对象,在其他语言中,属于对象(也被称为“类”)的函数通常被称为“方法”。

从技术角度来说,函数永远不会“属于”一个对象,所以把对象内部引用的函数称为“方法”似乎有点不妥。

因为 this 是在运行时根据调用位置动态绑定的,所以函数和对象的关系最多也只能说是间接关系。

无论返回值是什么类型,每次访问对象的属性就是属性访问。如果属性访问返回的是一个函数,那它也并不是一个“方法”。属性访问返回的函数和其他函数没有任何区别(除了可能发生的隐式绑定 this ,就像我们刚才提到的)。

function foo () {
  console.log('foo');
}

var someFoo = foo;

var myObj = {
  someFoo: foo,
};

foo; // function foo() {...}
someFoo; // function foo() {...}
myObj.someFoo; // function foo() {...}

someFoomyObject.someFoo 只是对于同一个函数的不同引用,并不能说明这个函数是特别的或者“属于”某个对象。如果 foo() 定义时在内部有一个 this 引用,那这两个函数引用的唯一区别就是 myObject.someFoo 中的 this 会被隐式绑定到一个对象。无论哪种引用形式都不能称之为“方法”。

数组

数组也支持 [] 访问形式,数组有一套更加结构化的值存储机制(不过仍然不限制值的类型)。数组期望的是数值下标,也就是说值存储的位置(通常被称为索引)是整数。

var myArray = ['foo', 42, 'bar'];

myArray.length; // 3
myArray[0]; // "foo"
myArray[1]; // 42

// 数组也会是对象
myArray.baz = 'baz';
myArray.length; // 3
myArray.baz; // "baz"

// 如果试图向数组添加一个属性,但是属性名“看起来”像一个数字,那它会变成一个数值下标
myArray['3'] = 'baz';
myArray.length; // 4
myArray[3]; // "baz"

复制对象

对于 JSON 安全(也就是说可以被序列化为一个 JSON 字符串并且可以根据这个字符串解析出一个结构和值完全一样的对象)的对象来说,有一种巧妙的复制方法:

var myObj = JSON.parse(JSON.stringify(otherObj));

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

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

属性描述符

var myObj = {
  a: 2
};

Object.getOwnPropertyDescriptor(myObj, 'a');
// {value: 2, writable: true, enumerable: true, configurable: true}

在创建普通属性时属性描述符会使用默认值,可以使用 Object.defineProperty() 来添加一个新属性或者修改一个已有属性(如果它是 configurable )并对特性进行设置。

不变性

有时候会希望属性或者对象是不可改变(无论有意还是无意)的,在 ES5 中可以通过很多种方法来实现。很重要的一点是,所有 的方法创建的都是浅不变形,也就是说,它们只会影响目标对象和它的直接属性。如果目标对象引用了其他对象(数组、对象、函数,等),其他对象的内容不受影响,仍然是可变的。

  1. 对象常量
var myObj = {};
Object.defineProperty(myObj, 'a', {
  value: 2,
  writable: false,
  enumerable: true,
  configurable: false
});
  1. 禁止扩展

禁止一个对象添加新属性并且保留已有属性

var myObject = {
  a: 2
};

Object.preventExtensions(myObject);

myObj.b = 3;
myObj.b; // undefined
  1. 密封

Object.seal() 会创建一个“密封”的对象,这个方法实际上会在一个现有对象上调用 Object.preventExtensions() 并把所有现有属性标记为 configurable:false

所以,密封之后不仅不能添加新属性,也不能重新配置或者删除任何现有属性(虽然可以修改属性的值)。

  1. 冻结

Object.freeze() 会创建一个冻结对象,这个方法实际上会在一个现有对象上调用 Object.seal() 并把所有“数据访问”属性标记为 writable:false ,这样就无法修改它们的值。

[[Get]]

var myObj = {
  a: 2
};
myObj.a; // 2

在语言规范中,myObject.amyObject 上实际上是实现了 [[Get]] 操作(有点像函数调用:[[Get]]() )。对象默认的内置[[Get]] 操作首先在对象中查找是否有名称相同的属性,如果找到就会返回这个属性的值。如果没找到就按原型链继续找,如果无论如何都没有找到名称相同的属性,那[[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 myObj = {
  get a() {
    return 2;
  }
};

Object.defineProperty(myObj, 'b', {
  get: function() {
    return this.a * 2;
  },
  enumerable: true
});

myObj.a; // 2
myObj.b; // 4

存在性

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

var myObj = {
  a: 2
};

('a' in myObj); // true
('b' in myObj); // false

myObject.hasOwnProperty('a'); // true
myObject.hasOwnProperty('b'); // false

in 操作符会检查属性是否在对象及其 [[Prototype]] 原型链中。相比之下,hasOwnProperty() 只会检查属性是否在 myObject 对象中,不会检查 [[Prototype]] 链。

所有的普通对象都可以通过对于 Object.prototype 的委托来访问 hasOwnProperty() ,但是有的对象可能没有连接到 Object.prototype (通过Object.create(null) 来创建)。在这种情况下,形如 myObejct.hasOwnProperty() 就会失败。这时可以使用一种更加强硬的方法来进行判断:Object.prototype.hasOwnProperty.call(myObject,"a") ,它借用基础的 hasOwnProperty() 方法并把它显式绑定到 myObject 上。

遍历

for..in 循环可以用来遍历对象的可枚举属性列表(包括 [[Prototype]] 链)。但是如何遍历属性的值呢?

对于数值索引的数组来说,可以使用标准的 for 循环来遍历值:

var myArray = [1, 2, 3];
for (var i = 0; i < myArray.length; i++) {
  console.log(myArray[i]);
}
// 1 2 3

ES5 中增加了一些数组的辅助迭代器,包括 forEach()every()some() 。每种辅助迭代器都可以接受一个回调函数并把它应用到数组的每个元素上,唯一的区别就是它们对于回调函数返回值的处理方式不同。

使用 for..in 遍历对象是无法直接获取属性值的,因为它实际上遍历的是对象中的所有可枚举属性,需要手动获取属性值。

那么如何直接遍历值而不是数组下标(或者对象属性)呢?幸好,ES6 增加了一种用来遍历数组的 for..of 循环语法:

for (var value of myArray) {
  console.log(value);
}
// 1 2 3
相关文章
|
8天前
|
存储 消息中间件 人工智能
【05】AI辅助编程完整的安卓二次商业实战-消息页面媒体对象(Media Object)布局实战调整-按钮样式调整实践-优雅草伊凡
【05】AI辅助编程完整的安卓二次商业实战-消息页面媒体对象(Media Object)布局实战调整-按钮样式调整实践-优雅草伊凡
37 11
【05】AI辅助编程完整的安卓二次商业实战-消息页面媒体对象(Media Object)布局实战调整-按钮样式调整实践-优雅草伊凡
|
3月前
|
前端开发 JavaScript
个人征信电子版无痕修改, 个人信用报告pdf修改,js+html+css即可实现【仅供学习用途】
本代码展示了一个信用知识学习系统的前端实现,包含评分计算、因素分析和建议生成功能。所有数据均为模拟生成
|
2月前
|
JavaScript 前端开发 开发者
讲述Vue框架中用于对象响应式变化的Object.defineProperty函数。
综上所述,Vue.js通过 `Object.defineProperty()`提供了强大的响应式能力,使得状态管理变得简洁高效。这种能力是Vue.js受到广大开发者青睐的重要原因之一。尽管Vue 3.x使用Proxy替代了该方法,但对于Vue 2.x及其之前版本,`Object.defineProperty()`是理解Vue.js内部工作机制不可或缺的一部分。
125 27
|
3月前
|
前端开发
个人征信PDF无痕修改软件,个人征信模板可编辑,个人征信报告p图神器【js+html+css仅供学习用途】
这是一款信用知识学习系统,旨在帮助用户了解征信基本概念、信用评分计算原理及信用行为影响。系统通过模拟数据生成信用报告,涵盖还款记录
|
5月前
|
编解码 JavaScript 前端开发
【Java进阶】详解JavaScript的BOM(浏览器对象模型)
总的来说,BOM提供了一种方式来与浏览器进行交互。通过BOM,你可以操作窗口、获取URL、操作历史、访问HTML文档、获取浏览器信息和屏幕信息等。虽然BOM并没有正式的标准,但大多数现代浏览器都实现了相似的功能,因此,你可以放心地在你的JavaScript代码中使用BOM。
155 23
|
4月前
|
JavaScript 数据可视化 前端开发
three.js简单实现一个3D三角函数学习理解
1.Three.js简介 Three.js是一个基于JavaScript编写的开源3D图形库,利用WebGL技术在网页上渲染3D图形。它提供了许多高级功能,如几何体、纹理、光照、阴影等,以便开发者能够快速地创建复杂且逼真的3D场景。同时,Three.js还具有很好的跨平台和跨浏览器兼容性,让用户无需安装任何插件就可以在现代浏览器上观看3D内容。
172 0
|
8月前
|
安全 Java
Object取值转java对象
通过本文的介绍,我们了解了几种将 `Object`类型转换为Java对象的方法,包括强制类型转换、使用 `instanceof`检查类型和泛型方法等。此外,还探讨了在集合、反射和序列化等常见场景中的应用。掌握这些方法和技巧,有助于编写更健壮和类型安全的Java代码。
455 17
|
6月前
|
前端开发 数据处理
对象数据的读取,看这一篇就够了!Object.keys()、Object.values()和Object.entries()用法详解;如何获取对象原型链上的属性
Object.keys()、Object.values()和Object.entries()都是利于对象操作的便捷方法,能有效提升数据处理的效率。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
10月前
|
JSON 前端开发 JavaScript
JavaScript中对象的数据拷贝
本文介绍了JavaScript中对象数据拷贝的问题及解决方案。作者首先解释了对象赋值时地址共享导致的值同步变化现象,随后提供了五种解决方法:手动复制、`Object.assign`、扩展运算符、`JSON.stringify`与`JSON.parse`组合以及自定义深拷贝函数。每种方法都有其适用场景和局限性,文章最后鼓励读者关注作者以获取更多前端知识分享。
134 1
JavaScript中对象的数据拷贝