对象
创建对象
Object创建
let person = new Object()
对象字面量
let person = {}
属性的类型
数据属性
-
[[Configurable]]---delete是否有效,特性是否可修改,是否可以将其更改为服务器属性,即:不可配置
-
[[Enumerable]]---是否可通过for-in循环返回,不可枚举
-
[[Writable]]---属性值是否可以被更改,不可更改
-
[[Value]]---实际的值
直接定义在对象上的属性:Value默认为undefined,其他默认为true
使用Object.defineProperty()定义时,如果configurable,enumerable,writable未指定,则其默认为false
let person = {}
Object.defineProperty(person,'nData',{
configurable:false,//严格模式下,尝试【删除】只读属性的值会抛出错误,
//configurable:false的操作不可逆,此后使用Object.defineProperty操作该属性会报错。
enumerable:false,
writable:false,//严格模式下,尝试【修改】只读属性的值会抛出错误
value:'测试!!'
})
console.log(person.nData)
服务器属性
-
[[Configurable]]---delete是否有效,特性是否可修改,是否可以将其更改为数据属性
-
[[Enumerable]]---是否可通过for-in循环返回
-
[[Get]]---读取时调用,默认为undefined
-
[[Set]]---更改时调用,默认为undefined
Get和Set默认为undefined,其他默认为true
let person = {
nData__:'测试!!',
}
Object.defineProperty(person,'nData',{
configurable:false,//严格模式下,尝试【删除】只读属性的值会抛出错误,该操作不可逆,此后使用Object.defineProperty操作该属性会报错
enumerable:false,
get(){//严格模式下,如果没有定义get(),在修改时会抛出错误,非严格模式则返回undefined
return this.nData__
},
set(value){//严格模式下,如果没有定义set(),在修改时会抛出错误
this.nData__ = value
}
})
console.log(person.nData)
person.nData = '更改!!'
console.log(person.nData)
相关方法
定义一个属性
Object.defineProperty(目标对象,属性名称,描述符对象)
定义多个属性
Object.defineProperties(目标对象,描述符对象)
let person = {
}
Object.defineProperties(person,{
data_1:{
value:'第一个数据!!'
},
data_2:{
value:'第二个数据!!'
}
})
console.log(person.data_1)
console.log(person.data_2)
读取(查看)一个属性的特性
Object.getOwnPropertyDescriptor(目标对象,属性名称)
let person = {
}
Object.defineProperty(person,'nData',{
writable:true,
value:'测试!!'
})
let descriptor = Object.getOwnPropertyDescriptor(person,'nData')
console.log(descriptor)
//打印如下:
// {
// value: '测试!!',
// writable: true,
// enumerable: false,//未指定,默认为false
// configurable: false//未指定,默认为false
// }
读取(查看)多个属性的特性
Object.getOwnPropertyDescriptors(目标对象)
let person = {
textData:'第二个数据!!'
}
Object.defineProperty(person,'nData',{
writable:true,
value:'测试!!'
})
let descriptor = Object.getOwnPropertyDescriptors(person)
console.log(descriptor)
//打印如下:
// {
// textData: {
// value: '第二个数据!!',
// writable: true,
// enumerable: true,
// configurable: true
// },
// nData: {
// value: '测试!!',
// writable: true,
// enumerable: false,
// configurable: false
// }
// }
合并对象
Object.assign(目标对象,...源对象)
let person = {
textData:'person!!',
name:'微尔利特.伊芙加登'
}
let person2 = {
textData:'person2!!'
}
//1参为目标对象,后参为源对象,实际是将后参中可枚举的属性复制到1参
let res = Object.assign(person,person2)
console.log(res)
// { textData: 'person2!!', name: '微尔利特.伊芙加登' }
console.log(person)//Object.assign()在返回新对象的同时,也修改了目标对象
// { textData: 'person2!!', name: '微尔利特.伊芙加登' }
- Object.assign()为浅复制,相同属性取最后;可以使用Object.assign()实现对象浅复制。
- Object.assign()没有“回滚”的概念,如果赋值中途出错,则只完成已复制的部分
相等判断
Object.is(1参,2参)
只接收两个参数
//类似于===,但有如下区别
console.log(Object.is(+0,-0))//false ===比较为true
console.log(Object.is(0,-0))//false ===比较为true
console.log(Object.is(+0,0))//true
console.log(Object.is(NaN,NaN))//true 和isNaN(NaN)相同效果,使用===比较为false
获取[[Prototype]]的值
Object.getPrototypeOf(目标对象)
更改[[Prototype]]的值
Object.setPrototypeOf(目标对象,新父亲)
- 重写对象的原型继承关系,严重影响性能,不建议使用,可以通过Object.create()实现效果
创建对象,并为其指定原型
Object.create(新父亲)
let person = {
name:'巴布'
}
let person2 = Object.create(person)//返回一个指定了继承的新对象
person2.size = 21
console.log(person2.name)//巴布
console.log(person2.size)//21
console.log(Object.getPrototypeOf(person2) === person)//true
确定属性是否在目标对象上
obj.hasOwnProperty(属性名)
- 属性存在于目标对象上返回true,不存在则返回false
let obj = function(){
this.a = 'a'
}
obj.prototype.b = 'b'
let test = new obj()
console.log(test.hasOwnProperty('a'))//true 来自实例
console.log(test.hasOwnProperty('b'))//false 来自原型
对象迭代
可枚举
-
Object.keys(目标对象):返回对象实例上所有可枚举属性所构成的数组
-
Object.values(目标对象):返回对象实例上所有可枚举属性的值所构成的数组
-
Object.entries(目标对象):返回对象实例上所有可枚举属性和值(键值对)所构成的数组
let obj = function(){
this.a = 'a-值'
this.c = 'c-值'
}
obj.prototype.b = 'b-值'
let test = new obj()
console.log(Object.keys(test))//[ 'a', 'c' ]
console.log(Object.values(test))//[ 'a-值', 'c-值' ]
console.log(Object.entries(test))//[ [ 'a', 'a-值' ], [ 'c', 'c-值' ] ]
for-in循环遍历包括原型上的属性在内的所有可枚举属性
- for-in和Object.keys()的枚举顺序是不确定的。
- Object.getOwnPropertyNames(),Object.getOwnPropertySymbols(),Object.assign()的枚举顺序是确定的。
列出所有实例的属性
所有属性,包括不可枚举属性
Object.getOwnPropertyNames(目标对象)
列出所有实例的符号属性
所有属性,包括不可枚举属性
Object.getOwnPropertySymbols(目标对象)
创建对象
工厂模式
function createObj(name){//工厂-加工并返回一个对象
let o = new Object()
o.name = name
o.setName = function(p){
this.name = p
}
return o
}
let obj1 = createObj('杰骆驼')
console.log(obj1.name)//杰骆驼
obj1.setName('术士猎人!!')
console.log(obj1.name)//术士猎人!!
存在的问题
- 没法确定创建的对象是什么类型,即对象标识问题
构造函数模式
- 解决了对象标识问题
function createObj(name){//这是一个构造函数
this.name = name
this.setName = function(p){
this.name = p
}
}
let obj1 = new createObj('洋葱骑士!!')//使用new关键字实例化对象
console.log(obj1.name)//洋葱骑士!!
obj1.setName('干杯!!')
console.log(obj1.name)//干杯!!
存在的问题
- 相同逻辑的函数重复初始化 将函数提到外部虽然能够解决上述问题,但又出现了新的问题:
- 将函数提到外部后导致代码过于分散
原型模式
理解原型
-
只要创建一个函数,其内部就会自行创建一个prototype属性指向其原型对象。
-
默认情况下,所有原型对象都自动获得一个指向其构造函数的constructor属性。
-
原型对象默认只会获得constructor属性,其他所有方法都继承自Object。
-
每次实例化时,实例中都会有一个[prototype]指针指向原型。
实现
let obj1 = function(weapon){
this.weapon = weapon
}
obj1.prototype.setweapon = function(weapon){//将一部分内容放在原型上
this.weapon = weapon
}
let test = new obj1('中子灭杀')
console.log(test.weapon)//中子灭杀
test.setweapon('地爆天星')
console.log(test.weapon)//地爆天星
test.weapon = '!!!' //实例中的属性遮蔽了原型对象上面的同名属性
console.log(test.weapon)//'!!!'
存在的问题
- 虽然原型上的属性不可更改,但引用类型内部不受限制,内部会被更改并共享。
继承
原型链继承
- 将父类型的实例赋到子类型的原型上,子类型的原型上就有了一个指向父类型原型的指针
function person(){}
person.prototype.getName = function(p){
return '测试!!'
}
function person2(){}
person2.prototype = new person()//继承原型上的属性和方法
let test = new person2()
console.log(test.getName())
原型链继承的弊端
-
因为所有子类型使用同一套原型上的数据,虽然基本类型无法通过子类更改,但引用类型会被更改并共享,导致数据混乱。
-
子类型在实例化时不能给父类型的实例化传参。
盗用构造函数
- 通过call()或apply()传入子类型this调用父类型构造方法,达到继承父类型上实例属性的效果
function person(name){
this.name = name
}
function person2(){
person.call(this,'测试!!')
}
let test = new person2()
console.log(test.name)
盗用构造函数的弊端
-
必须在构造函数中定义方法,导致方法不能复用。
-
子类型不能访问父类型原型上定义的属性和方法。
组合继承
实际使用最多的继承方式
-
通过盗用构造函数-继承实例属性。
-
通过原型链继承-继承原型上的属性和方法。
function person(name){
this.name = name
}
person.prototype.setName = function(name){
this.name = name
}
function person2(){
person.call(this,'测试!!')//继承实例属性
}
person2.prototype = new person()//继承原型上的属性和方法
let test = new person2()
console.log(test.name)//测试!!
test.setName('更改!!')
console.log(test.name)//更改!!
原型式继承
在已有对象的基础上再创建一个对象
let person = {
name:'巴布'
}
let person2 = Object.create(person)//通过Object.create()实现
console.log(person2.name)//巴布
使用场景
- 不需要单独创建构造函数,但仍然需要在对象间共享数据的情况(引用类型会被实例间共享,和原型模式一样)
寄生式继承
- 实现思路类似于寄生构造函数和工厂模式:创建一个实现继承的函数,以某种方式增强对象,然后返回该对象。
function createAnother(obj){//将函数增强并返回
let newObj = Object(obj)//Object()非必须
newObj.name = '拉妮'
newObj.setName = function(name){
this.name = name
}
return newObj
}
let obj1 = {size:'36D'}
let obj2 = createAnother(obj1)
console.log(obj2.size)//36D
console.log(obj2.name)//拉妮
obj2.setName('娇小拉妮')
console.log(obj2.name)//娇小拉妮
寄生式组合继承
- 是组合式继承的升级版本,较组合式继承,效率有所提升,但较复杂
组合式继承问题所在:
父类构造函数始终会被调用两次:一次是在创建子类原型时调用,另外一次是在子类构造函数中调用。 多次调用,影响效率。
function person1(name){
this.name = name
}
person1.prototype.setName = function(name){
this.name = name
}
function person2(){
person1.call(this,'卡尔弗兰兹')
}
function createAnother(obj1,obj2){//对组合式继承中原型链继承部分的替换
let prototype = Object(obj2.prototype)//创建对象
prototype.constructor = obj1//增强对象
obj1.prototype = prototype//赋值对象
}
createAnother(person2,person1)
let test = new person2()
console.log(test.name)//卡尔弗兰兹
test.setName('死亡爪陛下')
console.log(test.name)//死亡爪陛下
new关键字的执行过程
1.生成一个空对象
2.改变新对象proto指向,将其赋值为构造函数的prototype属性
3.将构造函数中的this指向改变为新对象并执行构造函数
4.返回新对象(如果构造函数有返回,则返回构造函数的返回,否则返回新对象)
类
类定义
块级作用域
类就是一种特殊的函数
声明式
class Person {}//类声明不会提升
类表达式
const Person = class PersonName{}
const Person = class {}//简化
- 类表达式名称是可选的;在把类表达式赋值给变量后可以通过name属性取得类表达式的名称字符串,但不能在表达式作用域外部访问这个标识符。
- 类内部:PersonName.name和Person.name返回PersonName
- 类外部:Person.name返回PersonName
- PersonName在类外部无法访问。
类构造函数
constructor
class Person {
constructor(name){//类构造函数-实例化时被调用执行,类构造函数必须配合new使用,否则抛出错误!
this.name = name
}
}
let obj1 = new Person('未闻蹊语')
console.log(obj1.name)//未闻蹊语
类内部构成
class Person {
constructor(name){//实例化时被调用执行
this.name = name//使用this定义的变量都存在于实例上
}
setName(name){//被定义在了原型对象上
this.name = name
}
static locate(){//静态方法---定义在类本身
console.log('我是你爸爸!!')
}
}
let obj1 = new Person('未闻蹊语')
console.log(obj1.name)//未闻蹊语
obj1.setName('!!!')
console.log(obj1.name)//!!!
console.log(obj1.locate)//undefined
Person.locate()//我是你爸爸!!
在类外部是可以手动添加属性到类原型上面的。
类继承
简单继承
extends
class Person {
constructor(){//实例化时被调用执行
this.name = '未闻蹊语'
}
setName(name){//被定义在了原型对象上
this.name = name
}
}
class Person2 extends Person {}//类也可以使用extends继承普通的构造函数
let obj1 = new Person2()
console.log(obj1.name)//未闻蹊语
obj1.setName('!!!')
console.log(obj1.name)//!!!
super关键字
-
派生类的方法可以通过super关键字引用它们的原型。
-
super只能在派生类中使用,而且仅限于类构造函数·实例方法和静态方法内部。
- 在类构造函数中可以通过supper调用父类构造函数。
- 在静态方法中可以通过supper调用父类静态方法。
class Person {
constructor(name){
this.name = name
}
setName(name){
this.name = name
}
static locate(){//静态方法
console.log('我是你爸爸!!')
}
}
class Person2 extends Person {
constructor(name){
//如果派生类没有构造方法,则JS会自己调用super(),并将派生类接收到的所有参数传递给super()作为参数。
//有构造函数就必须要有super(),除非构造函数返回一个对象。
super(name)//调用父类构造方法
//不能再super前使用this,否则会抛出错误!
this.size = '36D'
}
static locate(){//静态方法
super.locate()//使用super调用父类静态方法
}
}
let obj1 = new Person2('未闻蹊语')
console.log(obj1.name)//未闻蹊语
obj1.setName('!!!')
console.log(obj1.name)//!!!
console.log(obj1.size)//36D
Person2.locate()//我是你爸爸!!
使用super时的要求
-
只能在派生类构造函数和静态方法中使用。
-
不能单独使用super,否则会抛出错误。
-
调用super()会调用父类构造方法,并将返回的实例赋值给this。
类构造函数和静态方法内部有一个[[HomeObject]]指针,该指针指向定义该方法的对象,这个指针是自动赋值的。
抽象基类
-
使用场景:一个类需要被提供给其他类继承,但本身不会被实例化。
-
ECMAScript内没有支持抽象类的语法,但可以通过new.target实现。
class Person {
constructor(){
if(new.target === Person){//new.target保存了通过new关键字调用的类或函数
throw new Error('不能实例化抽象类!!')
}
}
}
class Person2 extends Person {
constructor(){
super()
}
}
let obj1 = new Person()//抛出错误
限制子类必须定义某个方法
class Person {
constructor(){
if(new.target === Person){
throw new Error('不能实例化抽象类!!')
}
if(!this.foo){
throw new Error('派生类必须定义foo方法!!')
}
}
}
class Person2 extends Person {
constructor(){
super()
}
foo(){
console.log('foo方法!!')
}
}
let obj1 = new Person2()
继承内置类型
使用场景:可以方便的扩展内置类型。
class Person2 extends Array {//继承于Array
constructor(){
super()
}
}
let obj = new Person2()
类混入-多类继承
思路:先让B继承A,C继承B,最后再让Person继承前面合成的超类,达到多类继承的效果。
class A{
getA(){
console.log('A')
}
}
let B = (req)=>{
return class extends req{
getB(){
console.log('B')
}
}
}
let C = (req)=>{
return class extends req{
getC(){
console.log('C')
}
}
}
function mixi(BaseClass,...nClass){//将传入的类继承为一个超类,再将其返回。
for(let e of nClass){
BaseClass = e(BaseClass)
}
return BaseClass
}
class Person extends mixi(A,B,C) {}
let obj = new Person()
obj.getA()//A
obj.getB()//B
obj.getC()//C