深入理解javascript对象

2023-10-27

理解对象

对象被定义为一组属性的无序集合,对象就是一组没有特定顺序的值。对象的每个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艺术

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

深入理解javascript对象 的相关文章

随机推荐

  • 关于在VS+QtTools环境下使用QList<自定义类型>调试存在乱码问题的研究【记录】

    文章目录 前言 同事的解决方案 当结构体作用域改变时 这时我更加迷惑了但是我发现一个新的点 总结目前发现的俩种解决方案 当我不调试执行运行它它它它有又变了 初步个人结论 Qt测试结果 总结 前言 这个问题是在同事在从数据库读取数据时才发现的
  • Javaweb登录功能优化及注销登录

    登录功能优化 上篇登录功能代码在从数据库取出用户信息的代码中少写了取出用户姓名 现在将其加上user setName rs getString u name 在登录成功后右上角会有一个xx用户 我们希望把用户名字显示在xx上 所以对其adm
  • idea2021搭建SpringMVC项目

    打开IDEA 点击file gt close Project 点击完了之后跳转到idea的首页 点击New Project 左侧选择maven 在右侧勾选Create from archetype 并且选择结尾带webapp的选项的类型 然
  • Elasticsearch安装ik分词插件

    前置条件 如果发现问题请留言 如果有发现不正确的地方 欢迎留言指正 感激不尽 已安装好Elasticsearch 本次安装插件版本为7 3 1 需与elasticsearch版本一致 elasticsearch安装在 home elk el
  • Fluid主题出错解决方案

    Fluid主题排版出错 重装一次主题解决 一 存在问题 大半个月没有浏览Hexo博客 再次浏览的时候发现网页的排版布局竟然出错了 PS 导航栏排版出错 PS 内容板块的宽度失效 PS footer部分排版也出错了 打开 开发者模式 查看一下
  • k8s远程debug

    k8s远程debug 1 方案1 方案1是不行的 因为k8s的ingress走的7层协议 1 1 应用 启动debug端口 java agentlib jdwp transport dt socket server y suspend n
  • PCL点云处理之添加高斯噪点的两种方法(详细注释版)(一百八十一)

    PCL点云处理之添加高斯噪点的两种方法 详细注释版 一百八十一 一 实验效果 二 算法简介 三 具体流程 四 PCL自带函数实现 1 代码 2 结果 五 Boost函数实现 1 代码 2 结果 总结 一 实验效果 通过实验测试 效果如上所示
  • java usb camera_android4.0 USB Camera实例(一)HAL层

    一直想自己写一个从HAL层到应用层的Camera例子 android4 0上usb camera用不了 所以决定自己写一个 usb camera和coms原理都是一样的 基本v4l2 只不过源码数据格式不一样而已 下面我们就从HAL层开始
  • IDEA在包下右键new没有Servlet选项?解决办法

    在包下右键没有new一个Servlet 1 首先检查pom xml文件中Tomcat和Servlet的坐标有没有导入 Tomcat坐标
  • Ubuntu安装psycopg2小记

    作者 Wally Yu 在windows上和Linux上安装psycopg2都遇到了点小插曲 记录如下 Windows下 1 前往官网下载源代码 http www initd org psycopg 2 解压 3 运行python setu
  • Docker-容器

    Docker的应用场景 Web 应用的自动化打包和发布 自动化测试和持续集成 发布 在服务型环境中部署和调整数据库或其他的后台应用 Docker的架构 Docker 镜像 Images Docker 镜像是用于创建 Docker 容器的模板
  • taro 支付宝/微信小程序的chooseImage真机和开发工具上的区别

    支付宝小程序 微信小程序
  • 创建Win PE启动盘(小白都会装系统)

    第一步 下载启动盘制作软件 打开搜索引擎 搜索 电脑店 找到下图链接 打开电脑店网站如下图所示 然后点击 完整版下载 开始下载制作PE系统的软件 下载完成后解压到当前目录 如下图 打开后 找到DianNaoDian exe文件双击打开 第二
  • 关于IDEA在创建Maven子模块后的pom.xml文件没有parent标签的解决方法。

    关于IDEA在创建Maven子模块后的pom xml文件没有parent标签的解决方法 问题 我们在创建Maven子模块后的pom xml文件一开始是有parent标签的 然后加载完就直接消失了 解决方法 直接手打上去 具体格式网上都有说怎
  • SQL Server(MMS)开启代理服务器(agent)方法(本篇版本展示界面为SQLserver2014)

    第一步 在SQL Server Management Studio中连接到SQL Server实例后 会显示 SQL Server 代理 节点 如果当前该实例的Agent服务没有启动 SQL Server 代理 后边就会显示 已禁用代理XP
  • libcurl库及curl API的简介

    目录 一 libcurl简介 二 curl API简介 三 库安装编译方法 内容来源 Http协议之libcurl实现 谢呈勖 博客园 cnblogs com 一 libcurl简介 libcurl是一个跨平台的网络协议库 支持http h
  • JAVA Eclipse连接SQL Server 2019并从数据库中读取表中数据

    一 进入SQL Server 配置登录名和密码 这里有默认的sa 一开始是禁用的 选中sa 右键 属性 1 授予and启用 2 设置登录名和密码 点击确定 然后关闭SQL 重新进入的时候身份验证选择SQL Server身份验证 二 新建一个
  • maven 配置多镜像

    1 配置maven的setting xml
  • Activity之任务和返回栈

    一个应用程序中会有多个activity 每个activity一般都有自己独立的功能 我们可以用activity启动自己应用中的另一个activity 例如 从一个数据列表界面 跳转到一个数据详情界面 也可以用我们的activity去打开其他
  • 深入理解javascript对象

    理解对象 对象被定义为一组属性的无序集合 对象就是一组没有特定顺序的值 对象的每个value值都由一个key来标识 一个key映射一个value值 1 Object 创建对象 创建了一个名为 person 的对象 而且有三个属性 name