@demonly
2017-10-14T06:40:02.000000Z
字数 5998
阅读 1032
JavaScript
ECMAScipt 中为对象的属性定义了内部特性,这些特性在 JavaScript 中不能够直接访问。特性有两种:数据属性和访问器属性。
对象的每个属性或者方法都有以下特性。
要修改属性默认的特性必须使用 Object.defineProperty()方法,这个方法接收三个参数,属性所在的对象、属性的名字和一个描述符对象。描述符对象表示需要对这个属性的特性所做的修改,这个对象必须设置 configurable、enumerable、writeable 和value 中的一个或多个属性,然后 Object.defineProperty()方法会对这些属性做出对应的修改。
访问器属性包含一对 getter 和setter 函数,在读取访问器属性时会调用 getter 函数,写入访问器属性时会调用 setter 函数。访问器属性有以下特性。
var book = {
_year: 2004,
edtion: 1
};
Object.defineProperty(book, "year", {
get: function() {
return this.year;
},
set: function(newValue) {
if (newValue > 2004) {
this._year = newValue;
this.edtion += newValue - 2004;
}
}
});
book.year = 2005;
alert(book.edtion); //2
Object.defineProperties()可以同时定义多个属性,这个方法接受两个参数,要修改属性的对象,和一个包含要修改的属性的对象。
var book = {}
Object.defineProperties(book, {
_year: {
writeable: true,
value: 2004
},
edtion: {
writeable: true,
value: 1
}
});
使用 Object.getOwnPropertyDescriptor()方法可以取得给定属性的描述符。这个方法接受两个参数,参数所在对象和要取得器描述符的属性名称。
Object 的每个实例都具有下列属性和方法。
省略了方法中的冒号和 function 关键字
var person = {
name: "Nicolas",
sayName() {
console.log(this.name)
}
}
在方法中调用 super 可以获得对象原型的引用
工厂模式用函数封装以特定借口创建对象的细节,在函数内部首先新建一个 Object 的实例,然后为其添加属性。
工厂模式的缺点是没有指明类。
function Person(name, age) {
this.name = name;
this.age = age;
}
var person1 = new Person("Nicholas", 29);
new 操作符内部的操作是
//新建一个对象
var obj = {};
//将对象的_proto_属性指向构造函数的原型
obj._proto_ = Person.prototype;
//将 this 指向对象并执行构造函数
Person.call(obj);
//返回对象
return obj;
构造函数模式的缺点是不同实例上的同名函数是不同的,每实例化一个对象都要定义一个函数。但是如果将函数写在全局作用域中就航务封装性可言。
function Person() {}
Person.prototype.age = 18;
Person.prototype.sayName = function() {};
var person = new Person();
person.age = 19;
只要创建了一个新函数,就会为这个函数创建一个 prototype 属性,这个属性指向构造函数的原型对象。
所有原型对象都会获得 constructor 属性,这个属性指向其所在的构造函数。
当使用构造函数创建一个新实例后,该实例的内部将包含一个不可见的proto属性,这个属性指向构造函数的原型对象。下图中的 Object 指的是 Person.prototype。
通过 isPrototypeOf()方法可以确定对象之间是否存在这个关系
Person.prototype.isPrototypeOf(person); //true
无论属性是直接存在于对象中还是存在于原型中,使用 in 操作符都会返回 true。用 hasOwnProperty()方法可以确定属性时否直接存在于对象中。
"sayName" in person; //true
person.hasOwnProperty("sayName"); //false
for-in 循环会枚举通过对象能够访问到的所有可枚举属性,也就是说原型中的属性也会被枚举。
Object.keys()方法接受一个
对象作为参数,返回一个包含对象上所有可枚举属性的字符串数组。因此通过对象的实例调用时不会返回原型中的属性。
Object.keys(Person.prototype); //["age", "sayName"]
Object,keys(person); //["age"]
可以以对象字面量的方式直接对原型对象赋值。但是这么做会覆盖掉原型对象上的所有属性,包括 constructor 属性。因此必要时我们需要手动写入 constructor 属性,手动写入的 constructor 属性的[[Enumerable]]会被默认设置为 true。
function Person() {};
Person.prototype = {
age: 18,
sayName: function() {}
}
Object.defineProperty(Person.prototype, "constructor", {
enumerable: false,
value: Person
});
对原型对象做的任何修改都能够立即从实例上反映出来,即使是先创建了实例后修改原型对象。
但是重写原型对象时情况就不一样了。
function Person() {}
var person = new Person();
Person.prototypr = {
age: 18
}
person.age; //error
因为实例的proto属性始终指向原型对象,而不是构造函数,在重写原型对象时实际上是创建了一个新对象然后赋值给了 Person.prototype。实例上的proto属性依然指向原来的原型对象。
对于包含引用类型的属性,所有实例上的这个属性都指向同一个引用类型值,在某一个实例上修改这个属性都会改变所有实例上的属性值。因此可以组合使用构造函数模式和原型模式,这是目前使用最为广泛的一种方法。
function Person(age, friend){
this.age = 18;
this.friend = [];
}
Person.prototype = {
constructor: Person,
sayName: function(){}
}
将构造函数和原型分别定义缺乏封装性,因此出现了动态原型模式。
function Person(age, friend){
this.age = 18;
this.friend = [];
if (typeof this.sayName != "function"){
//确保以下代码只会被执行一次
Person.prototype = {
constructor: Person,
sayName: function(){}
}
}
}
这种模式的基本思想是创建一个函数,这个函数的作用仅仅是封装创建对象的代码。
function Person(name){
var o = new Object();
o.name = name;
o.sayName = function() {};
}
稳妥构造函数模式与寄生构造函数模式的不同就在于稳妥构造函数中使用了私有变量。
function Person(name){
var o = new Object();
name = name;
o.sayName = function() {};
return o;
}
JavaScript 中实现原型链的本质是用一个新类型的实例重写构造函数的原型对象。
function SuperType() {
//some code
}
function Subtype() {
//some code
}
SubType.prototype = new Supertype();
var instance = new SubType();
这样的结果就是:instance 对象中的proto属性指向 SubType 的原型,SubType 的原型中的proto又指向 SuperType 的原型。
当访问 instance 对象的属性时,会首先在实例中寻找该属性,如果没有找到就会到对象的proto属性指向的对象中寻找,以此类推,直到找到该属性为止。因此 instance 对象就继承了 SubType 和SuperType 中的所有属性和方法。
借用构造函数模式的基本思想是在子类型的构造函数内部调用超类型的构造函数。
function SuperType(name) {
this.name = name;
}
function SubType(name) {
SuperType.call(this, name);
}
组合继承结合了原型链和借用构造函数模式
function SuperType(name) {
this.name = name;
}
SuperType.prototype.sayName = function() {
alert(this.name);
}
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
通过借用构造函数模式来定义属性,通过原型模式来定义方法。
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
var person = {
name: "",
friend: []
}
var anotherPerson = object(person);
这个函数允许以一个对象为原型创建对象,在 ES5中的 object.create()方法使这个函数得到了规范化。
function createAnother(original){
var clone = Object.create(original);
clone.sayHi = function(){
alert("Hi");
}
return clone;
}
组合继承在使用的时候有一个不足,就是无论什么情况下超类型的构造函数都会被调用两次。一次是在创建子类型的原型的时候,一次是在创建实例时借用的构造函数。在定义原型的时候子类型就已经获得了超类型所有的属性和方法,在借用构造函数的时候又重写了一次这些属性。
寄生式组合继承的模式如下
function inheritPrototype(subType, superType) {
var prototype = Object.create(supertype.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
寄生式组合继承不再调用构造函数,而是直接继承自构造函数的原型对象。
function SuperType(name){
this.name = name;
this,color = [];
}
SuperType.prototype.sayName = function(){
alert(this,name);
}
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function() {
alert(this.age);
};
class Foo {
constructor() {}
}
命名类表达式
let Class1 = class Class2 {};
console.log(typeof Class1); // function
console.log(typeof Class2); // undefiend
在属性或方法前加 static 关键字可以声明静态成员,静态成员仅能够通过类访问,不能够通过实例访问。
使用 extends 关键字可以实现类的继承,子类的构造函数中访问 this 之前必须使用 super() 取得 this。
class Square extends Rectangle {
constructor(...args) {
super(...args);
}
}
在子类中可以通过 super 调用父类中的方法(不包括静态成员)