2.12.1.Object对象
2.12.1.1.Object.create()
Object.create()
是 JavaScript 中的一个内建函数,它提供了一种基于原型链的方式来创建新对象。该方法允许您指定一个现有对象作为新创建对象的原型(__proto__
),并可选地定义新对象自身的可配置属性。以下是 Object.create()
方法的详细用法:
语法
Object.create(proto[, propertiesObject])
参数
-
proto
(Object): 必需参数,表示新创建对象的原型对象。新对象的[[Prototype]]
(内部属性,可通过__proto__
或Object.getPrototypeOf()
访问)将被设置为这个对象。如果传入null
,新对象将没有原型,也就是说它的原型链到头,不会从任何对象继承属性和方法。 -
propertiesObject
(Object): 可选参数,一个描述新对象初始属性的可枚举属性名到属性描述符映射的对象。这些属性会直接添加到新创建的对象上,而非其原型。每个属性描述符是一个包含以下属性的对象:value
: 属性的初始值。writable
: 布尔值,表示属性是否可写。enumerable
: 布尔值,表示属性是否可枚举(出现在for...in
循环中)。configurable
: 布尔值,表示属性是否可配置(能否被删除或改变属性描述符)。get
: 一个函数,作为属性的 getter,在访问该属性时被调用。set
: 一个函数,作为属性的 setter,在设置该属性时被调用。
如果省略此参数或传入
null
或undefined
,新创建的对象将没有任何自定义属性。
基本用法:仅指定原型对象
// 定义一个原型对象
const prototype = {
name: 'John Doe',
sayHello: function() {
console.log(`Hello, my name is ${this.name}`);
}
};
// 使用 Object.create 创建新对象,继承原型对象
const person = Object.create(prototype);
// 新对象可以直接访问原型上的属性和方法
person.sayHello(); // 输出: Hello, my name is John Doe
添加初始属性
const prototype = {
greet: function() {
console.log('Greetings!');
}
};
const person = Object.create(prototype, {
name: {
value: 'Alice',
writable: true,
enumerable: true,
configurable: true
},
age: {
value: 30,
writable: true,
enumerable: true,
configurable: true
}
});
console.log(person.name); // 输出: Alice
console.log(person.age); // 输出: 30
person.greet(); // 输出: Greetings!
使用 null
创建无原型对象
const objectWithoutPrototype = Object.create(null);
console.log(Object.getPrototypeOf(objectWithoutPrototype) === null); // 输出: true
特性和用途
-
原型继承:
Object.create()
提供了一种更直接的方式来实现基于原型的继承,无需构造函数或new
操作符,减少了对全局作用域的污染。 -
隔离原型:通过指定不同的原型对象,可以轻松创建多个对象共享相同的接口(方法)但数据独立,避免了构造函数继承中子类原型修改会影响到所有子对象的问题。
-
属性控制:通过
propertiesObject
参数,可以在创建对象时精细控制属性的行为,如是否可写、可枚举、可配置,以及定义 getter 和 setter。 -
兼容性处理:在旧版本浏览器中,可以通过 polyfill 实现
Object.create()
的功能,确保代码的跨环境兼容性。
注意事项
-
使用
Object.create()
创建的对象,其原型链上的属性查找遵循原型链规则。这意味着,如果在新对象上调用一个方法或访问一个属性,且该属性在新对象自身上未定义,JavaScript 会沿着原型链向上查找。 -
若传给
Object.create()
的propertiesObject
参数中的某个属性名与原型对象上的同名属性冲突,新创建对象上的同名属性会遮蔽原型上的属性。
综上所述,Object.create()
是一种强大的对象创建工具,它不仅简化了基于原型的继承过程,还提供了对新对象属性行为的细粒度控制,是实现面向对象编程和设计模式的重要手段之一。
2.12.1.2.( ES 8 )属性的描述
Object.getOwnPropertyDescriptors()
该方法返回指定对象所有自身属性的描述对象
数据属性:value
(值)、writable
(是否可写)、enumerable
(是否可枚举)、configurable
(是否可配置)
//声明对象
const STU = {
name : "王小二",
study : ['java','html','js']
};
console.log(Object.getOwnPropertyDescriptors(STU));
// 输出 :
// {
// name: { value: '王小二', writable: true, enumerable: true, configurable: true },
// study: { value: [ 'java', 'html', 'js' ], writable: true, enumerable: true, configurable: true }
// }
2.12.1.3.Object.setPrototypeOf()
在 JavaScript 中,__proto__
和 Object.setPrototypeOf() 都是用来操作对象的原型链的。
__proto__
是对象的一个内部属性,用于获取或设置对象的原型。可以通过访问器属性的方式来获取或设置 __proto__
。
const obj = {};
const proto = { hello: "world" };
obj.__proto__ = proto; // 设置 obj 的原型为 proto
console.log(obj.hello); // 输出 "world"
需要注意的是,__proto__
属性在 ECMAScript 6 标准中被弃用,虽然现代浏览器仍然支持它,但不建议在生产环境中使用它。
因此,推荐使用 Object.setPrototypeOf() 方法来设置对象的原型。
Object.setPrototypeOf() 是一个静态方法,用于设置一个对象的原型。它接收两个参数:要设置原型的对象和要设置的原型对象。
Object.setPrototypeOf() 方法会将第一个参数对象的原型设置为第二个参数的对象。
这样,第一个参数对象就能够继承第二个参数对象的属性和方法。
需要注意的是,频繁地改变对象的原型链会对性能产生负面影响。因此,除非必要,一般不建议经常性地改变对象的原型。
const obj = {};
const proto = { hello: "world" };
Object.setPrototypeOf(obj, proto); // 设置 obj 的原型为 proto
console.log(obj.hello); // 输出 "world"
2.12.1.4.Object.getPrototypeOf()
在 JavaScript 中,Object.getPrototypeOf() 是一个静态方法,用于获取指定对象的原型。
Object.getPrototypeOf() 方法接收一个参数,即要获取原型的对象。它会返回指定对象的原型。
const obj = {};
const proto = { hello: "world" };
Object.setPrototypeOf(obj, proto); // 设置 obj 的原型为 proto
const objPrototype = Object.getPrototypeOf(obj);
console.log(objPrototype); // 输出 { hello: "world" }
在上面的示例中,我们使用 Object.setPrototypeOf() 方法将 obj 的原型设置为 proto。
然后,通过 Object.getPrototypeOf() 方法获取了 obj 的原型,并将其赋值给 objPrototype。
需要注意的是,Object.getPrototypeOf() 方法返回的是指定对象的原型,即它的 [[Prototype]] 属性的值。
如果指定对象没有明确的原型,即它是通过 Object.create(null) 或者 null 构造的对象,
那么 Object.getPrototypeOf() 方法会返回 null。
Object.getPrototypeOf() 方法的应用场景包括,
判断对象是否继承了指定的原型,检查对象的原型链关系,以及执行一些基于对象原型的操作。
2.12.1.5.原型链
原型链是JavaScript中实现继承的核心机制,它描述了对象间的继承关系是如何通过对象的内部属性[[Prototype]]
(在大多数环境中可通过__proto__
属性或Object.getPrototypeOf()
方法间接访问)连接起来的结构。具体特点如下:
- 对象关联:每个JavaScript对象(除了
null
)都有一个内部的[[Prototype]]
引用,指向其原型对象。原型对象也是一个对象,同样可以有自己的原型,从而形成一个链条。 - 属性查找:当试图访问一个对象的属性或方法时,如果该对象本身没有定义该属性,JavaScript引擎会沿着原型链向上查找,直到找到该属性或方法,或者到达原型链的顶端(通常是
null
)。 - 属性共享:原型链使得对象能够继承其原型对象的属性和方法,实现了属性和方法的复用,避免了在每个对象上重复定义相同的属性。
- 动态性:原型链是动态的,可以在运行时修改,如更改对象的原型、添加或删除原型上的属性,这些变化会立即反映到依赖该原型的对象上。
2.12.1.6.( (ES5) Object.defineProperty()
这是一个JavaScript原生方法,用于精确添加或修改对象的属性描述符(property descriptor)。
Object.defineProperty()
方法接受三个参数:
Javascript
1Object.defineProperty(object, property, descriptor)
-
object
: 要在其上定义或修改属性的对象。 -
property
: 要定义或修改的属性的名称(字符串)。 -
descriptor
: 描述符对象,定义了属性的行为。它是一个具有以下可选键的对象:
-
value
: 该属性的值。 -
writable
: 表示该属性是否可写,默认为false
。 -
enumerable
: 表示该属性是否可枚举(例如在for...in
循环或Object.keys()
中),默认为false
。 -
configurable
: 表示能否通过delete
删除属性,或者能否再次修改属性描述符, 默认为false
。 -
get
: 一个函数,作为属性的 getter 方法。 -
set
: 一个函数,作为属性的 setter 方法。
-
let obj = {};
Object.defineProperty(obj, 'property1', {
value: 42,
writable: true,
enumerable: true,
configurable: true
});
// 定义一个具有getter和setter的属性
Object.defineProperty(obj, 'property2', {
get: function () {
return this._internalValue;
},
set: function (newValue) {
this._internalValue = newValue * 2;
},
enumerable: true,
configurable: true
});
obj.property1 = 24; // 可以赋值,因为writable为true
console.log(obj.property1); // 输出: 24
obj.property2 = 10;
console.log(obj.property2); // 输出: 20,因为setter方法翻倍了输入值
2.12.2.class类
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过 class 关键字,可以定义类。
ES6 的 class 可以看作只是一个语法糖,它的绝大部分功能ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
// ES 5
// 人员 : 相当于 构造方法
function Person(name, sex){
this.name = name;
this.sex = sex;
}
//添加方法
Person.prototype.sayHi = function(){
console.log("大家好!!");
}
//实例化对象
let wang = new Person('王小二', '男');
wang.sayHi();
console.log(wang);
// ES 6
//class
class Emp{
//构造方法 名字不能修改
constructor(name, sex){
this.name = name;
this.sex = sex;
}
//方法必须使用该语法
sayHi(){
console.log("大家好!!");
}
}
let li = new Emp("李小三", '女');
li.sayHi()
console.log(li);
2.12.2.1.set/get 方法
在name属性之前添加了get和set关键字,这样就创建了一个名为name的访问器属性。get方法用于获取该属性的值,set方法用于设置新的值。
需要注意的是,使用属性访问器时,实际的属性名会在内部使用一个带下划线的变量名来存储。这是一种常见的命名约定,用于区分属性访问器和实际存储属性的变量。
class Stu {
constructor(name) {
this._name = name;
}
get name() {
console.log('读取 name 属性')
return this._name;
}
set name(newName) {
console.log('修改 name 属性');
this._name = newName;
}
}
const stu = new Stu('王小二');
console.log(stu.name); // 输出 "王小二"
stu.name = '李小三';
console.log(stu.name); // 输出 "李小三"
2.12.2.2.静态
static 属于 类不属于 对象
// ES 5
// function Stu(){
//
// }
// 下面定义的属性方法属性 Stu , 相当于 静态的
// name 属性 , 比较特殊 为 Stu
// Stu.age = 12;
// Stu.sayHello = function(){
// console.log("hello, 我是王小二");
// }
// console.log(Stu.age)
// Stu.sayHello();
// Stu.prototype.sex = '男';
//
// let wang = new Stu();
// // 这些属性是属于 Phone 的, 而不是属于 wang 的
// console.log(wang.name);
// wang.sayHello();
// console.log(wang.sex);
// ES 6
class Stu{
//静态属性
static name = '李小三';
static sayHello(){
console.log("hello, 我是李小三");
}
}
let li = new Stu();
console.log(li.name);
// li.sayHello();
console.log(Stu.name);
Stu.sayHello();
2.12.2.3.ES 5 构造继承
// ES 5
// 人员 父类
function Person(name, sex){
this.name = name;
this.sex = sex;
}
//添加方法
Person.prototype.sayHi = function(){
console.log("大家好!!");
}
//员工 子类 , 增加了 salary 薪水 属性
function Employee(name, sex, salary){
// 调用父类的构造函数, 将自己及属性值 传入
Person.call(this, name, sex);
this.salary = salary;
}
//设置子级构造函数的原型
Employee.prototype = new Person;
Employee.prototype.constructor = Employee;
//声明子类的方法
Employee.prototype.eat = function(){
console.log("去食堂")
}
const wang = new Employee('王小二', '男',6499 );
console.log(wang);
console.log(wang.name);
console.log(wang.sex);
console.log(wang.salary);
wang.sayHi();
wang.eat()
在这段代码中,Employee
是 Person
的子类。以下是这两句代码的含义:
Employee.prototype = new Person;
这行代码的作用是将Person
构造函数的一个实例赋值给Employee
原型对象。通过这样做,所有Employee
类的实例都将继承Person
类的所有方法和属性(通过原型链)。在这里调用new Person
时并没有传入参数,因此新创建的Person
实例的name
和sex
属性将是undefined
。不过,在接下来的Employee
构造函数内部已经通过Person.call(this, name, sex)
正确地设置了这些属性。Employee.prototype.constructor = Employee;
在 JavaScript 中,每个构造函数的原型对象都有一个内置的constructor
属性,它指向该构造函数本身。但是,当执行了Employee.prototype = new Person
后,Employee
原型对象的constructor
被修改为指向Person
。为了修复这一问题,并确保Employee
的实例能够正确识别其构造函数,需要手动设置Employee.prototype.constructor
为Employee
。这样做的目的是在后续可能涉及检查对象构造函数的场景下(如 instanceof 操作符或.constructor
属性),能正确识别出对象是由哪个构造函数创建的。
2.12.2.4.ES 6 构造继承
// ES 6
class Person {
//构造方法
constructor(name, sex){
this.name = name;
this.sex = sex;
}
//父类的成员方法
sayHi(){
console.log("大家好!!");
}
}
class Employee extends Person {
//构造方法
constructor(name, sex, salary) {
super(name, sex);// Phone.call(this, brand, price)
this.salary = salary;
}
eat() {
console.log("去食堂")
}
// sayHi(){
// console.log("大家好!!我转正了");
// }
}
const li = new Employee('李小三', '女', 5799 );
console.log(li);
console.log(li.name);
console.log(li.sex);
console.log(li.salary);
li.sayHi();
li.eat()
2.12.2.5.( ES 11 )私有属性
通过 在属性前加 # 来设置私有的属性,
在内部可以直接使用( 如: info() ),
在外部直接调用 会报错 , Uncaught SyntaxError: Private field ‘#age’ must be declared in an enclosing class
class Person{
//公有属性
name;
//私有属性
#age;
#weight;
//构造方法
constructor(name, age, weight){
this.name = name;
this.#age = age;
this.#weight = weight;
}
info(){
console.log(this.name);
console.log(this.#age);
console.log(this.#weight);
}
}
//实例化
const girl = new Person('李小三', 18, '45kg');
console.log(girl.name); // 李小三
console.log(girl.#age); // Uncaught SyntaxError: Private field '#age' must be declared in an enclosing class
console.log(girl.#weight); // Uncaught SyntaxError: Private field '#weight' must be declared in an enclosing class
girl.info();