理解对象
对象是一组
属性的无序组合,map是有序的
1. 属性类型
属性分为数据属性和访问器属性,不同属性上的
特性是不同的
1.1 数据属性
普通属性,与c++中类似
特性
- [[Configurable]]:默认为true,表示可以通过delete删除并重新定义,修改它的特性以及是否可以改为访问器属性
- [[Enumerable]]:默认为true,属性能通过for-in循环返回,
为false时无法输出该属性 - [[Writable]]:默认true,属性是否可修改,如果为false,访问器set也无法修改其值
- [[Value]]:默认为undefined,存储着属性实际的值
1.2访问器属性
并不存储值,但是用户可以访问和修改,只是最终修改的在其他属性上(个人理解为私有属性的访问get()和修改set(value)函数,只是在调用上以属性形式隐式调用)
特性
- [[Configurable]]:默认为true,表示可以通过delete删除并重新定义,修改它的特性以及是否可以改为访问器属性
- [[Enumerable]]:默认为true,属性能通过for-in循环返回,
为false时输出看不见该属性 - [[Get]]:默认为undefined,获取函数,读取属性时调用
- [[Set]]:默认为undefined,设置函数,修改属性时调用
1.3 获取属性特性
1.Object.getOwnPropertyDescriptor()
- 输入:对象,属性名(注意
属性名是字符串) - 返回:特性描述
- 只获得实例自身的属性特性描述,无法获得原型链上的属性特性描述
const obj = {
value:2
}
console.log(Object.getOwnPropertyDescriptor(obj,'value'));
//{ value: 2, writable: true, enumerable: true, configurable: true }
2.Object.getOwnPropertyDescriptors()
- 输入:对象
- 返回:特征描述
const obj = {
value:2,
get getValue(){
return this.value;
}
}
console.log(Object.getOwnPropertyDescriptors(obj));
/*{
value: { value: 2, writable: true, enumerable: true, configurable: true },
getValue: {
get: [Function: get getValue],
set: undefined,
enumerable: true,
configurable: true
}
}*/
2. 创建属性(部分)
2.1 单个属性Object.defineProperty()
- 输入:对象,属性,描述符对象
- 描述符对象中,configurable、enumerable、writable的值如果不指定,则
默认为false
const obj = new Object();
Object.defineProperty(obj, 'valueProperty', {
configurable: true,
value: 12,
writable: true
});
Object.defineProperty(obj, 'getProperty', {
get() {
return this.valueProperty + 10; // 使用this.valueProperty来引用obj的属性
},
set(value) {
this.valueProperty = value; // 使用this.valueProperty来设置obj的属性
},
enumerable:true
});
console.log(obj); //{ getProperty: [Getter/Setter] },因为valueProperty中enumerate为false
obj.getProperty = 15; // 实际修改的是valueProperty
console.log(obj.valueProperty, obj.getProperty);
2.2 多个属性Object.defineProperties()
- 输入:对象,描述符对象
- 不指定属性特性则为false
- 描述符对象有变化,需要指定属性名
const obj = new Object();
Object.defineProperties(obj,{
valueProperty:{
value : 12,
},
getProperty:{
get(){
return this.valueProperty+10;
},
set(value){
this.valueProperty = value;
},
enumerable:true
}
})
console.log(obj); //{ getProperty: [Getter/Setter] }
obj.getProperty = 15; // 无效,因为属性writable为false
console.log(obj.valueProperty, obj.getProperty);//12 22
创建对象
构造函数
1.简单构造函数
构造函数依然是函数,只是
调用形式不同:new function(参数);
函数中this讲解会在后续章节详细讲解
function Person(name,age){
this.name = name;
this.age = age;
this.sayName = function(){
console.log(this.name);
};
}
const person = new Person('juejin',1);
console.log(person);//Person { name: 'juejin', age: 1, sayName: [Function (anonymous)] }
- 执行过程
- 内存创建一个新对象
- 新对象内部[[Prototype]]特性赋值为构造函数(也是对象)的prototype属性
- 构造函数内部的this被赋值为这个新对象
- 执行内部代码
- 如果构造函数返回非空对象,则返回该对象,否则返回刚创建的对象
- 缺陷
- 构造函数构造出来的对象都有单独的函数对象,没能做到共享
- 解决:
- 将函数实现在全局,然后构造函数指向它,但是这样会污染全局
- 原型模式
2.原型模式
函数都是对象,构造函数也是对象,其上有属性prototype,指向一个原型对象(在定义函数的时候就已经自动实现,也可手动指定),在使用new constructor()时,会将新创建的对象的[[Prototype]]属性指向函数constructor的prototype,从而新创建对象拥有了原型
function Person(){};
Person.prototype.name = 'Nicholas';//在原型对象上增加值
Person.prototype.age = 29;
Person.protorype.job = 'Software Engineer';
Person.prototype.sayName = function(){
console.log(this.name);
}
const person1 = new Person();
const person2 = new Person();
console.log(person1);//Person {},本身对象是没有任何属性
person1.sayName();//juejin,但是会向上找原型
对象都有特性[[prototype]],
- 构造函数和原型之间循环引用,构造函数的prototype指向原型,原型的constructor指向构造函数
- 新创建对象与构造函数没有直接关系,只与原型有关系,可以使用
Object.getPrototypeOf()获得对象内部特征[[Prototype]]的值,一般也可以使用__proto__也可以获得 - 在原型上定义函数,就可以实现实例之间共用函数
- 原型对象也是对象,所以也有[[Prototype]],即也有原型,一直向上会找到Object,
Object的原型是null
属性
- 访问属性:会先查找自身存在该属性否,没有则查找原型对象,依次向上(原型链),知道找到或原型为null
- 重写属性:只允许在实例自身上进行重写,实例没有该属性则新增属性,并在访问时遮蔽原型链上同名属性
- 删除属性:delete操作符删除实例属性
- 区分属性:方法hasOwnProperty(属性名)返回是否是实例自身属性
- 获得属性:
- in:操作符in可以确定对象能否访问到指定属性
- for-in:会获得实例可达、可枚举的属性,包括实例属性和原型属性
- Object.keys():获得实例可枚举非符号属性,与之类似的有values(),entries()
- Object.getOwnPropertyNames():获得除符号属性外的实例属性(无论可否枚举)
- Object.getOwnPropertySymbls():只获得符号属性,符号属性是没有名字概念的
function Person(){};
Person.prototype.name = 'juejin';
Person.prototype.sayName = function(){
console.log(this.name);
}
const person1 = new Person();
const person2 = new Person;//不传参时()可以省略
person1.name = 'fugai';//重写时会在实例上添加属性,遮盖原型链上的属性
console.log(person1.name);//fugai,访问到的是实例上的属性
console.log(person2.name);//juejin,原型上的属性
console.log(person1.hasOwnProperty('name'));//true
console.log('name' in person1);//true,能访问到
console.log(person2.hasOwnProperty('name'));//false
console.log('name' in person2);//true,能访问到
delete person1.name;
console.log(person1.name);//juejin,原型上的属性
其他原型语法
- instanceOf操作符:obj原型链上找构造函数,如果存在则为true
- 指定原型对象:可以将构造函数的prototype属性指向自定义对象,记得将自定义对象的constructor属性([[Enumerable]]:false)指向构造函数,才能让instanceOf生效
但是注意时机,实例是在new constructor()的时候将构造函数原型赋值引用传递给实例的[[Prototype]]的,所以在指定构造函数的原型之前尽量不要创建实例,否则出现如下情况
- 原生对象原型:比如String的操作都是定义在String.prototype中的
问题
原型是所有实例共享的,其上属性一改俱改
Object
1.new Object()
Object()就是系统自带的构造函数,这样创造的对象就是一个空的,且原型指向Object对象
2.Object.create(原型,实例属性描述符)
创建对象,并且将对象的原型指向参数中的原型
const prototypeObj = {
sayHello() {
console.log('Hello!');
}
};
const newObjWithProps = Object.create(prototypeObj, {
myProperty: {
value: 'This is a property',
writable: true,
enumerable: true,
configurable: true
}
});
console.log(newObjWithProps.myProperty); // 输出: This is a property
Object.assign(目标对象,多个源对象)
合并对象,将源对象中的实例可迭代属性(包括符号)浅复制到目标对象,如果是访问器属性,则会调用该属性的[[Get]]获得值,再调用目标对象的[[Set]]设置属性的值;函数最后返回目标对象
- 如果多个源对象上有相同属性,则后者覆盖前者
const result = {set getterFunction(value){
console.log(`获得了值${value}`);
}};
const back = Object.assign(result,{
name:'jue',
get getterFunction(){
console.log('调用了get函数,获得值value');
return 'value';
}
},
{
name:'juejin',
age:1,
get test(){
console.log('源对象如果没有对应的set,则将返回的值创建新数据属性')
return 'getter'
}
})//调用了get函数,获得值value
//获得了值value
//源对象如果没有对应的set,则将返回的值创建新数据属性
console.log(result);//{ getterFunction: [Setter], name: 'juejin', age: 1, test: 'getter' }
console.log(result === back);//true
字面量
const obj = {}与const obj = new Object()等价,只是写法更加简洁
增强的对象语法
- 属性值引号省略:如果键是标识符、数字,则可以不写引号,引擎自动转换为字符串
const obj = {
1:123,
name:'juejini'
};
console.log(obj['1'],obj['name']);//123 juejini
- 属性值简写:属性名如果和变量名是一样的,则可只写其中一个
{name:name}可以写为{name} - 可计算属性:属性名需要使用变量存储的字符串,将属性名写成[变量]
const name = 'juejin';
const ageKey = 'age';
const obj = {
name,//简写
[ageKey]:1 //属性名绑定变量的值
};
console.log(obj);//{ name: 'juejin', age: 1 }
- 简写方法名:常规写法是属性:匿名函数,直接写成属性(参数){},当然也支持可计算属性
//下面写法都是等价的
const obj1 = {
sayName:function(){}//常规写法
};
const obj2 = {
sayName(){}//简写
}
const functionKey = 'sayName';
const obj3 = {
[functionKey](){}//可计算属性
}
console.log(obj1,obj2,obj3)
//{ sayName: [Function: sayName] } { sayName: [Function: sayName] } { sayName: [Function: sayName] }
对象解构
本质上而言,就是通过匹配键,然后将值赋值给对应位置上的变量
let {name:personName,age:personAge}={name:'juejin',age:1};
console.log(personName,personAge);//juejin 1
- 加上之前的语法增强,如果变量名和属性简写相同,则也可省略只写一个
- 如果无法对应,即属性在原对象上不存在,则变量为undefined
- 如果是实现声明的变量,则解构赋值表达式必须在一对括号内
- 如果是在函数参数中使用解构,并不会影响到arguments,同时可以在函数内使用解构后的变量
let {name,age,sex}={name:'juejin',age:1};//简写
console.log(name,age,sex); //juejin 1 undefined
function test({name}){//参数解构
console.log(arguments);
console.log(name);
}//[Arguments] { '0': { name: 'juejin', age: 1 } }
//juejin
test({name:'juejin',age:1})
继承
原型链
function SuperType(){
this.propertyRef = ['red','blue'];
this.propertyValue = 2;
}
function SubType(){
this.subProperty = false;
}
SubType.prototype = new SuperType();//简略原型链
const obj1 = new SubType()
const obj2 = new SubType()
//SubType原型没有构造函数属性没有正确指定
console.log(obj1.constructor); //[Function: SuperType]
//原型链问题
//值属性,其实是在实例上增加该属性
obj1.propertyValue = 1;
obj2.propertyValue = 4;
console.log(obj1.propertyValue);//1
console.log(obj2.propertyValue);//4
//引用属性,先访问,查询到有该值,再进行更改
obj1.propertyRef[0] = 'yellow';
console.log(obj2.propertyRef)//[ 'yellow', 'blue' ]
- 对象 instanceOf 构造函数名:如果对象原型链上出现过构造函数,则返回true
- 原型.isPrototypeOf(对象):如果原型出现在对象的原型链上,则返回true
- 原型链问题:
- 过于共享,原型上如果有引用类型,则所有实例上都能修改
- 子类型在实例化的时候无法给父类型的构造函数传参
盗用构造函数
原型链直接继承问题明显,父类上的属性会被共享,我们只希望函数属性被共享,而原始属性特别是引用值属性被定义在实例上,所以希望在
子类构造函数中能调用父类构造函数,并将this指向赋值为实例,这样就能将父类属性定义在实例上
function SuperType(){
this.propertyRef = ['red','blue'];
this.propertyValue = 2;
}
function SubType(){
SuperType.call(this);//指定this
this.subProperty = false;
}
const obj1 = new SubType()
console.log(obj1);
/*
SubType {
propertyRef: [ 'red', 'blue' ],
propertyValue: 2,
subProperty: false
}
*/
- 函数中的属性:call和apply能调用函数的同时指定函数体中的this;
- 缺点:与直接使用构造函数定义对象一样,函数没法共享
组合继承
构造函数中定义属性,原型上定义函数
function SuperType(name){//构造函数中加属性
this.propertyRef = ['red','blue'];
this.propertyValue = 2;
this.name = name
}
SuperType.prototype.sayName = function(){ //原型上定义方法
console.log(this.name);
}
function SubType(name){
SuperType.call(this,name);//调用时传参
this.subProperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
const obj1 = new SubType('juejin');
obj1.sayName();//juejin
console.log(obj1);
/*
SubType {
propertyRef: [ 'red', 'blue' ],
propertyValue: 2,
name: 'juejin',
subProperty: false
}
console.log(obj1.__proto__);//原型上多了一份属性
/**
SuperType {
propertyRef: [ 'red', 'blue' ],
propertyValue: 2,
name: undefined
}
*/
- 缺点:父类构造函数被调用两次,原型上也多了一份属性定义
原型式继承
直接继承已有的对象,即constructor.prototype=已有对象,Object.create(已有对象)就是这样
寄生组合继承
创建一个原型对象,使得原型对象的[[prototype]]指向父类原型,constructor指向构造函数
function SuperType(name){//构造函数中加属性
this.propertyRef = ['red','blue'];
this.propertyValue = 2;
this.name = name
}
SuperType.prototype.sayName = function(){ //原型上定义方法
console.log(this.name);
}
function SubType(name){
SuperType.call(this,name);//调用时传参
this.subProperty = false;
}
SubType.prototype = Object.create(SuperType.prototype);//区别
SubType.prototype.constructor = SubType;
const obj1 = new SubType('juejin');
obj1.sayName();//juejin
console.log(obj1);
/*
SuperType {
propertyRef: [ 'red', 'blue' ],
propertyValue: 2,
name: 'juejin',
subProperty: false
}
*/
console.log(obj1.__proto__);
/**
SuperType { constructor: [Function: SubType] }
*/
类ES6
本质上还是原型和构造函数,只是写法更加简单
定义
- class Person{};//与函数不同,不能提升
- const Person = class {};
class Foo {
constructor(){ /* constructor */ }
describe(){ /* describe */ }
}
// 等价于
function Foo (){
/* constructor */
}
Foo.prototype.describe = function(){ /* describe */ }
构成
由类构造函数、实例属性、原型方法、get、set、静态方法和私有属性组成;
- 类构造函数
与普通构造方法基本一样,只是普通构造函数可以像普通函数那样调用,而类构造函数不行;创建实例的时候就是在调用此方法,且后台执行过程与调用普通构造函数一样;
在构造函数中添加实例成员
- 实例属性
- 可以在构造函数中使用this来定义
- 可以在块内使用字段声明
class Person{
constructor(name){
this.name = name;//构造函数中定义实例属性
}
sex;//可以不需要赋值,默认为undefined
age = new Number(10);//类块中定义实例属性
sayName(){
console.log(this.name);
}
}
const person1 = new Person('juejin');
const person2 = new Person()
console.log(person1,person2);
// Person { sex: undefined, age: [Number: 10], name: 'juejin' }
// Person { sex: undefined, age: [Number: 10], name: undefined }
console.log(person1.age === person2.age) //false
- 方法
类块中定义函数,和对象中一样,也可以简写,注意是定义在原型上而不是实例上,且方法中的this与调用环境有关,一般是指向实例的
class Person{
constructor(name){
this.name = name
this.age = 10;
}
sayName(){//定义在原型上
console.log(this.name);
}
}
const person1 = new Person();
const person2 = new Person();
console.log(person1); //Person { name: undefined, age: 10 }
console.log(person1.sayName === person2.sayName)//true
- 获取函数、设置函数
也是定义在原型上的
class Person{
constructor(name){
this.name = name
this.age = 10;
}
get sayName(){
return 'sayName'+this.name;
}
}
const person1 = new Person();
const person2 = new Person();
console.log(person1); //Person { name: undefined, age: 10 }
console.log(person1.sayName) //sayNameundefined
console.log(person1.sayName === person2.sayName)//true,因为是定义在原型上的
- 类静态成员
定义在类之上,只能使用类名访问,且静态方法中的this一般是指向类的,可以被继承
class Person{
constructor(name){
this.name = name
this.age = 10;
}
get sayName(){
return 'sayName'+this.name;
}
static nameStatic = '静态成员';
static sayNameStatic(){
console.log("静态方法调用",this.nameStatic);
}
}
const person1 = new Person('juejin');
Person.sayNameStatic();//静态方法调用 静态成员
- 私有属性
在属性名前加#号,可以使得该属性:在类外部引用
#名称、引用未在类内部声明的私有属性,或尝试使用 [delete]移除声明的属性都会抛出语法错误。
class ClassWithPrivateField {
#privateField;
constructor() {;
delete this.#privateField; // Syntax error,不能使用delete
this.#undeclaredField = 42; // Syntax error,没有在类块中声明
}
}
const instance = new ClassWithPrivateField();
instance.#privateField; // Syntax error,不能在类外直接访问
- 迭代器和生成器方法
在方法前面加*即可将方法变为生成器,如果实现了*[Symbol.iterator](){}(默认迭代器),则实例就变成了可迭代对象
class Person{
constructor(...parms){
this.parms = parms
}
*[Symbol.iterator](){
for(const item of this.parms){
yield item;
}
}
}
const person1 = new Person('xi','tu');
for(const item of person1){
console.log(item);
}//xi tu
继承
使用
extends关键字,不仅可以继承类,还可以继承拥有[[construct]]和原型的对象(函数对象)
super
指向父类,要么用来调用父类构造函数,要么用来调用父类对应的方法(一般是调用静态方法)
- 构造函数中调用super(),此时就类似于之前的盗用构造函数,将父类构造函数中的属性全部加到实例上
- 在构造函数中,必须先使用super(),才能使用this
- 如果子类没有写构造函数,则默认将创建实例时传入的参数全部传入父类构造函数中,并调用父类构造函数
class Person{
constructor(name){
this.name = name;
}
speak(){
console.log('im Person');
}
}
class Teacher extends Person{
constructor(name,age){
super(name);
this.age = age;
}
speak(){
super.speak();
console.log('im a teacher');
}
}
const teacher1 = new Teacher('xitu',22);
console.log(teacher1)//Teacher { name: 'xitu', age: 22 }
teacher1.speak();
//im Person
//im a teacher
class Student extends Person{};
const student = new Student('juejin');//没有构造函数依然会调用父类构造函数
console.log(student);//Student { name: 'juejin' }