理解对象
属性类型:为实现JavaScript引擎,定义了部分特性的内部值,用两个方括号表示,如[[ Enumerable ]]
1、数据属性:一个数据值的位置,可否读写
[[ Configtable ]]:能否通过delete删除属性从而重新定义属性。一旦定义成不可配置,则不可逆
[[ Enumerable ]]:能否通过for-in循环返回属性
[[ Writable ]]:能否修改属性的值
[[ Value ]]:包含这个属性的数据值
PS:若要修改属性默认特征,必须使用
Object.defineProperty(obj, “属性”, { wtritable: false,value: "Nick" })
进行修改。
如,var person = {};
Object.defineProperty(person, “name”, {writable: false, value: ’Nick’});
alert(person.name); // “Nick”
person.name = ‘jack’; //严格模式下报错
alert(person.name); // “Nick”
如果使用 Object.defineProperty创建新属性,没有指定configuration、enumerable、writable,则默认都是false
2、访问器属性:不包含数据值;包含一堆setter、getter(非必要)
[[ Configtable ]]:能否通过delete删除属性从而重新定义属性。一旦定义成不可配置,则不可逆
[[ Enumerable ]]:能否通过for-in循环返回属性
[[ Get ]]:表示能否通过for-in循环返回属性。默认undefined
[[ Set ]]:在写入属性时调用的函数。默认undefined
PS:访问权不能直接定义,必须使用
Object.defineProperty
进行定义。
如,var book = { _year:2004, edition:1 };
Object.defineProperty(book, “year”, {
get: function() {
return this._year;
},
set: function(newValue) {
if(newValue>2004) {
this._year = newValue;
This.edition += newValue - 2004
}
}
});
book.year = 2005;
alert(book.edition); // 2
这是访问器属性的常见方式,即设置一个属性的值会导致其他属性发生变化。
不一定要同时指定getter和setter。
1、只指定getter意味着属性不能写,尝试写入会被忽略。
2、严格模式下,只指定getter,尝试写入会报错。
3、只指定setter意味着不能读,,非严格模式下返回undefined,严格模式下报错
旧版本【小于ECMAScript5】浏览器使用book.__defineGetter__(“year”, function() { ... }),book.__defineSetter__(“year”,function(newValue) { ... })
定义多个属性:
通过Object.defineProperty描述符一次性定义多个属性
var book = {};
Object.defineProperty(book, {
_year: {
writable: true,
value: 2004
},
edition: {
writable: true,
value: 1
},
year: {
get: function() {
return this._year;
},
set: function(newValue) {
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
}
})
// 定义了2个数据属性(_year和edition)和1个访问器属性(year)
读取属性的特征:
Object.getOwnPropertyDescription方法,可以取得给定属性的描述符。
var descriptor = Object.getOwnPropertyDescriptor(book, “_year”);
alert(descriptor.value) // 2004
alert(descriptor.configurable) //false
alert(typeof descriptor.get) //undefined
var desc2 = Object.getOwnPropertyDescriptor(book, “year”);
alert(desc2.value) // undefined
alert(desc2.enumerable) // false
alert(typeof desc2.get) // “function"
创建对象
Object创建对象产生大量重复代码,使用工厂模式变体实现对象创建。
1、工厂模式
function cratePerson(name, age, job) {
var o = new Object();
o.name = name;
...
o.sayName = function(){
alert(this.name)
}
return o
}
var p1 = createPerson(“aaa”,17,”student")
// 缺点: 不知道对象类型
2、构造函数模式 —— 实例对象如p1、p2自带constructor属性
function Person(name, age, job) {
this.name = name;
…
this.sayName = function(){
alert(this.name)
}
}
var p1 = new Person(“aaa”,17,”student")
// 特点:
// 1、没有显式创建对象
// 2、直接将属性、方法赋给this对象
// 3、没有return语句
//
// 创建实例过程:
// 1、创建新对象
// 2、构造函数作用域赋给新对象(this则指向新对象)
// 3、执行构造函数代码
// 4、返回新对象
1)、构造函数当做函数
与普通函数区别,调用方式不同。使用
new 函数名方式调用的就可以作为构造函数
普通调用方式,属性和方法都被添加到window对象中
2)、构造函数的问题
每个方法需在每个实例重新创建
sayName函数重复创建但是并不指向同一个方法问题
【在外部定义一个函数sayName,让每个实例的this.sayName等于外部sayName方法,这样指针都指向同一个方法,不会出现p1.sayName == p2.sayName // false 这样的问题】
3、原型模式
每个函数都有一个prototype(原型)属性,它是一个指针,指向一个对象,这个对象包含特定类型
所有实例
共享的属性和方法。
function Person() {}
Person.prototype.name = “aaa”;
Person.prototype.age = 18;
Person.prototype.job = “student”;
Person.prototype.sayName = function(){ alert(this.name); };
var p1 = new Person();
p1.sayName(); // “aaa”
var p2 = new Person();
p2.sayName(); // “aaa”
alert(p1.sayName() == p2.sayName); // true
// 构造函数具有相同的属性和方法
// 所有实例共享属性和方法——p1和p2访问的是同一sayName()
1)、理解原型对象
默认情况下,所有原型对象都会自动获取一个construct属性,这个属性指向prototype属性所在函数的指针,
如Person.prototype.constructor指向Person,通过这个constructor我们可以继续为原型对象添加其他属性和方法。
这个联系存在于实例和构造函数的原型之间,而不是实例和构造函数之间,所以,如果需要为对象添加属性和方法需要使用
Person.prototype.属性 = “xxx”
确定对象与实例之间是否存在关系:Person.prototype.
isPrototypeOf
(p1)
返回对象的prototype的值:Object.getProtoTypeOf(p1) // Person.prototype
Object.getProtoTypeOf(p1).name //
“aaa”
代码读取某个对象属性时,会执行搜索,目标是给定的名字的属性。 实例本身【找到则返回,未找到继续】 => 原型【找到返回,未找到,未找到返回undefined】
实例中添加属性之后,如p1.name = “bbb”,则这个属性会屏蔽原型对象中的属性,不会指向原型中
delete删除实例的属性之后,可以恢复指向原型属性
hasOwnProperty
( )判断属性是否存在实例中,p1.
hasOwnProperty(
“name
")
2)、原型与in操作符
单独使用: ”name” in p1 // true p1.hasOwnProperty(“name”) // 重写则为true
for-in使用: for(var o in p1) // 返回每个能够通过对象访问、可枚举的属性。
取得所有可枚举的属性——Object.keys(Person.prototype) // "name,age,job,sayname”
Object.keys(p1) // "实例的属性,重写几个有几个,不读取原型的属性”
Object.getOwnPropertyNames(Person.prototype) //获取所有属性【包含不可枚举的】
3)、更简单的原型语法
每次都写Person.prototype.xxx繁琐
Person.prototype = {
name: “aaa”,
age: 17,
。。。
}
// constructor不指向Person,即p1.constructor == Person //false
Person.prototype = {
constructor: Person,
name: “aaa”,
age: 17,
。。。
}
// 上述情况下[[ Enumerate ]]属性为true,原生constructor属性不可枚举,如果需要改为不可枚举,可以使用Object.defineProperty()
4)、原型的动态性:
使用Person.prototype.xxx给原型添加属性,原型继续不会改变,因为其松散的连接关系
var p1 = new Person();
Person.prototype.sayHi = function() {alert(“Hi”)};
p1.sayHi(); // “Hi” 【不会报错】
使用Person.prototype = {xxx} 重写原型属性和方法 —— 原型被修改为另一个对象,指向不同,切断了之前实例和现有原型的联系,之前实例指向最初的原型
var p1 = new Person();
Person.prototype = {
constructor: Person,
name: “aaa”,
…
sayName: function() {
alert(this.name);
}
}
p1.sayName(); // error【报错】
5)、原生对象的原型
原生引用类型都在其构造函数原型上定义了方法
通过原生对象原型可以取得所有默认方法,还可以随时添加方法。
6)、原型对象的问题
省略了构造函数传递初始化,所有实例默认情况取得相同的属性值。
实例共享原型所有属性,引用类型指向的属性,一个修改,其他都修改
p1.friends.push(“ccc”) // [‘aaa’,’bbb’,’ccc']
此时,p2.friends // [‘aaa’,’bbb’,’ccc’],但是p2并没有ccc这个朋友。
4、组合使用构造函数模式与原型模式
1)、构造函数模式用于定义实例属性
2)、原型模式用于定义方法和共享的属性
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.friends = [“Shelby”,”Court”];
}
Person.prototype = {
constructor: Person,
sayName: function() {
alert(this.name);
}
}
5、动态原型模式
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.friends = [“Shelby”,”Court”];
if(typeof this.sayName != “function") {
Person.prototype.sayName = function(){
alert(this.name);
}
}
}
// 蓝色字体部分,只在sayName不存在的情况下,才会为其添加到原型中。
6、寄生(parasitic)构造函数模式
创建一个构造函数,该函数仅封装创建对象的代码,再返回新创建的对象
用法举例: 自定义数组,添加自定义方法,
function SpecialArray() {
var vals = new Array();
vals.push.apply(vals, arguments);
vals.toPipedString() = function() {
return this.join(‘|’);
}
return vals;
}
var colors = new SpeacialArray(“red”,”blue”,”green”);
alert(colors.toPipedString()); // “red|blue|green"
6、稳妥构造函数模式
稳妥对象(durable object)表示没有公共属性,方法也不引用this的对象。
稳妥对象适合在安全环境中【这些环境禁止使用new和this】,或者在防止数据被其他应用程序【如Mashup程序】改动是使用。
function Person(name, age, job) {
var p = new Person();
p.sayName = function(){
alert(name);
}
return p;
}
只能使用sayName方法访问name成员。
继承
oo语言支持
接口继承
和
实现继承
,接口继承只继承方法签名,实现继承继承实际的方法。ECMAScript只支持实现继承——依靠原型链。
public double calculateAnswer(double wingSpan, int numberOfEngines, double length, double grossTons) {
//do the calculation here
}
上述方法的签名: calculateAnswer(double, int, double, double)
1、原型链
—— 基本思想利用原型让每一个引用类型继承另一个引用类型的属性和方法。
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。
![](https://i-blog.csdnimg.cn/blog_migrate/b54ada6a8de59dfc4e60628f63f16fe2.jpeg)
function Person() {
this.name ="人类";
}
Person.prototype.getPersonName = function(){
return this.name;
};
function Man() {
this.name = "男人";
}
Man.prototype = new Person();
Man.prototype.getManName = function(){
return this.name;
};
var man1 = new Man();
alert(man1.getPersonName()); // “人类”
// Man继承了Person,而继承是通过创建Person实例,并将该实例赋给Man.prototype实现的。
// 本质是重写原型对象,代之以一个新的类型的实例
![](https://i-blog.csdnimg.cn/blog_migrate/010f7fbca0ace2a7c33064c3c26d8066.png)
之前学习读取模式访问某个实例属性时,首先会在实例中搜索该属性。如果没有找到该属性,则会继续搜索实例的原型。
通过原型链实现继承的情况下,搜索就会沿着原型链继续向上。
上图,例如 instance.getSuperValue() => 实例 => SubType.prototype => SuperType.prototype 最后找到。
1)、默认原型: Object
![](https://i-blog.csdnimg.cn/blog_migrate/9f729a18c4fc808b76b4ec259eb6c075.png)
2)、确定原型和实例的关系
instanceof
instance instanceof Object // true
instance instanceof SuperType // true
instance instanceof SubType // true
isPrototypeOf
Object.isPrototypeOf(instance) // true
SuperType.isPrototypeOf(instance) // true
SubType.isPrototypeOf(instance) // true
3)、谨慎地定义方法
重写超类【父类】方法,应该在替换原型的语句之后
不要使用字面量重新创建原型方法 // aaa.prototype = { xxx }
// 添加新方法
SubType.prototype.getSubValue = funcrion(){
return this.subproperty;
}
// 重写父类方法
SubType.prototype.getSuperValue = function(){
return false;
}
sub1.getSuperValue() // false // Sub构造函数指向SubType.prototype,sub1实例指向SubType原型,SubType原型有getSuperValue方法,因此返回false
// 通过SubType实例调用getSuperValue(),由于重写了原型方法,之前的方法将被屏蔽,sub1实例指向SuperType原型
super1.getSuperValue() // true // Super构造函数指向Super.prototype,super1实例指向Super原型,Super原型原有getSuperValue方法,返回true
// 通过SuperType实例调用getSuperValue(),继续调用之前的SuperType原型的方法。
4)、原型链的问题
引用类型值得原型问题,包含引用类型的原型属性会被所有实例共享。
创建子类型实例时,不能向超类构造函数中传值
function SuperType(){
this.colors = [“red”,“blue”,“green”];
}
function SubType(){
}
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push(“black”);
alert(instance1.colors); // “red,blue,green,black”
var instance2 = new SubType();
alert(instance2.colors); // “red,blue,green,black”
// 上述代码中包含一个数组【引用类型值】
2、借用构造函数
在子类型构造函数内部调用超类型构造函数。
function SuperType(){
This.colors = [“red”,”blue”,”green”];
}
function SubType(){
SuperType.call(this); // 继承了SuperType,“借调”了超类的构造函数。
}
var instance1 = new SubType();
instance1.colors.push(“black”);
alert(instance1.colors); // “red,blue,green,black”
var instance2 = new SubType();
alert(instacne2.colors) // “red,blue,green”
1)、传递参数——可以在子类构造函数中向超类构造函数传递参数。
2)、借用构造函数的问题——没有函数复用,因为方法都在构造函数中定义
3、组合继承
使用
原型链实现对
原型属性和方法的继承,
通过借用
构造函数来实现对
实例属性的继承。
4、原型式继承
function object(o){
function F(){}
F.prototype = o; // 浅复制
return new F();
}
// 引用类型共享
Object.create(o, externalParam)
// o, 效果同上
// externalParam【可选】指定任何属性将覆盖原型对象上的任何同名属性。
5、寄生式继承
创建一个仅用于封装继承过程的函数。该函数内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。
function createAnother(original){
var clone = object(original); // 调用object函数创建一个新对象,作为新对象的基础对象
clone.sayHei = function(){ // 以某种方式增强这个新对象
alert(“Hi”);
};
return clone; // 返回这个对象
}
// 缺点:不能做到函数复用
6、寄生组合式继承
通过借用构造函数来继承属性【SuperType.call(this,name)】,通过原型链的混成形式继承方法。【inheritProroType(subType, superType)】
本质上,就是使用寄生式继承来继承超类型的原型,再将结果指定给子类型的原型。
function inheritProtoType(subType, superType){
var prototype = object(superType.prototyope); // 创建对象,实际是创建超类型的原型副本
prototype.constructor = subType; // 增强对象,即让超类型原型副本指向子类型构造函数,弥补因重写原型失去默认的constructor属性
subType.prototype = prototype; // 指定对象,让经过增强的原型副本指向子类型的原型
}