JavaScript面向对象—对象的创建和操作

前言

虽然说在JavaScript编程语言中,函数是第一公民,但是JavaScript不仅支持函数式编程,也支持面向对象编程。JavaScript对象设计成了一组属性的无序集合,由key和value组成,key为一个标识符名称,而value可以是任意类型的值,当函数作为对象的属性值时,这个函数就可以称之为对象的方法。下面就来看看JavaScript的面向对象吧。

1.JavaScript创建对象的方式

一般地,常用于创建对象的方式有两种,早期经常使用Object类,通过new关键字来创建一个对象,有点类似于Java中创建对象,后来为了方便就直接使用对象字面量的方式来创建对象了,用法更为简洁。

  • 使用Object类创建对象;

    1. const obj = new Object() // 创建一个空对象
    2. // 往对象中添加属性
    3. obj.name = 'curry'
    4. obj.age = 30
  • 使用对象字面量创建对象;

    1. // 直接往{}添加键值对
    2. const obj = {
    3. name: 'curry',
    4. age: 30
    5. }

2.对象属性操作的控制

对象创建出来后,如何对该对象进行操作控制呢?这里涉及到一个很重要的方法:Object.defineProperty()。

2.1.Object.defineProperty()

该方法可以在对象上定义一个新的属性,也可修改对象现有属性,并将该对象返回。

  1. Object.defineProperty(obj, prop, descriptor)

接收三个参数:

  • obj:指定操作的对象;
  • prop:指定需要定义或修改的属性名称;
  • description:定义或修改的属性描述符;

2.2.属性描述符的分类

什么是属性描述符?顾名思义就是对对象中的属性进行描述,简单来说就是给对象某个属性指定一些规则。属性描述符主要分为数据属性描述符存取属性描述符两种类型。

对于属性描述符中的属性是否两者都可以设置呢?其实数据和存取属性描述符两者是有区别,下面的表格统计了两者可用和不可用的属性:

属性 configurable enumerable value writable get set
数据属性描述符 可以 可以 可以 可以 不可以 不可以
存取属性描述符 可以 可以 不可以 不可以 可以 可以

那么为什么有些属性可以用,有些属性又不能用呢?因为数据属性描述符和存取属性描述符所担任的角色不一样,下面就来详细介绍一下,它们两者的区别。

2.3.数据属性描述符

从上面的表格可以知道,数据属性描述符可以使用configurable、enumerable、value、writable。而这就是数据属性描述符的四个特性。

  • Configurable:表示是否可以通过delete删除对象属性,是否可以修改它的特性,或者是否可以将它修改为存取属性描述符。当通过new Object()或者字面量的方式创建对象时,其中的属性的configurable默认为true,当通过属性描述符定义一个属性时,其属性的configurable默认为false
  • Enumerable:表示是否可以通过for-in或者Object.keys()返回该属性。当通过new Object()或者字面量的方式创建对象时,其中的属性的enumerable默认为true,当通过属性描述符定义一个属性时,其属性的enumerable默认为false
  • Writable:表示是否可以修改属性的值。当通过new Object()或者字面量的方式创建对象时,其中的属性的writable性描述符定义一个属性时,其属性的writable默认为false
  • Value:属性的value值,读取属性时会返回该值,修改属性时会对其进行修改。(默认:undefined)
  1. const obj = {
  2. name: 'curry'
  3. }
  4. Object.defineProperty(obj, 'age', {
  5. configurable: false, // age属性是否可以删除,默认false
  6. enumerable: false, // age属性是否可以枚举,默认false
  7. writable: false, // age属性是否可以写入(修改),默认false
  8. value: 30 // age属性的值,默认undefined
  9. })
  10. // 当configurable为false,age属性是不可被删除的
  11. delete obj.age
  12. console.log(obj) // { name: 'curry', age: 30 }
  13. // 当writable为false,age属性的值是不可被修改的
  14. obj.age = 18
  15. console.log(obj) // { name: 'curry', age: 30 }
  1. // 如果将enumerable修改为false,age属性是不可以被遍历出来的
  2. for (const key in obj) {
  3. console.log(key) // name
  4. }

2.4.存取属性描述符

存取属性描述符可以使用configurable、enumerable、get、set。在获取对象某个属性值时,可以通过get来拦截,在设置对象某个属性值时,可以通过set来拦截。configurable和enumerable的用法和特性跟数据属性描述符一样。

  • Get:获取属性时会执行的函数。(默认undefined)
  • Set:设置属性时会执行的函数。(默认undefined)

get和set的使用场景:

  • 隐藏某一个私有属性,不希望直接被外界使用和赋值。如下代码_age表示不想直接被外界使用,外界就可以通过使用age的set和get来访问设置_age了。

  • 如果希望截获某一个属性它访问和设置值的过程。(Vue2的响应式原理就在这)

    1. const obj = {
    2. name: 'curry',
    3. _age: 30
    4. }
    5. // 注意:这里的this是指向obj对象的
    6. Object.defineProperty(obj, 'age', {
    7. configurable: true,
    8. enumerable: true,
    9. get: function() {
    10. console.log('age属性被访问了')
    11. return this._age
    12. },
    13. set: function(newValue) {
    14. console.log('age属性被设置了')
    15. this._age = newValue
    16. }
    17. })
    18. obj.age // age属性被访问了
    19. obj.age = 18 // age属性被设置了

2.5.同时给多个属性定义属性描述符

上面使用Object.defineProperty()方法都是给单个属性进行定义描述符,想要一次性定义多个属性,那么就可以使用Object.defineProperties()方法了。写法如下:

  1. Object.defineProperties(obj, {
  2. name: {
  3. configurable: true,
  4. enumerable: true,
  5. writable: true,
  6. value: 'curry'
  7. },
  8. age: {
  9. configurable: false,
  10. enumerable: false,
  11. get: function() {
  12. return this._age
  13. },
  14. set: function(newValue) {
  15. this._age = newValue
  16. }
  17. }
  18. })

3.Object中常用的方法

上面介绍了Object中defineProperty和defineProperties两个方法。其实Object中还有很多方法,下面介绍一些常用的。

  • 获取对象的属性描述符:

    • 获取单个属性:Object.getOwnPropertyDescriptor
    • 获取所有属性:Object.getOwnPropertyDescriptors
    1. const obj = {
    2. name: 'curry',
    3. age: 30
    4. }
    5. console.log(Object.getOwnPropertyDescriptor(obj, 'age')) // { value: 30, writable: true, enumerable: true, configurable: true }
    6. console.log(Object.getOwnPropertyDescriptors(obj))
    7. /*
    8. {
    9. name: {
    10. value: 'curry',
    11. writable: true,
    12. enumerable: true,
    13. configurable: true
    14. },
    15. age: { value: 30, writable: true, enumerable: true, configurable: true }
    16. }
    17. */
  • Object.preventExtensions():禁止对象扩展新属性,给一个对象添加新的属性会失败(在严格模式下会报错)。

  • Object.seal():将对象密封起来,不允许配置和删除属性。(实际还是调用preventExtensions,并且将现有属性的configurable设置为false

  • Object.freeze():将对象冻结起来,不允许修改对象现有属性。(实际上是调用seal,并且将现有属性的writable设置为false

4.JavaScript创建多个对象

上面提到的创建对象的方式仅适用于创建单个对象适用,如果有多个对象比较类似,那么一个个创建必然是很麻烦的,如何批量创建对象呢?JavaScript也给我们提供了一些方案。

4.1.方案一:工厂函数

如果我们不想在创建对象时做重复的工作,那么就可以定义一个函数为我们去做这些重复性的工作,我们只需要将属性对应的值传入函数即可。

  1. function createObj(name, age) {
  2. // 创建一个空对象
  3. const obj = {}
  4. // 设置对应属性值
  5. obj.name = name
  6. obj.age = age
  7. // 公共方法共用
  8. obj.sayHello = function() {
  9. console.log(`My name is ${this.namename}, I'm ${this.age} years old.`)
  10. }
  11. // 将对象返回
  12. return obj
  13. }
  14. const obj1 = createObj('curry', 30)
  15. const obj2 = createObj('kobe', 24)
  16. console.log(obj1) // { name: 'curry', age: 30, sayHello: [Function (anonymous)] }
  17. console.log(obj2) // { name: 'kobe', age: 24, sayHello: [Function (anonymous)] }
  18. obj1.sayHello() // My name is undefined, I'm 30 years old.
  19. obj2.sayHello() // My name is undefined, I'm 24 years old.

缺点:创建出来的对象全是通过字面量创建的,获取不到对象真实的类型。

4.2.方案二:构造函数

(1)什么是构造函数?

  • 构造函数也称之为构造器(constructor),通常是我们在创建对象时会调用的函数;
  • 在其他面向对象的编程语言里面,构造函数是存在于类中的一个方法,称之为构造方法;
  • 如果一个普通的函数被使用new操作符来调用了,那么这个函数就称之为是一个构造函数;
  • 一般规定构造函数的函数名首字母大写;

(2)new操作符调用函数的作用

当一个函数被new操作符调用了,默认会进行如下几部操作:

  • 在内存中创建一个新的对象(空对象);
  • 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性
  • 构造函数内部的this,会指向创建出来的新对象
  • 执行函数的内部代码(函数体代码);
  • 如果构造函数没有返回对象,则默认返回创建出来的新对象。

(3)构造函数创建对象的过程

  • 通过构造函数创建的对象就真实的类型了,如下所示的Person类型;
  1. function Person(name, age) {
  2. this.name = name
  3. this.age = age
  4. this.sayHello = function() {
  5. console.log(`My name is ${this.name}, I'm ${this.age} years old.`)
  6. }
  7. }
  8. const p1 = new Person('curry', 30)
  9. const p2 = new Person('kobe', 24)
  10. console.log(p1) // Person { name: 'curry', age: 30, sayHello: [Function (anonymous)] }
  11. console.log(p2) // Person { name: 'kobe', age: 24, sayHello: [Function (anonymous)] }

缺点:在每次使用new创建新对象时,会重新给每个对象创建新的属性,包括对象中方法,实际上,对象中的方法是可以共用的,消耗了不必要的内存。

  1. console.log(p1.sayHello === p2.sayHello) // false

4.3.方案三:原型+构造函数

在了解该方案之前,需要先简单的认识一下何为原型。

(1)对象的原型

JavaScript中每个对象都有一个特殊的内置属性[[prototype]](我们称之为隐式原型),这个特殊的属性指向另外一个对象。那么这个属性有什么用呢?

  • 前面介绍了,当我们通过对象的key来获取对应的value时,会触发对象的get操作;
  • 首先,get操作会先查看该对象自身是否有对应的属性,如果有就找到并返回其值;
  • 如果在对象自身没有找到该属性就会去对象的[[prototype]]这个内置属性中查找;

那么对象的[[prototype]]属性怎么获取呢?主要有两种方法:

  • 通过对象的__proto__属性访问;
  • 通过Object.getPrototypeOf()方法获取;
  1. const obj = {
  2. name: 'curry',
  3. age: 30
  4. }
  5. console.log(obj.__proto__)
  6. console.log(Object.getPrototypeOf(obj))

(2)函数的原型

所有的函数都有一个prototype属性,并且只有函数才有这个属性。前面提到了new操作符是如何在内存中创建一个对象,并给我们返回创建出来的对象,其中第二步这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性。将代码与图结合,来看一下具体的过程。

示例代码:

  1. function Person(name, age) {
  2. this.name = name
  3. this.age = age
  4. }
  5. const p1 = new Person('curry', 30)
  6. const p2 = new Person('kobe', 24)
  7. // 验证:对象(p1\p2)内部的[[prototype]]属性(__proto__)会被赋值为该构造函数(Person)的prototype属性;
  8. console.log(p1.__proto__ === Person.prototype) // true
  9. console.log(p2.__proto__ === Person.prototype) // true

内存表现:

  • p1和p2的原型都指向Person函数的prototype原型;
  • 其中还有一个constructor属性,默认原型上都会有这个属性,并且指向当前的函数对象;

(3)结合对象和函数的原型,创建对象

先简单的总结一下:

  • 前面使用构造函数创建对象的缺点是对象中的方法不能共用;
  • 对象的属性可以通过[[prototype]]隐式原型进行查找;
  • 构造函数创建出来的对象[[prototype]]与构造函数prototype指向同一个对象(同一个地址空间);
  • 那么我们可以将普通的属性放在构造函数的内部,将方法放在构造函数的原型上,当查找方法时,就都会去到构造函数的原型上,从而实现方法共用;
  1. function Person(name, age) {
  2. this.name = name
  3. this.age = age
  4. }
  5. Person.prototype.sayHello = function() {
  6. console.log(`My name is ${this.name}, I'm ${this.age} years old.`)
  7. }
  8. const p1 = new Person('curry', 30)
  9. const p2 = new Person('kobe', 24)
  10. console.log(p1.sayHello === p2.sayHello) // true

JavaScript面向对象—对象的创建和操作的更多相关文章

  1. javascript面向对象 用new创建一个基于原型的javascript对象

    //创建一个类 其实就是个对象 var Student={ name:"robot", height:1.6, run:function(){ console.log(this.n ...

  2. JavaScript 对象的创建和操作

    <script>         // 对象是属性的无序集合,每个属性都是一个名/值对. 属性名称是一个字符串.         // 对象种类         // 内置对象(nativ ...

  3. JavaScript(对象的创建模式)

    JavaScript和其他语言略有不同,在JavaScript中,引用数据类型都是对象(包括函数).不过,在JavaScript中并没有“类”的概念,这决定了在JavaScript中不能直接来定义“类 ...

  4. JavaScript面向对象之类的创建

    JavaScript对象的定义: 在js中函数极为对象,对象分为二种:对象字变量产生的对象连接到Object.prototype:函数对象连接到Function.prototype 方法:当一个函数被 ...

  5. Javascript实现对象的创建

    能使用{}创建对象就不要使用new Object,能使用[]创建数组就不要使用new Array,JS中字面量的访问速度要高于对象. 1.通过object构造函数创建单个对象 var o = new ...

  6. Javascript之对象的创建

    面向对象语言有一个非常显著的标志,那就是它们都有类的概念,通过类之间的继承就可以达到任意创建具有相同属性方法的对象.而在ECMAScript中并没有类的概念,它把对象定义为:无序属性的集合,其属性包含 ...

  7. JavaScript面向对象—继承的实现

    JavaScript面向对象-继承的实现 前言 面向对象的三大特性:封装.继承和多态.上一篇我们简单的了解了封装的过程,也就是把对象的属性和方法封装到一个函数中,这一篇讲一下JavaScript中继承 ...

  8. JavaScript原生对象及扩展

    来源于 https://segmentfault.com/a/1190000002634958 内置对象与原生对象 内置(Build-in)对象与原生(Naitve)对象的区别在于:前者总是在引擎初始 ...

  9. javascript面向对象规则汇总以及json

    javascript中一切皆对象,而且定义非常灵活, 于是出现了一些相对其他编程语言环境下匪夷所思的代码: ---------------------------------------------- ...

随机推荐

  1. 微服务架构 | 11.1 整合 Seata AT 模式实现分布式事务

    目录 前言 1. Seata 基础知识 1.1 Seata 的 AT 模式 1.2 Seata AT 模式的工作流程 1.3 Seata 服务端的存储模式 1.4 Seata 与 Spring Clo ...

  2. 「JOI 2015 Final」城墙

    「JOI 2015 Final」城墙 复杂度默认\(m=n\) 暴力 对于点\((i,j)\),记录\(ld[i][j]=min(向下延伸的长度,向右延伸的长度)\),\(rd[i][j]=min(向 ...

  3. bom案例5-简单动画

      <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&q ...

  4. vi/vim 设置.vimrc(/etc/vim | $HOME)

    转载请注明来源:https://www.cnblogs.com/hookjc/ "====================================================== ...

  5. java_JDBC,连接数据库方式,RestSet结果集,Statement,PreparedStatement,事务,批处理,数据库连接池(c3p0和Druid)、Apache-DBUtils、

    一.JDBC的概述 1.JDBC为访问不同的数据薛是供了统一的接口,为使用者屏蔽了细节问题.2. Java程序员使用JDBC,可以连接任何提供了JDBC驱动程序的数据库系统,从而完成对数据库的各种操作 ...

  6. 基于PXIe接口的CoaXpress高速相机图像采集、回放

    PXIe简介 PCI eXtensions for Instrumentation or PXI is a computer-based hardware and software platform ...

  7. VNCTF RE复现 (BabyMaze 时空飞行)

    babymaze pyc混淆! 还没反编译出来 只能找个脚本偷字节码 import marshal, dis f = open('babymaze.pyc', 'rb') f.read(4) f.re ...

  8. 03 前端基础之JavaScript

    目录 前端基础之JavaScript JavaScript JavaScript注释 变量与常量 基本数据类型 number类型 string类型 boolean类型 null与undefined类型 ...

  9. opencv笔记-SimpleBlobDetector

    通用的 Blob 检测方法包括:Laplacian of Gaussian(LoG), Difference of Gaussian(DoG), Derterminant of Hessian(DoH ...

  10. Solution -「POI 2014」「洛谷 P5904」HOT-Hotels 加强版

    \(\mathcal{Description}\)   Link.   给定一棵 \(n\) 个点的树,求无序三元组 \((u,v,w)\) 的个数,满足其中任意两点树上距离相等.   \(n\le1 ...