• 作者:老汪软件技巧
  • 发表时间:2024-09-17 10:03
  • 浏览量:

该系列文章连载于公众号coderwhy和掘金XiaoYu2002中

脉络探索一、面向对象是现实的抽象方式

现实世界的东西大多数都是可以在编程中抽象出来的

编程是对现实世界的抽象,而面向对象是对现实世界抽象的一种方式

1.1. 什么是面向对象对象是JavaScript中一个非常重要的概念,这是因为对象可以将多个相关联的数据封装到一起,更好的描述一个事物:用对象来描述事物,更有利于我们将现实的事物,抽离成代码中某个数据结构:1.2. JS中的面向对象

{
  key:value
}

那我们要如何创建一个对象?

早期使用创建对象的方式最多的是使用Object类,并且使用new关键字来创建一个对象:

后来很多开发者为了方便起见,都是直接通过字面量的形式来创建对象:

1.2.1. 创建对象的方式使用对象字面量的优势:

// 示例 1: 使用 new Object() 创建对象
var person1 = new Object();
person1.name = "小余";
person1.age = 18;
person1.greet = function() {
    console.log("Hello, my name is " + this.name + " and I am " + this.age + " years old.");
};
//上面的方式是对person1当作一个构造函数,然后通过new关键字来执行函数,这个时候也会创建出来对象
// 调用 person1 的方法
person1.greet();  // 输出: Hello, my name is 小余 and I am 18 years old.
// 示例 2: 使用对象字面量来创建对象
var person2 = {
    name: "小余",
    age: 18,
    greet: function() {
        console.log("Hello, my name is " + this.name + " and I am " + this.age + " years old.");
    }
};
// 调用 person2 的方法
person2.greet();  // 输出: Hello, my name is 小余 and I am 18 years old.

1.3. 对象的数据属性描述符

在JavaScript中,数据属性描述符是一种用于控制对象属性行为的工具。它提供了对属性的更细致管理,允许开发者定义属性的各种特性。数据属性描述符包含了几个特殊的属性(或称为“特性”),这些特性决定了对象属性的行为方式

1.3.1. 对属性操作的控制

在前面,我们的属性都是直接定义在对象内部,或者直接添加到对象内部的:

var obj = {
    name:"小余",
    age:20,
    sex:"男"
}
//对属性的控制
//获取属性
console.log(obj.name)//小余
//给属性赋值
obj.name = "xiaoyu"
console.log(obj.name)//xiaoyu
//删除属性
delete obj.name
console.log(obj)//{ age: 20, sex: '男' }

如果我们想要对一个属性进行比较精准的操作控制,那么我们就可以使用属性描述符

二、Object.defineProperty

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

Object.defineProperty(obj, prop, descriptor)

而我们本章节的标题其实就是属性描述符详解,最重要的内容隐藏在第三参数之中

返回值:被传递给函数的对象

var obj = {
    name:"xiaoyu",
    age:20
}
Object.defineProperty(obj,"height",{
    //很多的配置
    value:1.75
})
console.log(obj)
//node环境下打印
//{ name: 'xiaoyu', age: 20 }

图13-1 浏览器下的height属性

var obj = {
    name:"xiaoyu",
    age:20
}
Object.defineProperty(obj,"height",{
    //很多的配置,我们在这里写入的就是属性描述符
    value:1.75
})
console.log(obj.height);//Node环境下打印 1.75

var obj = {
    name: "xiaoyu",
    age: 20
};
Object.defineProperty(obj, "height", {
    value: 1.75,
    enumerable: true  // 设置为true使其可枚举
});
console.log(obj);  // 将在所有环境中显示 { name: 'xiaoyu', age: 20, height: 1.75 }

2.1. 属性描述符分类并且数据描述符和存取描述符是有一点冲突的:configurable(可配置的)enumerable(可枚举的)value(值)writable(可写的)get(获取)set(设置)

_描述符是什么_描述符类

数据描述符

可以

可以

可以

可以

不可以

不可以

存取描述符

可以

可以

不可以

不可以

可以

可以

2.1.1. 不兼容原因

如果同时定义了这两组特性,就会引起混淆,因为JavaScript引擎不会清楚在访问或修改属性时应该直接操作一个静态值,还是应该调用一个函数来决定这些操作。因此,JavaScript规范不允许我们这样做,以确保属性的行为是明确和一致的

两种不同的情况导致属性描述符分裂出两种模式:

数据属性描述符存取属性描述符2.1.2. 数据属性描述符

在接下来我们描述属性描述符的时候,会用[[]]来括起来属性

[[Enumerable]]:表示属性是否可以通过for-in或者Object.key()返回该属性;[[Writable]]:表示是否可以修改属性的值;[[value]]:属性的value值,读取属性时会返回该值,修改属性时,会对其进行修改

//name和age虽然没有使用属性描述符来定义,但是它们也是具备对应的特性的,以下是对应的默认值
//value:赋值的value
//configurable:true
//enumerable:true
//writable:true
var obj = {
    name:"xiaoyu",
    age:18
}
//数据属性描述符
Object.defineProperty(obj,"address",{
    //很多配置
    value:"福建省",//默认值undefined
    //该属性不可删除,不可修改。不可以重新定义属性描述符
    configurable:false//默认值false
    //该特性是配置对应的属性(address)是否是可以枚举的
    enumerable:true,//默认值false
    //该特性是否可以赋值
    writable:false//默认值false
})
delete obj.name
console.log(obj)//{ age: 18 },name被成功删除
delete obj.address
console.log(obj.address);//福建省	没删除掉,因为我们设置了不可配置configurable:false
//测试enumerable的作用
console.log(obj)
for(var key in obj){
    console.log(key,'for遍历');//如果enumerable为false,则只会出来name和age,address只有设置为true的时候才会出来
}
console.log(Object.keys(obj),'keys的作用');
//enumerable前后对比
//[ 'name', 'age' ] keys的作用(enumerable:false)
//[ 'name', 'age', 'address' ] keys的作用(enumerable:true)
//测试writable的作用
obj.address = "上海市"
console.log(obj.address);//福建省,新的内容不可写入。如果我们不设置value为福建省,则在不可写入的情况下显示undefined

2.1.3. 存取属性描述符[[Enumerable]]:表示属性是否可以通过for-in或者Object.keys()返回该属性;[[get]]:获取属性时会执行的函数,默认为undefined[[set]]:设置属性时会执行的函数,默认为undefined

var obj = {
    name:"xiaoyu",
    age:18,
    _address:"泉州市"//_开头表示私有的,不希望被人看到。我们就通过get来使用address代替掉_address。别人就通过address调用我们,而不是使用_address调用
}
//当我们使用get、set,不使用value和writable的时候,叫做存取属性描述符
//使用场景:1.隐藏某一个私有属性,不希望直接被外界使用和赋值
//2.如果我们希望截获某一个属性,它访问和设置值的过程时,我们也会使用存储属性描述符
Object.defineProperty(obj,"address",{
    //很多配置
    enumerable:true,
    configurable:true,
    //value跟writable与get、set不能共存
    // value:"福建省",
    // writable:true,
    get:function(){
        foo()//我们希望获取值的时候提醒我们一下的时候就会这么干
		return this._address//将_address这个属性隐藏起来,给他套上了address这个马甲
    },
    set:function(value){
        bar()//这样就截获了它获取值的过程,这是Vue2响应式的原理
		//当我们如下对obj.address进行赋值的时候,值就通过形参传递了进来,我们在这里进行赋值的操作
        this._address = value
    }
})
console.log(obj)//{ name: 'xiaoyu', age: 18, address: [Getter/Setter] }
console.log(obj.address);//泉州市,get拿到了值
obj.address = "coderwhy"//我们使用的是address,而不是_address了哦,注意这里的变化
console.log(obj.address);//coderwhy
function foo(){
    console.log("获取了一次address的值")
}
function bar(){
    console.log("设置了一次address的值")
}

静与动本就是相反的一对,在JS中反应了两种不同的处理方式,为了防止引起混淆,所以是不能够同时进行的,这就是他们的不兼容原因,也可以通过这种方式来进行记忆它们的特性2.2. 学习属性描述符的意义我们提到,属性描述符是有能够说明一个API的能力边界,那我们是不是可以联想一下,技术文档会这样进行讲解吗?

图12-2 MDN文档对API能力边界的描述

图12-3 React文档词汇

后续预告让我们在下一篇章中,去分清什么是构造函数,构造函数的缺点吧!