JavaScript对象
JavaScript对象
一、属性类型
1.数据属性
- configurable:表示属性是否可以通过过delete删除并定义,是否可以修改它的特性,以及是否可以把它改成访问器属性。默认情况下,所有直接定义在对象上的属性的这个特性都是true。
- Enumerable:表示属性是否可以通过for-in循环返回。默认情况下,所有直接定义在对象上的属性的这个特性都是true。
- writable:表示属性的值是否可以被修改。默认情况下,所有直接定义在对象上的属性的这个特性都是true。
- value:包含属性实际的值。读取和写入属性值的位置,默认值为undefined。
2.访问器属性
- configurable:表示属性是否可以通过delete删除并重新定义,是否可以修改它的特性,以及是否可以把它改为数据属性。默认情况下,所有直接定义在对象上的属性的这个特性都是true。
- enumerable:表示属性是否可以通过for-in循环返回。默认情况下,所有直接定义在对象上的属性的这个特性都是true。
- get:获取函数,在读取属性时调用。默认值为undefined。
- set:设置函数,在写入属性时调用,默认值为undefined。
访问器属性是不能直接定义的,必须使用Object.defineProperty()。
二、Object.defineProperty和Object.defineProperties
// 定义单个属性
let a = {};
Object.defineProperty(a, 'name', {
value: 'Mr.Yao',
// 没有写的配置默认为false
// configurable: false,
// writable: false,
// enumerable: false,
});
console.log(a);
// {}
// 定义多个属性
let a = {};
Object.defineProperties(a, {
name_: {
value: 'Mr.Yao',
// 如果没有加上下面这条,name_无法写入
// writable: true,
},
age: {
value: 22,
},
name: {
get () {
return this.name_;
},
set (name) {
this.name_ = name;
},
}
});
console.log(a)
console.log(Object.getOwnPropertyDescriptors(a))
console.log(a.name);
a.name = 'Mr.Do';
console.log(a.name);
三、读取属性的特性
使用Object.getOwnPropertyDescriptor()方法可以取得指定属性的属性描述符。
使用Object.getOwnPropertyDescriptors()实际上会在每个自有属性上调用Object.getOwnPropertyDescriptor()并在一个新的对象中返回。
四、合并对象
Object.assign()这个方法接收一个目标对象和一个或多个源对象作为参数,然后将每个源对象中可枚举(Object.propertyIsEnumerable()返回true)和自有(Object.hasOwnProperty()返回true)属性复制到目标对象。Object.assign()实际上对每个源对象执行的是浅复制。如果多个源对象都有相同的属性,则使用最后一个复制的值。
五、创建对象
-
工厂模式
工厂模式是一种众所周知的设计模式,广泛应用于软件工程领域,用于抽象创建特定对象的过程。
function createPerson(name, age, job) { let o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function() { console.log(this.name); } return o; } let person = createPerson('Mr.Yao', 22, 'Student'); console.log(person);
-
构造函数模式
自定义构造函数,以函数的形式为自己的对象类型定义属性和方法。
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = function() { console.log(this.name); } } let person1 = new Person('Mr.Yao', 22, 'Software Engineer'); let person2 = new Person('MD', 12, 'Student'); person1.sayName(); person2.sayName();
和工厂模式的区别:
- 没有显式地创建对象。
- 属性和方法直接赋值给this。
- 没有return。
要创建实例,应使用new操作符。
- 在内存中创建一个对象。
- 这个新对象内部的[[prototype]]特性被赋值为构造函数的prototype属性。
- 构造函数内部的this被赋值为这个新对象(即this指向新对象)。
- 执行构造函数内部的代码(给新对象添加属性)。
- 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。
-
原型模式
每个函数都会创建一个prototype属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。实际上,这个对象就是通过调用构造函数创建的对象的原型。使用原型对象的好处是,在它上面定义的属性和方法可以被对象实例共享。
function Person() {} Person.prototype.name = 'Nicholas'; Person.prototype.age = 29; Person.prototype.job = 'Software Engineer'; Person.prototype.sayName = function() { console.log(this.name); } let person1 = new Person(); let person2 = new Person(); person1.sayName(); person2.sayName(); console.log(person1.sayName === person2.sayName) // true
无论何时,只要创建一个函数,就会按照特定的规则为这个函数创建一个prototype属性(即指向原型对象)。默认情况下,所有原型对象自动获得一个名为constructor的属性,指回与之关联的构造函数。Person.ptototype.constructor指向Person。Chrome,Firefox等浏览器实现,实例有一个
__proto__
指向原型。
六、继承
-
原型链
每个构造函数都有一个原型对象,原型有个属性指回构造函数,而实例有一个内部指针指向原型。如果原型是另一个类型的实例,那就意味着这个原型本身有一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。
function SuperType() { this.superProperty = 'Super'; } SuperType.prototype.saySuperName = function() { console.log(this.superProperty); } function SubType() { this.subProperty = 'Sub'; } SubType.prototype = new SuperType(); // console.log(SubType.prototype.constructor) // SubType原型是一个SuperType的实例,这个实例有一个指向SuperType原型的指针 // SubType.prototype.constructor === SuperType SubType.prototype.saySubName = function() { console.log(this.subProperty); } let sub = new SubType; sub.saySuperName(); sub.saySubName();
子类有时候需要覆盖父类的方法,或者增加父类没有的方法。为此,这些方法必须在原型赋值之后再添加到原型上。
以对象字面量方式创建原型方法会破坏之前的原型链,因为这相当于重写了原型链。
function SuperType() { this.superProperty = 'Super'; } SuperType.prototype.saySuperName = function() { console.log(this.superProperty); } function SubType() { this.subProperty = 'Sub'; } SubType.prototype = new SuperType(); // console.log(SubType.prototype.constructor) // console.log(SubType.prototype.constructor === SuperType) // 通过对象字面量添加新方法,这会导致上一行无效 SubType.prototype = { saySuperName() { console.log(this.superProperty); }, saySubName() { console.log(this.subProperty); } } SubType.prototype.saySubName = function() { console.log(this.subProperty); } let sub = new SubType; sub.saySuperName(); sub.saySubName();
-
盗用构造函数
在子类构造函数中调用父类构造函数。函数是在特定上下文中执行代码的简单对象,所以可以使用apply()和call()方法以新创建的对象对上下文执行构造函数。
function SuperType() { this.colors = ['red', 'blue', 'green']; } function SubType() { // 继承SuperType SuperType.call(this); } let instance1 = new SubType(); instance1.colors.push('black'); console.log(instance1.colors); let instance2 = new SubType(); console.log(instance2.colors);
通过使用call()或者apply()方法,SuperType构造函数在为SubType的实例创建的新对象的上下文中执行了。这相当于新的SubType对象上运行了SuperType()函数中的所有初始化代码。
-
组合继承
使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。
function SuperType(name) { this.name = name; this.colors = ['red', 'blue', 'green']; } SuperType.prototype.sayName = function() { console.log(this.name); } function SubType(name, age) { // 继承属性 SuperType.call(this, name); this.age = age; } // 继承方法 SubType.prototype = new SuperType(); SubType.prototype.sayAge = function() { console.log(this.age); } let instance1 = new SubType('Mr.Yao', 22); instance1.colors.push('black'); console.log(instance1.colors); instance1.sayName(); instance1.sayAge(); let instance2 = new SubType('MD', 18); instance2.sayName(); instance2.sayAge(); console.log(instance2.colors);
-
原型式继承
object函数会创建一个临时构造函数,将传入的对象赋值给这个构造函数的原型,然后返回这个临时类型的一个实例。
function object(o) { function F() {}; F.prototype = o; return new F(); } let person = { name: 'Nicholas', friends: ['Shely', 'Court', 'Van'], }; let anotherPerson = object(person); anotherPerson.name = 'Greg'; anotherPerson.friends.push('Rob'); console.log(person.friends)
Object.create()方法将原型式继承的概念规范化了。
-
寄生式继承
创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。
function createAnother(original) { let clone = Object.create(original); clone.sayHi = function() { console.log('hi'); }; return clone; } let person = { name: 'Nichloas', friends: ['Shely', 'Court', 'Van'], }; let anotherPerson = createAnother(person); anotherPerson.sayHi();
object.create()函数不是寄生式继承所必须的,任何返回新对象的函数都可以在这里使用。
-
寄生式组合继承
组合继承存在效率问题,父类构造函数始终会被调用两次:一次在创建子类原型时调用,另一次在子类构造函数中调用。
function SuperType(name) { this.name = name; this.colors = ['red', 'blue', 'green']; } SuperType.prototype.sayName = function() { console.log(this.name); } function SubType(name, age) { // SubType实例化的时候第二次调用SuperType() SuperType.call(this, name); this.age = age; } // 第一次调用SuperType() SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function() { console.log(this.age); };
寄生式组合继承通过盗用构造函数继承属性,但使用混合式原型链继承方法。
function inheritPrototype(subType, superType) { // 创建一个父类原型实例 let prototype = Object.create(superType.prototype); prototype.constructor = subType; // 赋值给子类原型 subType.prototype = prototype; } function SuperType(name) { this.name = name; this.colors = ['red', 'blue', 'green']; } SuperType.prototype.sayName = function() { console.log(this.name); } function SubType(name, age) { // SubType实例化的时候第二次调用SuperType() SuperType.call(this, name); this.age = age; } // 第一次调用SuperType() // SubType.prototype = new SuperType(); inheritPrototype(SubType, SuperType); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function() { console.log(this.age); }; let instance = new SubType(); console.log(instance.colors); console.log(instance instanceof SuperType); // true console.log(instance instanceof SubType); // true
七、类
1.类的构成
类可以包含构造函数方法、实例方法、获取函数、设置函数和静态类函数,但这些都不是必须的。空的类定义照样有效。默认情况下,类定义中的代码都在严格模式下执行。
2.类的构造函数
-
实例化
使用new调用类的构造函数会执行如下操作。
- 在内存中创建一个新的对象。
- 这个新对象内部的[[prototype]]指针被赋值为构造函数的prototype属性。
- 构造函数内部的this被赋值为这个新对象(即this指向新对象)。
- 执行构造函数内部的代码(给新对象添加属性)。
- 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的对象。
-
把类当成构造函数
类标识符有prototype属性,而这个原型也有一个constructor属性指向类自身。
-
实例成员
每次通过new调用类标识符时,都会执行类构造函数。在这个函数内部,可以为新创建的实例(this)添加”自有“属性。每个实例对应一个唯一的成员对象,这意味着所有成员都不会在原型上共享。
-
原型方法与访问器
为了在实例间共享方法,类定义语法把在类块中定义的方法作为原型方法。
-
静态方法
静态类成员在类定义中使用static关键字作为前缀。在静态成员中,this引用类自身。
-
非函数原型和类成员
class Person { sayName() { console.log(`${Person.greeting} ${this.name}`); } } // 在类上定义数据成员 Person.greeting = 'My name is'; // 在原型上定义数据成员 Person.prototype.name = 'jake'; let p = new Person(); p.sayName(); // My name is jake
3.继承
-
继承基础
使用extends关键字,就可以继承任何拥有[[construct]]和原型的对象。派生类都会通过原型链访问到类和原型上定义的方法。this的值会反映调用相应方法的实例或者类。
class Vehicle { identifyPrototype(id) { console.log(id, this); } static identifyClass(id) { console.log(id, this); } } class Bus extends Vehicle {}; let v = new Vehicle(); let b = new Bus(); b.identifyPrototype('bus'); Bus.identifyClass('bus'); v.identifyPrototype('vehicle'); Vehicle.identifyClass('vehicle');
-
构造函数和super()
派生类的方法可以通过super关键字引用它们的原型。这个关键字只能在派生类中使用,而且仅限于类构造函数、实例方法和静态方法内部。在类构造函数中使用super可以调用父类构造函数。
class Vehicle { constructor() { this.hasEngine = true; } } class Bus extends Vehicle { constructor() { // 不要在调用super()之前使用this,否则会抛出ReferenceError super();// 相当于super.constructor() console.log(this instanceof Vehicle); console.log(this); } } new Bus();
- super只能在派生类构造函数和静态方法中使用
- 不能单独引用super关键字,要么用它调用构造函数,要么用它引用静态方法。
- 调用super()会调用父类构造函数,并将返回的实例赋值给this。
- super()的行为如同调用构造函数,如果需要给父类构造函数传参,则需手动传入。
- 如果没有定义类构造函数,在实例化派生类时会调用super(),而且会传入所有传给派生类的参数。
- 在类构造函数中,不能在调用super()之前引用this。
- 如果在派生类中显式定义了构造函数,则要么在其中调用super(),要么必须在其中返回一个对象。
-
抽象基类
class A { constructor() { if (new.target === A) { throw new Error('A不能被实例化'); } } } let a = new A; // Error: A不能被实例化
-
类混入[Mixin]
class Vehicle {}; let FooMixin = (SuperClass) => class extends SuperClass { foo() { console.log('foo'); } }; let BarMixin = (SuperClass) => class extends SuperClass { bar() { console.log('bar'); } }; let BazMixin = (SuperClass) => class extends SuperClass { baz() { console.log('baz'); } }; class Bus extends FooMixin(BarMixin(BazMixin(Vehicle))) {}; let b = new Bus(); b.foo(); b.bar(); b.baz();
class Vehicle {}; let FooMixin = (SuperClass) => class extends SuperClass { foo() { console.log('foo'); } }; let BarMixin = (SuperClass) => class extends SuperClass { bar() { console.log('bar'); } }; let BazMixin = (SuperClass) => class extends SuperClass { baz() { console.log('baz'); } }; // class Bus extends FooMixin(BarMixin(BazMixin(Vehicle))) {} function mix(BaseClass, ...Mixins) { return Mixins.reduce((pre, cur) => { return cur(pre); }, BaseClass) } class Bus extends mix(Vehicle, FooMixin, BarMixin, BazMixin) {}; let b = new Bus(); b.foo(); b.bar(); b.baz();