面向对象(较深入)

本文详细介绍了JavaScript中的面向对象编程,包括对象的创建、属性操作以及使用Object.defineProperty()进行细粒度控制。讲解了数据描述符和存取描述符的属性,如configurable、enumerable、writable、value、get和set。还探讨了对象的原型、构造函数以及创建多个相似对象的不同方案,如工厂模式和构造函数。
摘要由CSDN通过智能技术生成

面向对象

面向对象是现实的抽象方法

  • 对象是JavaScript中一个非常重要的概念,这是因为对象可以将多个相关联的数据封装到一起,更好的描述一个事物:

    • Car: { 颜色:color, 价格:price, 品牌:brand }
  • 用对象来描述事物,更有利于我们将现实的事物,抽离成代码中某个数据结构

  • JavaScript支持多种编程范式,包括函数式编程和面向对象编程:

    • JavaScript中的对象被设计成一组属性的无序集合,像是一个哈希表,有key和value组成;
    • key是一个标识符名称,value可以是任意类型,也可以是其他对象或者函数类型;
    • 如果值value是一个函数,那么我们可以称之为是对象的方法;
  • 创建对象的方式

    // JavaScript
    // 1.
    var obj1 = new Object()
    obj1.name = "zzwi"
    obj1.age = 18
    obj1.gender = "man"
    obj1.going = function() {
        console.log(this.name + "want to be a great person")
    }
    // 2.
    var obj2 = {
        name: "zzwi",
        age: 18,
        gender: "man",
        going: function() {
            console.log(this.name + "want to be a great person")
        }
    }
    

对属性操作的控制

delete

代码如下:

// JavaScript
// 承接上文代码
delete obj1.age // 删除对象obj1的age属性
console.log(obj1) // {name: 'zzwi', gender: 'man', going: ƒ}
// 可以看到,age属性确实被移除了

Object.defineProperty()

Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

Object.defineProperty(obj,prop,descriptor)

  • 可接收的三个参数:

    • obj:要定义属性的对象
    • prop:要定义或修改的属性的名称或Symbol
    • descriptor:要定义或修改的属性描述符
  • 返回值:

    • 被传递给函数的对象
  • 属性描述符的类型有两种:

    • 数据属性描述符
    • 存取属性描述符
    configurableenumerablevaluewritablegetset
    数据描述符××
    存取描述符××
configurable:

表示是否允许delete删除属性,是否可以修改属性值,是否可以将其(数据属性)修改为存取属性描述符

  • 当我们直接在一个对象上定义某个属性时,这个属性的configurable为true
  • 当我们通过属性描述符定义一个属性时,这个属性的configurable默认为false

承接上文代码

// JavaScript
Object.defineProperty(obj2,"age",{
      configurable: false // 设置属性age不可更改
})
delete obj2.age
console.log(obj2) // {name: 'zzwi', age: 18, gender: 'man', going: ƒ}
// 属性age没有被删除
// JavaScript
// 如果这样敲代码则会报错
Object.defineProperty(obj2,"age",{
      configurable: false 
})
Object.defineProperty(obj2,"age",{
      configurable: true 
})
// 控制台报错
enumerable:

表示属性是否可以通过for-in或者Object.keys()返回该属性;

  • 当我们直接在一个对象上定义某个属性时,这个属性的enumerable为true
  • 当我们通过属性描述符定义一个属性时,这个属性的enumerable默认为false

代码承接上文

// JavaScript
Object.defineProperty(obj2,"age",{
    configurable: false,
    enumerable: false
})
console.log(Object.keys(obj2)) // ['name', 'gender', 'going']
writable:

表示是否可以修改属性的值;

  • 当我们直接在一个对象上定义某个属性时,这个属性的writable为true
  • 当我们通过属性描述符定义一个属性时,这个属性的writable默认为false

承接上文代码

// JavaScript
Object.defineProperty(obj2,"age",{
    // configurable: false,
    enumerable: false,
    writable: false // 不允许修改该属性的值
})
obj2.age = 20
// delete obj2.age
console.log(obj2) // {name: 'zzwi', gender: 'man', age: 18, going: ƒ}
// 属性age的值无法修改
value:

属性的value值,读取属性时会返回该值,修改属性时,会对其进行修改;

  • 默认情况下这个值是undefined

承接上文代码

// JavaScript
Object.defineProperty(obj2,"age",{
    configurable: false,
    enumerable: false,
    writable: true, // 允许修改该属性的值
    value: 20 // 将属性age的值修改为20
})
// delete obj2.age
console.log(obj2) // {name: 'zzwi', gender: 'man', age: 20, going: ƒ}
get:

获取属性时会执行的函数。默认为undefined

承接上文代码

// JavaScript
var address = "普宁市"
Object.defineProperty(obj2,"address",{
    configurable: true,
    enumerable: true,
    get: function() {
        return address
    }
})
console.log(obj2.address) // 普宁市
set:

设置属性时会执行的函数。默认为undefined

承接上文代码

// JavaScript
var address = "普宁市"
Object.defineProperty(obj2,"address",{
    configurable: true,
    enumerable: true,
    get: function() {
        return address
    },
    set: function(value) {
        address = value
    }
})
console.log(obj2.address) // 普宁市
obj2.address = "广州市"
console.log(obj2.address) // 广州市

另外的写法:

// JavaScript
var obj = {
    _age: 18, // 社区规范:_开头代表不希望被修改(私有属性)
    running: function() {console.log("Running now")},
    set age(value) {
        this._age = value
    },
    get age() {
        return this._age
    }
}
console.log(obj.age) // 18
obj.age = 19
console.log(obj.age) // 19

这种写法默认属性操作符configurable / enumerable 的值都为true.

同时定义多个属性:

承接上文代码

// JavaScript
Object.defineProperties(obj1,{
    name: {
        writable: true,
        value: "coder"
    },
    going: {
        configurable: true,
        value: "excellent"
    }
})
console.log(obj1) // {name: 'coder', gender: 'man', going: 'excellent', age: 18}

注意:

value/writable 和 get/set 不共存

Object.defineProperty()的 get/set 便是vue2的响应式原理

Object.getOwnPropertyDescriptor

承接上文代码

// JavaScript
// 获取某一个特性属性的属性描述符
console.log(Object.getOwnPropertyDescriptor(obj1,"name"))
// 获取对象的所有属性描述符
console.log(Object.getOwnPropertyDescriptors(obj1))

对象内属性限制方法的补充:

// JavaScript
// 禁止对象继续添加新的属性
Object.preventExtensions(obj1)
obj1.address = "广州市"
console.log(obj1.address) // undefined
// JavaScript
// 禁止对象内的属性变化
Object.seal(obj1)
delete obj1.age
obj1.address = "广州市"
console.log(obj1.address) // undefined
console.log(obj1.age) // 18
// JavaScript
// 不允许对象内的属性值发生改变
Object.freeze(obj1)
obj1.age = 19
console.log(obj1.age) // 18

创建多个相似对象的方案

方案一:逐个创建

如下代码所示

// JavaScript
var p1 = {
    name: '张三',
    age: 18,
    height: 1.60
};
var p2 = {
    name: '李四',
    age: 19,
    height: 1.70
};
var p3 = {
    name: '王五',
    age: 20,
    height: 1.80
};
...

缺点:创建同样的对象时,需要编写重复的代码

方案二:工厂模式

如下代码所示

// JavaScript
function createPerson(name,age,height,address) {
    var p = {
        name,
        age,
        height,
        address,
        eating: function() {
            console.log(this.name+"在吃饭");
        },
        running: function() {
            console.log(this.name+"在跑步");
        }
    }
    return p
}
var v1 = createPerson("张三",18,1.60,"北京市")
var v2 = createPerson("李四",19,1.70,"上海市")
var v3 = createPerson("王五",20,1.80,"广州市")
...

缺点:打印创建对象时,对象的类型都是Object类型

方案三:构造函数

约定俗成的规范(非强制):构造函数首字母大写

JavaScript的构造函数和其他编程语言内的有点不太一样

  • js的构造函数也是一个普通的函数,从表现形式来说,和千千万万个普通的函数没有任何区别;
  • 如果有这么一个普通的函数被使用new操作符来调用了,那么这个函数就称之为是一个构造函数,并且它会执行如下操作:
    1. 在内存中创建一个新的对象(空对象)
    2. 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性
    3. 构造函数内部的this,会指向创建出来的新对象
    4. 执行函数的内部代码(函数体代码)
    5. 如果构造函数没有返回非空对象,则返回创建出来的新对象

如下代码所示

// JavaScript
function Person(name,age,height,address) {
    this.name = name
    this.age = age
    this.height = height
    this.address = address
    this.eating = function() {
        console.log(this.name+"在吃饭");
    }
    this.running = function() {
        console.log(this.name+"在跑步");
    }
}
var q1 = new Person("张三",18,1.6,"北京市")
var q2 = new Person("李四",19,1.7,"上海市")
var q3 = new Person("王五",20,1.8,"广州市")

缺点:需要为每个对象的函数去创建一个函数对象实例

方案三补充:原型prototype

JavaScript中每个对象都有一个 [[prototype]] ,这个属性可以称之为对象的原型(隐式原型)

函数作为对象来说, 它也是有[[prototype]] 隐式原型

又因为它是一个函数, 所以它还会多出来一个显示原型属性: prototype

承接上文代码

// JavaScript
// 打印对象原型的方法
// 浏览器提供
console.log(obj.__proto__) // { ... }
// ES5之后的规范
console.log(Object.getPrototypeOf(obj)) // { ... }

当我们从一个对象中获取某一个属性时,它会触发[[get]]操作

  1. 在当前对象中去查找对应的属性,如果找到就直接使用
  2. 如果没有找到,那么会沿着当前对象的原型(链)去查找
// JavaScript
console.log(obj.hhh); // undefined
obj.__proto__.hhh = "哈哈哈"
console.log(obj.hhh); // 哈哈哈

函数作为对象来说, 它也是有[[prototype]] 隐式原型

又因为它是一个函数, 所以它还会多出来一个显示原型属性: prototype

// JavaScript
function Person() {}
// 函数原型
console.log(Person.prototype) // { ... }
var f1 = new Person()
var f2 = new Person()
console.log(f1.__proto__ === f2.__proto__) // true
console.log(f1.__proto__ === Person.prototype); // true

这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性

上方代码中,f1和f2的原型完全相同的原因是它们都指向同一个原型对象,即new操作后的构造函数Person所指向的原型对象prototype

prototype原型

从上图中,我们还可以看到

prototype.constructor = 构造函数本身

// JavaScript
console.log(Person.prototype.constructor); // ƒ Person() {}
console.log(Person.prototype.constructor === Person); // true

修改原型:

方法一:逐个更改

// JavaScript
Person.prototype.name = "coder"
Person.prototype.age = 18
console.log(f1.name,f1.age) // coder 18

方法二:修改整个对象

// JavaScript
Person.prototype = {
    name: "zzwi",
    age: "21",
    gender: "man",
    eating: function() {
        console.log(this.name+"在吃饭");
    },
    running: function() {
        console.log(this.name+"在跑步");
    }
}
/* 因为属性constructor的属性描述符enumerable值为false,
       即不可枚举,所以采用Object.defineProperty()进行添加*/
Object.defineProperty(Person,"constructor",{
    configurable: true,
    enumerable: false,
    writable: true,
    value: Person
})

参考

深入JavaScript高级语法-coderwhy

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值