面向对象-对象和属性描述符详解(一)
面向对象编程
面向对象编程是一种使用包含数据和行为的对象来模拟现实世界实体的编程范式。这种方法便于封装、继承和多态性,代码更易管理、复用和扩展
-
在JS当中所体现的主要来源于封装和继承,封装在日常处处都会用到,我们使用函数方法就是一种封装
-
继承则是我们的重点,原型链的概念就来自于继
-
创建对象的方式
-
new Object()
new Object() 是 JavaScript 的原生构造函数,调用时会创建一个空对象并返回该对象:
let obj = new Object() // 创建一个对象
obj.name = 'zhangsan' // 给对象添加属性和方法
obj.sayName = function(){ // 给对象添加方法console.log(this.name) // this指向当前对象
}
obj.sayName() // 调用方法
- 字面量方式创建对象
最简洁直观的方式,适合定义单例对象或简单数据结构
let obj = { // 创建一个对象name: 'zhangsan', // 添加属性和方法sayName: function(){ // 添加方法console.log(this.name) // this指向当前对象}
}
obj.sayName() // 调用方法
- 工厂函数
工厂函数是一种创建对象的函数,返回一个新对象而不是使用 this,无法使用 instanceof 检查类型,无法使用原型链
function createPerson(name, age) {return {name,age,greet() {console.log(`Hello, ${this.name}!`);}};
}
const bob = createPerson('Bob', 25);
bob.greet(); // 输出: Hello, Bob!
- Object.create()
Object.create() 方法用于创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
let obj = Object.create(null) // 创建一个对象,原型为null
obj.name = 'zhangsan' // 添加属性和方法
obj.sayName = function(){ // 添加方法console.log(this.name) // this指向当前对象
}
obj.sayName() // 调用方法const proto = {
greet() {console.log(`Hello, ${this.name}!`);
}
};
const john = Object.create(proto);
john.name = 'John'; // 添加自身属性
john.greet(); // 输出: Hello, John!
- 构造函数
构造函数是一种特殊的函数,用于创建和初始化对象。在 JavaScript 中,构造函数通常以大写字母开头,用于区分普通函数。
通过 new 调用函数创建对象实例,适合批量生成结构化对象
function Person(name, age) {this.name = name;this.age = age;
}
Person.prototype.greet = function() {console.log(`Hello, ${this.name}!`);
};
const alice = new Person('Alice', 30);
alice.greet(); // 输出: Hello, Alice!
- 类语法
语法糖形式,更贴近传统面向对象语言,内部仍基于原型链 - 支持继承(extends)、静态方法(static)、访问器(get/set)
- 方法自动绑定到原型,不可枚举
class Person {constructor(name, age) {this.name = name;this.age = age;}greet() {console.log(`Hello, ${this.name}!`);}
}
const charlie = new Person('Charlie', 28);
- 原型继承优化(寄生组合继承)
解决组合继承中父类构造函数重复调用的问题,是继承的最佳实践。 - 方法通过原型链共享,属性独立初始化。
- 避免父类构造函数重复执行(组合继承的缺陷)
function Parent(name) {this.name = name;
}
Parent.prototype.say = function() {console.log(this.name);
};
function Child(name, age) {Parent.call(this, name); // 继承属性this.age = age;
}
Child.prototype = Object.create(Parent.prototype); // 继承方法
Child.prototype.constructor = Child; // 修复构造函数指向
const child = new Child('Tom', 10);
属性描述符
数据属性描述符是一种用于控制对象属性行为的工具。它提供了对属性的更细致管理,允许开发者定义属性的各种特性。数据属性描述符包含了几个特殊的属性(或称为“特性”),这些特性决定了对象属性的行为方式
- 通过属性描述符可以精准的添加或修改对象的属性,也就是一种"特性"
- 属性描述符需要使用 Object.defineProperty 来对属性进行添加或者修改
Object.defineProperty
- 定义
Object.defineProperty() 方法用于直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
Object.defineProperty(obj, prop, descriptor)
// obj:要定义属性的对象
// prop:要定义或修改的属性的名称或者Symbol(后面会讲解这个类型)
// descriptor:要定义或修改的属性描述符
const obj = {}; // 创建一个空对象
Object.defineProperty(obj, 'name', { // 定义属性value: 'zhangsan', // 属性值writable: true, // 是否可写enumerable: true, // 是否可枚举configurable: true // 是否可配置
});
console.log(obj.name); // 输出: zhangsan
- 属性描述符分类
- 属性描述符分为数据描述符和存取描述符,二者不可混用
- 数据描述符(直接控制值)
- value:属性值(默认 undefined)
- writable:是否允许赋值修改(默认 false)
- enumerable:是否可枚举(如 for…in 能否遍历,默认 false)
- configurable:是否可删除或重新配置特性(默认 false)
const obj = {}; // 创建一个空对象
Object.defineProperty(obj, 'id', {value: 1001,writable: false, // 不可修改configurable: false // 不可删除或重定义
});
console.log(obj.id); // 输出: 1001
obj.id = 1002; // 尝试修改,无效
delete obj.id; // 尝试删除,无效
consloe.log(obj);
- 存取描述符(通过函数拦截操作)
- get:获取属性值的函数(默认 undefined)
- set:设置属性值的函数(默认 undefined)
- enumerable:是否可枚举(如 for…in 能否遍历,默认 false)
- configurable:是否可删除或重新配置特性(默认 false)
const obj = {}; // 创建一个空对象
Object.defineProperty(obj, 'age', {get() { // 获取属性值的函数return this._age; // 返回私有属性}, set(newValue) { // 设置属性值的函数this._age = newValue; // 设置私有属性},
})
console.log(obj.age); // 输出: undefined
obj.age = 20; // 设置属性值
console.log(obj.age); // 输出: 20
数据属性描述符有如下四个特征
- Configurable:表示属性是否可以通过delete删除属性,是否可以修改它的特性,或者是否可以将它修改为存取属性描述符
- 对象上定义某个属性时,这个属性的Configurable为true
- 通过属性描述符定义一个属性时,这个属性的Configurable默认为false
- Enumerable:表示属性是否可以通过for-in或者Object.key()返回该属性;
- 对象上定义某个属性时,这个属性的Enumerable为true
- 通过属性描述符定义一个属性时,这个属性的Enumerable默认为false
- Writable:表示是否可以修改属性的值;
- 对象上定义某个属性时,这个属性的Writable为true
- 通过属性描述符定义一个属性的时候,这个属性的Writable为false
- value:属性的value值,读取属性时会返回该值,修改属性时,会对其进行修改
- 默认情况下这个值是undefined
var obj = {name:"sumu",age:18
}//name和age虽然没有使用属性描述符来定义,但是它们也是具备对应的特性的,以下是对应的默认值
//value:赋值的value
//configurable:true
//enumerable:true
//writable:true
// 属性描述符添加另外一个属性address
//数据属性描述符
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
存取属性描述符有如下四个特征
- Configurable:表示属性是否可以通过delete删除属性,是否可以修改它的特性,或者是否可以将它修改为存取属性描述符
- 对象上定义某个属性时,这个属性的Configurable为true
- 通过属性描述符定义一个属性时,这个属性的Configurable默认为false
- Enumerable:表示属性是否可以通过for-in或者Object.key()返回该属性;
- get:获取属性时会执行的函数,默认为undefined
- set:设置属性时会执行的函数,默认为undefined
var obj = {name:"sumu",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(){console.log("获取了一次address的值")return this._address//将_address这个属性隐藏起来},set:function(value){bar()//这样就截获了它获取值的过程,这是Vue2响应式的原理//当我们如下对obj.address进行赋值的时候,值就通过形参传递了进来,我们在这里进行赋值的操作this._address = value}
})console.log(obj)//{ name: 'sumu', age: 18, address: [Getter/Setter] }
console.log(obj.address);//上海市 获取了一次address的值
obj.address = "北京市"//我们使用的是address,而不是_address了哦,注意这里的变化,_address的值并没有改变,是私有属性
console.log(obj.address);//北京市function bar(){console.log("设置了一次address的值")
}
对象上同时定义多个属性 - Object.defineProperties
- 定义
Object.defineProperties() 方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象。
Object.defineProperties(obj, props)
// obj:要定义属性的对象
// props:要定义或修改的属性描述符的集合
const obj = {//私有属性(js中没有严格意义上的私有属性,依旧可以通过obj._age访问到,但是外人是不知道这个隐藏起来的属性,只知道他的替代obj.age)_age:20
};
Object.defineProperties(obj, { // 定义多个属性name: { // 定义属性value: 'sumu', // 属性值writable: true, // 是否可写enumerable: true, // 是否可枚举configurable: true // 是否可配置},address: { // 定义属性value: '上海市', // 属性值writable: true, // 是否可写enumerable: true, // 是否可枚举configurable: true // 是否可配置},age:{configurable:false,enumerable:false,get:function(){return this._age},set:function(value){this._age = value}}
});
console.log(obj.name); // 输出: sumu
console.log(obj.address); // 输出: 上海市console.log(obj.age)//20
console.log(obj,"为了看age的不可枚举是否生效");//{ _age: 20, name: 'sumu' } 这是为了看age的不可枚举是否生效
obj.age = 18
console.log(obj.age);//18
对象方法
- getOwnPropertyDescriptor - 获取对象上指定属性的描述符
- getOwnPropertyDescriptors- 获取对象上所有属性的描述符
- defineProperty - 定义或修改对象上的一个属性
- defineProperties - 定义或修改对象上的多个属性
- preventExtensions - 防止对象扩展
//返回指定对象上一个自有属性对应的属性描述符
Object.getOwnPropertyDescriptor(obj, prop)
//返回一个对象,该对象包含了目标对象所有自有属性的属性描述符
Object.getOwnPropertyDescriptors(obj)var obj = {names: "苏木",age: 18
}
console.log(Object.getOwnPropertyDescriptor(obj, 'names'));
console.log(Object.getOwnPropertyDescriptors(obj));
Object的方法对 对象的限制
- 禁止对象扩展新属性:preventExtensions
- 给一个对象添加新的属性会失败(在严格模式下会报错)
- 密封对象,不允许配置和删除属性:seal
- 实际是调用preventExtensions
- 并且将现有属性的configurable:false
- 冻结对象,不允许修改现有属性:freeze
- 实际上是调用seal
- 并且将现有属性的writable:false
var obj = {names: "sumu",age: 18
};// 禁止对象扩展新属性
Object.preventExtensions(obj);
obj.newProperty = 'new'; // 尝试添加新属性,这将失败
console.log(obj.newProperty); // 输出:undefined,在严格模式下这会抛出错误
console.log(obj); // 输出:{ names: 'sumu', age: 18 },新属性未添加
var obj = {names: "sumu",age: 18
};// 密封对象,不允许配置和删除属性
Object.seal(obj);
obj.names = 'new'; // 尝试修改属性值,这将成功
delete obj.age; // 尝试删除属性,这将失败
console.log(obj.names); // 输出:new,属性值已修改
console.log(obj.age); // 输出:18,属性未删除
console.log(obj); // 输出:{ names: 'new', age: 18 },属性未删除// 冻结对象,不允许修改现有属性
Object.freeze(obj);
obj.names = "MOS"; // 尝试修改属性值,这将失败
console.log(obj.names); // 输出:new,因为freeze后属性值不可更改