一、理解对象
属性的类型:ECMA-262使用一些内部特性来描述属性的特征,开发者无法在js中直接访问这些特性。为了将某个特性标识为内部特性,用两个[]括号括起来。
数据属性:数据属性包含一个保存数据值的位置,有四个内部特性描述其行为
[ [ Configurable ] ] :表示属性是否可以通过delete删除并重定义,是否可以修改其特性,以及是否可以修改为访问器属性。默认为true
[ [ Enumberable ] ] :表示属性是否可以通过for-in循环返回。默认为true
[ [ Writable ] ] : 表示属性的值是否可以修改。默认为true
[ [ Value ] ] :包含属性的实际值。默认为undefined
修改属性的默认特性:Object.defineProperty(object, propertyName, ConfigureObj)
注意点:
1.一个属性被定义为不可配置之后,就不能再变成可配置的;再次调用Object.definePropert()并修改非writable属性会导致错误
2.在调用Object.defineProperty()时,configurable enumerable writable如果不指定则会默认为false
3.在 configurable 特性为 false 且 writable 为 true 时,可以更改 value 和 writable 特性。
访问器属性:访问器属性不包含数值,相反包含一个getter和一个setter函数,但这两个函数非必须,有4个特性
访问器属性无法直接定义,必须通过Object.defineProperty()定义
[ [ Configurable ] ] : 表示是否可以通过delete删除并重新定义;是否可以修改其特性;是否可以改为数据属性
[ [ Enumberable ] ]: 表示是否可以通过for-in 取到
[ [ Get ] ] : 获取函数,读取时被调用,默认为undefined
[ [ Set ] ] :设置函数,写入时被调用, 默认为undefined
定义多个属性: Object.defineProperties()
获取属性的特性: Object.getOwnPropertyDescriptor(obj, propertyName)
ES2017新增 Object.getOwnPropertyDescriptors(obj)
合并对象: 合并或者混入(mixin)
ES6提供Object.assign(), 接受一个目标对象和一个或多个源对象作为参数。将每个源对象中的可枚举的和自有的属性复制给目标对象。对于每个符合条件的属性,这个方法会使用源对象上的[ [ Get ] ]取得属性的值,然后使用目标对象上的[ [ Set ] ]设置属性的值
注意点:
1.实际上对每个对象是浅复制,如果多个源有相同的属性,取最后一个;
2.不能在两个对象之间转移获取函数和设置函数;
3.如果赋值之间出错,则会中止操作,但不会回滚。
对象标识及相等判定:ES6新增 Object.is()
增强的对象语法:
1.属性值简写;2.可计算属性;3.简写方法名
对象解构:
1.解构赋值不一定与对象的属性匹配。可以忽略某些属性;而如果引用的属性不存在,则该变量的值为undefined
2.解构并不要求变量在解构表达式中声明,不过如果给事先声明的变量赋值,则赋值表达式必须在一对括号中
3.解构在内部使用函数ToObject()把源数据结构转为对象。这意味着原始值会被当做对象,null和undefined不能被解构
嵌套解构:解构对引用嵌套的属性或者赋值目标没有限制。在外层没有定义的情况下不能使用嵌套解构
部分解构:涉及多个属性的解构赋值是一个输出无关的顺序化操作;开始的赋值成功而后面的错误时,则解构完成部分
参数上下文匹配:在函数的参数列表中进行解构赋值,对参数的解构赋值不会影响arguments对象
二、创建对象
1.工厂模式:用于抽象创建特定对象的过程。
可以解决创建多个类似对象的问题,但是没有解决对象标识的问题(即创建的对象是什么类型)
2.构造函数模式:在实例化时,如果不传参数,构造函数后面的括号可加可不加;有new操作符,就会执行构造函数。在这个过程中会执行如下操作
1)在内存中创建一个新对象
2)这个新对象内部的[ [ Prototype ] ] 特性被赋值为构造函数的prototype属性
3) 构造函数内部的this被赋值为这个新对象
4)执行构造函数内部代码
5)如果构造函数返回非空对象,则返回该对象;否则返回新对象
构造函数也是函数,与普通函数不同的只是调用方法,如果构造函数用new操作符,则被当做普通函数执行
构造函数模式的问题在于公共函数或者变量会在每个实例中创建一遍,解决此问题的方法是在外部定义公共函数
原型模式:每个函数都会创建一个prototype属性,这个属性是一个对象,包含 应该由特定引用类型的实例 共享的 方法和属性。由这种方式定义的属性和方法是所有实例共享的。
1.理解原型:无论何时,只要创建一个函数,就会按照特定规则为函数创建一个prototype属性,指向其原型对象,
原型对象自动获的一个名为constructor属性,指向与之关联的构造函数。
在自定义构造函数时,其原型对象只会默认获取constructor属性,其他属性继承自Object
每次调用构造函数时创建一个实例时,实例内部的 [ [ Prototype ] ] 特性(指针)会指向构造函数的原型对象,可以通过__proto__访问实例的原型对象
虽然不是所有的实现都暴露了[ [ Prototype ] ] ,但是可以通过原型对象调用isPrototypeOf()确定两个对象之间的关系。
Object类型有一个方法getPrototypeOf()
Object.setPrototypeOf()可以向实例的[ [ Prototype ] ] 写入一个新值
Object.create()可以在创建新的对象时,指定其原型对象为所接受的参数。
Object.hasOwnProperty()判断一个对象的属性是在本身上还是在原型对象上
2.原型层级:访问一个对象的某个属性时,在对象上没找到则会到对象的原型对象上寻找,直至Object.prototype
3.原型和in操作符:
单独使用in操作符时,会在可以通过对象访问指定属性时返回true,无论属性在对象上还是其原型对象上
for-in中,可以通过对象访问到的且可被枚举的属性都会返回,包括对象属性和其原型对象属性。
遮蔽原型中不可枚举属性的实例属性也会在for-in中返回。
获取对象实例上所有的可枚举的属性 Object.keys()
获取对象实例上所有属性,无论是否可枚举 Object.getOwnPropertyNames()
4.属性枚举顺序:
for-in Object.keys()枚举顺序不确定
Object.getOwnPropertyNames() Object.getOwnPropertySymbols() Object.assign()先以升序枚举数值键,然后以插入顺序枚举字符串和符号键
对象迭代:Object.values() Object.entries()
重写整个原型会切断最初原型与构造函数之间的联系,但是已经创建了的实例引用的仍然是切断之前的原型。
原型的问题:
弱化了构造函数初始传参的能力
不同的实例应该有自己的数据备份,而不是全部共享。
三、继承
ECMAScript只支持实现继承,不支持接口继承,主要通过原型链实现。
1.原型链:
默认情况下,所有的引用类型都继承自Object
原型与实例的关系确定
1)instanceof 如果一个实例的原型链中出现过相应的构造函数,则返回true
2) isPrototypeOf() 原型链中每个原型都可以调用这个方法,只要原型链中包含这个原型,就会返回true
关于方法:
1.子类需要添加方法时,必须在原型赋值后再添加
2.以字面量方式创建原型方法会破坏之前的原型链
原型链的问题:
原型中包含的引用值会在所有实例之间共享
子类型在实例化时不能给父类型传递参数
2.盗用构造函数
在子类的构造函数中调用父类的构造函数。这种技术又叫对象伪装或者经典继承,解决了原型中包含引用值导致的问题
主要优点:可以传递参数
主要缺点:必须在构造函数中定义方法,因此函数不可重用,子类也不能访问父类原型上的方法
3.组合继承(伪经典继承):
综合了原型链和盗用构造函数,使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性
4.原型式继承:出发点是即使不自定义类型,也可以通过原型实现对象之间的信息共享。本质上对obj进行了一次浅拷贝
适用情况,有一个对象,需要在其基础上再创建一个对象。
ES5新增Object.create()方法,将原型式继承的概念规范化,接受两个参数,作为新对象原型的对象,定义额外属性的对象(这个跟
Object.defineProperties的第二个参数一样,使用内部特性描述一个参数)
5.寄生式继承:思路类似于寄生构造函数和工厂模式,创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象
通过寄生式继承给对象添加函数会导致函数难以重用
6.寄生式组合继承:
组合式继承的效率问题,父类构造函数会被调用两次:一次在创建子类原型时,一次在子类的构造函数中
通过盗用构造函数继承属性,但使用混合原型链继承方法,不通过调用父类构造函数给子类原型赋值,而是取得父类原型副本
四、类
类定义:类声明、类表达式
虽然函数声明可以提升,但是类定义不行
函数受函数作用域限制,类受块作用域限制
类的构成包括,构造函数方法、实例方法、获取函数、设置函数、静态函数
类表达式的名称可选,在赋值完成后,可以通过name属性访问类表达式的名称,但是不能在类表达式外部访问
类构造函数:
1)实例化时:需要使用new关键字,如果不传参数,可以省略()。默认情况下,构造函数返回this对象,返回其他对象,无法通过instanceof检测
2)使用new的过程:
在内存中创建一个对象
将对象内部的[ [ prototype ] ] 特性指针指向其原型对象
将this指向该对象
执行构造函数内部代码(添加属性方法)
如果构造函数返回非空对象,返回指定值;否则返回刚刚创建的对象
普通的构造函数如果不使用new,会将this作为全局对象使用
把类当做特殊的函数:
类本身使用new调用时,类本身会被当做构造函数,而constructor()不会被当做构造函数
如果创建实例时直接将类构造函数当做普通构造函数使用,类本身不会被当做构造函数,而是类内部的构造函数
与立即调用函数表达式相似,类表达式也可以立即实例化
实例、原型、类成员
实例成员:每个实例都对应唯一成员对象,所有成员都不会在原型上共享
原型方法与访问器:
类定义语法把在类块中定义的方法作为原型方法(所有在类块中定义的内容都存在于其原型上)
不能在类块中给原型添加原始值或者对象作为成员数据,可以通过获取函数和设置函数设置访问器,语法行为类似于普通对象。
静态类方法:使用static关键字标识,定义在类本身身上
非函数原型和类成员:
虽然类定义并不支持在原型或者类上添加成员数据,但是在类的外部可以手动添加
迭代器与生成器方法:
类语法支持在类本身和类原型上定义生成器方法(个人实验后,也可以在constructor()函数中给实例定义生成器方法)
继承:虽然类继承使用的新语法,但是背后依然是使用原型链
继承基础:使用extends关键字,可以继承任何拥有[ [ Construct ] ] 和原型的对象,不仅可以继承类,亦可以继承普通构造函数
类和原型上定义的方法,都会被带到派生类中
构造函数、HomeObject、super()
ES6给类构造函数和静态方法内部添加了内部特性 [ [ HomeObject ] ],这个指针指向该方法的对象,只能在js引擎内部访问,super始终定义为[ [ HomeObject ] ] 的原型
派生类可以通过super关键字引用它们的原型,仅限于派生类的构造函数和静态方法中
不能单独引用super关键字,要么调用其构造函数,要么引用其静态方法
调用super()会调用父类构造函数,并将返回的实例赋值给this
super()的行为如同调用构造函数,如果需要传参,传入super()
如果没有定义类构造函数,则实例化派生类时会调用super(),而且会传入所有传给派生类的参数
构造函数中,不能在调用super()之前引用this
在派生类中,如果定义了构造函数,其中要么必须调用super(),要么必须返回一个对象
抽象基类:
new.target保存了通过new关键字调用的类或函数,以此判断可以实现抽象基类,并要求派生类必须实现的方法
继承内置类型:
有些内置类型的方法会返回新的实例,默认情况下返回的实例类型与原始实例类型一致。
可以通过覆盖Symbol.species访问器,决定返回的实例类型
类混入:
extends关键字后可以接任何一个能够解析为类或者对象的JavaScript表达式