时隔两年后二刷JavaScript高级程序语言,纯手打读书笔记+思维导图,让自己有一个比较全面的知识体系,后面有遇到例子的时候会慢慢补充更多的用法。有不足之处欢迎大家评论区指出,共勉!!
一、对象的属性
- 定义属性
Object.defineProperty(obj,prop,desc) 或者Object.defineProperties(obj,{prop:{desc}})
desc是属性的配置,有六种,以下是其默认值。
value get set 默认值为undefined
writable enumerable configurable //通过defineProperty定义的属性,这三个都默认为false,但object.name = ovv定义的都默认为true。
enumerable 可枚举:属性是否会出现在for in 或者 Object.keys()的遍历中
configrable 是否可以修改属性配置(除了writable为true时value可变,writable可以由true到false,或者),以及可否删除。
2、属性的分类
1)数据属性:有保存数值的位置,没有set和get特性描述符。
2)访问器属性:不包含数值,没有writable和value,若无set则默认只读。
访问器属性只能通过defineProperty方法定义。
两种属性之间的转换是:只要添加了两种属性特有的描述符,就会自动转为哪种属性。
3、读取属性的特性
1)获取单个属性:
通过Object.getOwnPerpertyDescriptor(obj,prop)返回一个描述属性特性的对象desc,例如desc.value来获取属性的值,desc.enumerable获取是否可枚举。
2)获取全部属性:
通过Object.getOwnPerpertyDescriptor(obj)能够获取对象的全部属性的配置。{prop:{desc}}
4、合并对象
Object.assign()将每个对象中可枚举+自身属性复制到目标对象,通过get/set读写属性值,对值的复制是浅复制。
var result = Object.assign(obj1,obj2,obj3) //三个对象的属性合并到Result对象中。
- 对象相等
Object.is(a,b)解决了===的特殊情况,如在Object.is()中,+0,0相等,两个NaN相等。
- 简写
- 属性名和值的变量名一样 name
- 计算属性名表达式 [getKeyName(nameKey)]
- 简写方法名 funName(){}
- 对象解构
let { name: newName, age,job=’111’} = person; //将属性解构为变量,还可以赋初始值,用于当没有此属性时使用默认值。
二、创建对象☆
1、工厂模式
function createPerson(name) {
let o = new Object();
o.name = name;
return o;
}
工厂模式:函数内部new对象并返回,没有解决对象标识问题(对象是什么类型的)
- 构造函数模式
function Person(name){
this.name = name;
this.sayName = function(){} //构造函数里的方法会在每个实例上都创建一遍,所以在这里可以把函数的定义挪到构造函数外部去。this.sayName = sayName(Person外部定义)
};
const obj = new Person(name)
构造函数的问题:外部定义方法会搞乱全局作用域,因为方法在全局定义却只会在obj内部调用,通过原型模式解决此问题。
- 原型模式
原型上定义的属性和方法可以被对象实例共享。
可以通过原型对象字面量的形式,创建对象,注意要添加构造函数(完全修改原型)。
缺点:①无法通过构造函数传参 ②原型上的数据是每个实例共享的,而非单独拥有。
- 组合使用构造模式+原型模式
原型对象写方法和constructor,构造函数中写属性。
- 原型
函数Person:prototype属性,指向原型对象(Person.prototype)
原型对象Person.prototype:是一个普通的对象,有constructor(指向Person)+原型上的属性+方法。Person.prototype.isPrototypeOf(person1)//判断person1的__proto__是否指向此原型对象。
实例person:实例内部[[Prototype]]特性指向构造函数的原型对象(Person.prototype),但我们不能直接访问[[Prototype]],而是通过__proto__来访问。
Object.getPrototypeOf(),能够返回person的__proto__
获取实例自身属性person1.hasOwnProperty(‘name’)
in操作符:可以单独使用或在for-in中循环使用。
单独用in:只要可以通过对象访问到的属性in都为true,无论原型还是实例本身。
for-in:可被枚举的属性(包括实例和原型)
Object.keys(person) //数组:所有可枚举属性名
Object.getOwnPropertyNames(person) //所有实例属性(包括不可枚举)
Object.getOwnPropertySymbols() //所有实例属性(符号键名)
后面两个方法的枚举顺序是确定的:升序数值键名,插入顺序字符或符号键。
对象迭代:
Object.values() //value的数组
Object.entries() // [key,value]的数组
原型注意:
- 原型对象字面量
可以通过对象字面量一次性对原型对象创建多个属性。但这样是重写了整个Person.propertype对象,因此要通过Object.defineProperty()来加一个不可枚举的constructor。
- 动态性:
在原型上添加属性和方法,之前创建的实例也会相应的反应,因为指针已指向此对象。
但重写整个原型对象,由于[[Propertype]]是在调用构造函数时自动赋值的,所以之前创建的实例还是指向原来的原型对象,并不会做出修改。
- 继承
原型链:即原型对象本身是另一个构造函数的实例,通过这样实现继承。
判断继承关系:①instanceof ②Person.prototype.isPrototypeOf(obj)
- 原型链继承
- 盗用构造函数
3、组合继承(盗用构造函数 + 原型链)
盗用构造函数:实例属性不共享 + 构造函数传参
原型链:共享方法 + 实现继承
function SuperType(name){ //构造函数
this.name = name;
}
function SubType(name, age){ // 盗用构造函数:继承属性
SuperType.call(this, name);
this.age = age;
}
// 原型链:继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType; //修改构造器为SbType本身。
SubType.prototype.sayAge = function() {
console.log(this.age);
};
父类构造函数会调用两次:原型链继承(属性在原型对象上) + 盗用构造函数(属性在实例本身上,遮蔽了原型对象上的属性)
4、原型式继承
Object.create(新对象原型,desc) //desc和defineProperty的desc一样,定义新增的属性。
适用于不需要单独创建构造函数的情况,与原型模式一样,需注意引用值的共享。
- 寄生式继承
function createAnother(original){ //要继承的对象
let clone = object(original); // 通过调用函数创建一个新对象
clone.sayHi = function() { // 以某种方式增强这个对象
console.log("hi");
};
return clone; // 返回这个对象
}
寄生式继承实现类似于工厂模式。它会使得函数无法重用,类似构造函数模式。
- 寄生式组合继承
核心代码就是将组合继承中,创建原型链部分的代码替换为以下代码,即可实现只调用一次原型的构造函数。寄生式组合继承可以算是引用类型继承的最佳模式。
function inheritPrototype(subType, superType) {
let prototype = object(superType.prototype); // 创建对象
prototype.constructor = subType; // 修改构造器
subType.prototype = prototype;} // 赋值原型,实现原型链
- 类
class关键词是es6新增的,可以定义类,但这是语法糖,实际还是原型和构造函数。
- 定义类
class Person{}
const Person = class {} //在{}块中可以通过Person.name获取类表达式的名称字符串。
类受块级作用域限制
- 类构造函数
constructor关键词 定义创建类的构造函数。
- 实例化过程
创建新对象-->[[prototype]]=Person.prototype-->this = obj-->执行构造函数返回obj
类构造函数必须使用new调用,而其他构造函数若不使用new调用,则为window调用。
2)把类当成一个特殊的函数
typeof Person //function
类本身具有与普通构造函数一样的行为。通过类创建或者类的constructor创建一个对象,instancof是可以区分的。
let p1 = new Person(); //p1 instanceof Person 为True
let p2 = new Person.constructor(); //p2 instanceof Person.constructor 为True
3、类语法中的成员
- 实例成员:构造函数中this指向新创建的实例,可以为实例添自有属性。
- 原型方法:在类中定义的方法作为原型方法,也支持set/get访问器。
- 静态成员:static定义在类上,this引用类自身,静态成员执行不特定于实例的操作。
- 迭代器与生成器:在类定义中,可以将生成器定义在原型上(原型方法)或类上(static)
注意:不能在类块中给原型添加原始值或对象作为成员数据,但可以在类定义外添加(会数据共享)。
4、继承
1、继承基础
使用extends关键词可以继承类、普通的构造函数。(有[[Construct]]和原型的对象)
this的值会反应调用相应方法的实例或者类。
2、构造函数/HomeObject/super
1)super:
super在派生类的方法(构造函数、静态方法)中,用super引用类的原型。
调用super会调用父类的构造函数,可以传参,并将返回的实例赋值给this,不能在调用super之前引用this。
2)[[HomeObject]]
构造函数和静态方法有[[HomeObject]],指向定义该方法的对象,super则指向[[HomeObject]]的原型。
3)构造函数
若未定义构造函数,则实例化派生类时会将全部参数传入父类的构造函数中。
若定义了构造函数,则必须调用super或return一个对象。
- 抽象基类
抽象基类:可以供其他类继承,但本身不会被实例化。new.target保存通过new调用的类或函数,来实现抽象基类,还可以要求派生类必须定义某种方法。
// 抽象基类
class Vehicle {
constructor() {
if (new.target === Vehicle) { //抽象基类
throw new Error('Vehicle cannot be directly instantiated');
}
if (!this.foo) { //要求派生类必须定义foo方法
throw new Error('Inheriting class must define foo()');
}
console.log('success!');
}
}
- 继承内置类型
继承JS内置的类型,这些类型的方法有返回值时,会默认返回新的类型,可以如下修改。
class SuperArray extends Array { //继承内置类型
static get [Symbol.species]() { //修改方法返回类型为Array而非默认的SuperArray
return Array;
}
}
- 类混入
把不同的类集中到一个类中,区别与对象混入(Object.assign()),实现是在类表达式中混入多个对象元素,可以嵌套调用ABC三个类,但是很少使用混入模式,更多使用组合模式。
function mix(BaseClass, ...Mixins) {
return Mixins.reduce((accumulator, current) => current(accumulator), BaseClass);
}
class Bus extends mix(Vehicle, A, B, C) {}