JS面向对象程序设计

JavaScript关于面向对象语法操作及基础知识

对象:

创建对象的方式:

  • 实例化对象:

    1
    var obj = new Object();
  • 对象字面量:

    1
    var obj = { ... };

属性类型:

数据属性:

包含一个数据值的位置。在这个位置可以读取和写入值。

1
var person = { name: xuchao, ... };     // name为数据属性
4个描述其行为的特性:
  • [ [ Configurable ] ]:表示能否通过delete删除属性,能否修改属性的特性,能否把属性修改为访问器属性。默认为true。
  • [ [ Enumerable ] ] :表示能否通过for-in循环返回属性。默认为true。
  • [ [ Writable ] ] :表示能否修改属性的值。默认为true。
  • [ [ Value ] ] :属性的数据值。默认为undefined。
修改属性默认的特性:
1
2
3
4
5
6
Object.defineProperty( Object , propertyName, obj );
/********************************************
Object: 属性所在的对象,
propertyName: 属性名,
obj: 描述符对象 { Writable : false, ..... }
********************************************/

使用Object.defineProperty()创建新属性时,如果不指定,则ConfigurableEnumerableWritable默认为 false

访问器属性:

不包含数值。不能直接定义,智能通过Object.defineProperty()定义。

  • [ [ Configurable ] ]:表示能否通过delete删除属性,能否修改属性的特性,能否把属性修改为访问器属性。

  • [ [ Enumerable ] ] :表示能否通过for-in循环返回属性。默认为true。

  • [ [ Get ] ] :读取属性时调用的函数。默认为undefined。

  • [ [ Set ] ] :在写入属性时调用的函数。默认为undefined。

定义多个属性:

1
2
3
4
5
6
7
8
9
10
11
Object.defineProperties( Object , {
propertyName_1: {
writable: ... ,
...
},
propertyName_2: {
writable: ... ,
...
},
....
});

读取属性的特性:

1
2
var obj = Object.getOwnPropertyDescriptor( Object , propertyName );
//返回一个对象,如果是数据属性,则对象属性为数据属性的特性,访问器属性同理。

创建对象:

工厂模式:

用函数封装对象:

1
2
3
4
5
6
7
8
9
10
11
12
function createPerson (name, age, job) {
var o = new Object();
o.name = name;
o.age = age;
a.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
// 实例化一个对象
var person_1 = createPerson("Xuchao", 23, "Web Security Engineer");

缺点:没有解决对象识别问题

构造函数模式:

创造自定义的函数构造属性和方法:

1
2
3
4
5
6
7
8
9
10
function Person (name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
alert(this.name);
};
}
// 实例化一个对象
var person_1 = new Person("Xuchao", 23, "Web Security Engineer");

实例化对象后会产生一个constructor(构造函数)属性,该属性指向原型,既 person_1.constructor = Person

优点:解决工厂模式的缺点。

缺点:每个方法需要在每个实例上重新创建一遍。

原型模式:

让所有对象实例共享原型对象的属性和方法:

1
2
3
4
5
6
7
8
9
10
11
12
function Person () {
Person.prototype.name = "Xuchao";
Person.prototype.age = 23;
Person.prototype.job = "Web Security Engineer";
Person.prototype.sayName = function () {
alert(this.name);
};
}
//实例化一个对象
var person_1 = new Person();
person_1.sayName(); // "Xuchao"
alert(person_1.sayName() == person_2.sayName()); // "true"
理解原型对象:

1、Obj.prototype.isPrototypeOf( obj_x ); 判断obj_x中是否有Obj.prototype的指针

2、Object.prototype.getPrototypeOf( obj_x ); 返回obj_x中[ [ prototype ] ]的值

​ 为实例添加名字相同的属性,会将指针指向实例属性而屏蔽掉原型属性上的值,若想让指针重新指向原型属性,需用 delete 删除实例属性。

Obj.hasOwnPrototype( propertyName ) 判断属性值是实例属性还是原型属性

原型与in操作符:

单独使用 in 操作符,可以判断指定属性是否可以通过对象访问到(与实力属性、原型属性无关,只要能访问该属性都返回true

hasOwnProperty()方法结合使用可以判断该属性是存在于实例对象中,还是存在于原型中。

Object.keys( Element ) - 接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数学组

Object.getOwnPropertyNames( Element ) - 得到所有实力属性,无论它是否可枚举

更简单的原型语法:

通过对象字面量重写原型对象:

1
2
3
4
5
6
7
8
9
10
function Person(){
}
Person.prototype = {
name: 'Xuchao',
age: 23,
job: 'Web',
sayName: function(){
alert(this.name);
}
};

使用该方法重写原型,constructor不再指向Person,而是指向Object,解决方法如下:

1
2
3
4
Person.prototype = {
constructor: Person, //解决constructor不指向Person的问题
...
};//此方法会将constructor变为可枚举对象,即[ [ Enumberable ] ]特性被设置为true
原型的动态性:

对原型对象所进行的任何修改都能够立即从实例上反映出来,因为指向原型的是一个指针而不是一个副本

1
2
3
4
5
var friend = new Person();
Person.prototype.sayHi = function(){
console.log("Hi");
};
friend.sayHi(); // "Hi"

重写原型对象后,实例指向的依旧是原来的原型对象而非新的原型对象

原型对象的问题:
  1. 省略了为构造函数传递初始化参数的环节,结果会导致所有的实例具有相同的属性值。
  2. 原型中所有的属性都是被很多实例共享的,在一个实例中修改原型属性其他实例也同样会修改。

组合使用构造函数模式和原型模式:

构造函数模式用于定义实例属性,原始模式用于定义方法和共享的属性

> 每一个实例都会有一份实例属性的副本,但又同时共享着对方法的引用

寄生构造函数模式:

创建一个函数,封装创建对象的代码,再返回新创建的对象。

1
2
3
4
5
6
7
8
9
10
11
12
function Person (name, age, job) {   //一般情况下不建议使用
var o = new Object();
o.name = name;
o.age = age;
a.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
// 实例化一个对象
var person_1 = new Person("Xuchao", 23, "Web Security Engineer");

与工厂模式区别:使用new操作符,包装函数叫做构造函数

稳妥构造函数模式:

  1. 没有公共属性
  2. 其方法不引用this对象

与其他模式不同:

  1. 新建方法不引用this
  2. 不使用new操作符调用构造函数

适合在安全执行环境下使用

继承

利用原型链实现继承。

原型链

一个实例指向原型对象,而原型对象又是另一个原型的实例,层层叠进,构成原型链。

默认原型:

所有的引用类型都继承了Object,所有函数都是Object的实例。因此默认原型都会包含一个指针指向Object.prototype

原型和实例的关系:

  1. instanceof操作符
1
instance instanceof Object      //实例instance是否在对象Object中  true/false
  1. isPrototypeOf方法
1
Object.prototype.isPrototypeOf(instance)  //同上
原型链定义需要注意的问题:
  1. 给原型添加方法的代码一定要放在替换原型的语句以后
  2. 通过原型链事先继承时,不能使用对象字面量创建原型方法。因为这样会重写原型链。

借用构造函数(伪造对象、经典继承)

在子类型构造函数的内部调用超类型构造函数

1
2
3
4
5
6
7
function SuperType(){
this.colors = ['red','green','blue'];
}
function SubType(){
// 继承了SuperType
SuperType.apply(this);
}

不会共享属性,而是会通过apply()方法产生一个副本

  1. 可以在子类型构造函数中向超类型构造函数传递参数
1
2
3
4
5
6
7
function SuperType( name ){
this.name = name;
}
function SubType(){
// 继承了SuperType,同时还传递了参数
SuperType.apply(this, 'Xuchao');
}
  1. 方法函数无法复用,在超类型原型定义的方法,对子类型也不可见。

组合继承(伪经典继承)

将原型链和借用构造函数的技术结合到一块

1
2
3
4
5
6
7
8
function SuperType(name){
// 利用构造函数模式创建原型属性
this.name = name;
}
SuperType.prototype.sayName = function(){
// 利用原型链的方式创建原型方法
console.log(this.name);
}

原型式继承

借助原型中可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。

1
2
3
4
5
function object(o){
function F(){} // 创建一个临时性的构造函数
F.prototype = o; // 将传入的对象作为这个构造函数的原型
return new F(); // 返回了这个临时类型的一个新实例
} // 其本质是object对传入的对象进行了一次浅复制

ES5通过新增Object.create()方法规范了原型式继承:

1
Object.create(protoObj, propeytyObj[可选])  //新对象的原型对象, 为新对象定义额外属性的对象

包含引用类型值得属性始终都会共享相应的值,就像是用原型模式一样

寄生类继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再返回对象

1
2
3
4
5
6
7
function createAnother( func_1 ){
var clone = object( func_1 ); // 通过调用函数创建一个新对象[可以用其他方式]
clone.sayHi = function(){ // 以某种方式来增强这个对象
alert("Hi");
};
return clone; // 返回这个对象
}

寄生组合式继承

通过借用构造函数来继承属性,通过原型链的混合形式来继承方法。

1
2
3
4
5
function inheritPrototype( subType, superType ){
var prototype = object(superType.prototype); // 创建对象
prototype.constructor = subType; // 增强对象
subType.prototype = prototype; // 指定对象
}// 只调用一次SuperType构造函数,避免了在SubType.prototype上创建不必要的、多余的属性,且原型链不变

客官里面请~