JavaScript高级程序设计:学习笔记3--面向对象设计和函数

1. 面向对象的程序设计

1. 理解对象

    每个对象都是基于引用类型创建.一般创建对象的方式如下:

var person = {
	name : "Nicholas",
	age : 29,
	job : "Software Engineer",
	
	sayName : function(){
		alert(this.name);
	}
};

1. 属性类型

数据属性:包含一个数据值的位置,在此位置上可以读取和写入值.它具有以下四个特性:

[[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性.

[[Enumerable]]:表示能否通过for-in循环返回属性.

[[Writable]]:表示能否修改属性的值.

[[Value]]:包含这个属性的值.

    而我们可以通过Object.defineProperty()方法来修改属性默认的特性.它接收三个参数:属性所在的对象,属性的名字和一个描述符对象.而描述符对象必须是configurable,enumerable,writable和value.

var person = {};
Object.defineProperty(person, "name", {
	writable : false,		//说明不可写
	value : "Nicholas"
});

print(person.name);		//输出:Nicholas
person.name = "voler";	//不可更改
print(person.name);		//输出:Nicholas

    特性 Configurable很特殊,一旦修改为false,则无法再次变为可配置(即无法再次设定为true了):

<html><head><meta charset="utf-8" /> 
<script type="text/javascript">
function print(str) {
	document.write(str + "<br />");
}

var person = {};
Object.defineProperty(person, "name", {
	writable : true,		//说明可写
	configurable : false,
	value : "Nicholas"
});

//writable属性
print(person.name);		//输出:Nicholas
person.name = "voler";	//可更改
print(person.name);		//输出:voler

//configurable属性
delete person.name;		//不可删除
print(person.name);		//输出:voler
try {
	Object.defineProperty(person, "name", {
	configurable : true,
	value : "Nicholas"
	});
} catch(err) {
	print(err);		//输出:TypeError: Cannot redefine property: name
}

</script></head></html>

访问器属性:不包含数据值,但是通过get/set函数来操作数据,通过设定属性名来存储数据.它具有[[Configurable]],[[Enumerable]],[[Get]]:读取数据,[[Set]]:写入数据.设置一个属性导致其他属性发生变化.

var book = {
	_year : 2004,
	edition : 1
};

//通过设定year属性名来操作对象
Object.defineProperty(book, "year",{
	get : function() {
		print("3");
		return this._year;
	},
	set : function(newValue) {
		print("1");
		if (newValue > 2004) {
			this._year = newValue;
			this.edition += newValue - 2004;
		}
	}
});

//对属性year的赋值,实际上会调用set函数
book.year = 2005;
print("2");
//而读取year属性,实际上会调用get函数--通过set/get函数,可以达到数据的私有化
print(book.year);
    浏览器显示如下:

JavaScript高级程序设计:学习笔记3--面向对象设计和函数_第1张图片

2. 定义多个属性

    通过Object.defineProperties()方法达到.

<script type="text/javascript">
function print(str) {
	document.write(str + "<br />");
}

var book = {};

Object.defineProperties(book, {
	_year: {
		value: 2004,
		writable: true	//这里必须设置writable属性:否则默认无法进行修改,在定义多个属性的情况下
	},
	edition: {
		value: 1
	},
	year: {
		get: function() {
			print("get");
			return this._year;
		},
		set: function(newValue) {
			print("set");
			if (newValue > 2004) {
				this._year = newValue;
				this.edition += newValue - 2004;
			}
		}
	}
});

book.year = 2006;
print(book.year);	//2006
</script>

3. 读取属性的特性

    通过Object.getOwnPropertyDescriptor()方法达到.

<html><head><meta charset="utf-8" /> 
<script type="text/javascript">
function print(str) {
	document.write(str + "<br />");
}

var book = {};

Object.defineProperties(book, {
	_year : {
		value : 2004
	},
	edition : {
		value : 1
	},
	year : {
		get : function() {
			return this._year;
		},
		set : function(newValue) {
			if (newValue > 2004) {
				this._year = newValue;
				this.edition += newValue - 2004;
			}
		}
	}
});
debugger;
var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
print(descriptor.value);			//2004
print(descriptor.configurable);		//false
print(typeof descriptor.get);		//undefined

//对于访问器属性,是没有value的,所以不能通过descriptor.get来得到值
var descriptor = Object.getOwnPropertyDescriptor(book, "year");
print(descriptor.value);			//undefined
print(descriptor.enumerable);		//false
print(typeof descriptor.get);		//function
</script></head></html>

2. 创建对象

1. 工厂模式

    用函数来封装以特定接口创建对象的细节:

function createPerson(name, age, job) {
	var o = new Object();
	o.name = name;
	o.age = age;
	o.job = job;
	o.sayName = function() {
		print(this.name);
	};
	return o;
}

var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");

2. 构造函数模式

    创建自定义的构造函数,从而定义自定义对象类型的属性和方法:

//这里this对应new出来的对象
function Person(name, age, job) {
	this.name = name;
	this.age = age;
	this.job = job;
	this.sayName = function() {
		print(this.name);
	}
}

var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
    与之前的工厂模式不同之处在于:

1. 没有显式的创建对象.

2. 直接将属性和方法赋给了this对象.

3. 没有return语句.

    而创建Person的新实例,需要使用new操作符.当调用new的时候,会经历以下四个步骤:

1. 创建一个新对象.

2. 将构造函数的作用域赋给新对象(因此this就指向了这个新对象)

3. 执行构造函数中的代码(为这个新对象添加属性)

4. 返回新对象.

    而创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型,这正是构造函数胜过工厂模式的地方.

print(person1 instanceof Object);	//true
print(person2 instanceof Person);	//true
print(person1 instanceof Object);	//true
print(person2 instanceof Person);	//true

1. 将构造函数当作函数

    构造函数和一般函数没什么区别.函数只要用new操作符来调用,就是构造函数,否则就是一般函数:

//这里this对应new出来的对象
function Person(name, age, job) {
	this.name = name;
	this.age = age;
	this.job = job;
	this.sayName = function() {
		print(this.name);
	}
}

//this--->person
var person = new Person("Nicholas", 29, "Software Engineer");
person.sayName();	//Nicholas

//this--->window
Person("Greg", 27, "Doctor");
window.sayName();	//Greg
sayName();			//Greg

var o = new Object();
Person.call(o, "Kristen", 25, "Nurse");
o.sayName();		//Kristen

2. 构造函数的问题

    每个方法都要在每个实例上重新创建一遍.

3. 原型模式

    我们创建的每个函数都有一个prototype(原型属性),这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法.

function Person(){}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Enginner";
Person.prototype.sayName = function(){
	print(this.name);
};

var person1 = new Person();
person1.sayName();	//Nicholas

var person2 = new Person();
person2.sayName();	//Nicholas

    这里在firebug上有一个很有启发性的调试:

JavaScript高级程序设计:学习笔记3--面向对象设计和函数_第2张图片

1. 理解原型对象

    无论什么时候,只要创建一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象.在默认情况下,所有原型对象都会自动获得一个constructor属性,这个属性包含一个指向prototype属性所在函数的指针.

person1.constructor.prototype === Person.prototype
person2.constructor === person1.constructor

JavaScript高级程序设计:学习笔记3--面向对象设计和函数_第3张图片

    而isPrototypeOf()和getPrototypeOf()方法用来确定原型对象和实例之间的关系:

print(Person.prototype.isPrototypeOf(person1));	//true
print(Person.prototype.isPrototypeOf(person2));	//true
print(Object.getPrototypeOf(person1) == Person.prototype);	//true
print(Object.getPrototypeOf(person1).name);		//Nicholas
    虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值.如果我们向实例中添加一个属性,而该属性与实例原型中的一个属性同名,那我们会屏蔽原属性.我们可以用delete来删除掉添加的属性.
<html><head><meta charset="utf-8" /> 
<script type="text/javascript">
function print(str) {
	document.write(str + "<br />");
}

function Person(){}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Enginner";
Person.prototype.sayName = function(){
	print(this.name);
};

var person1 = new Person();
var person2 = new Person();

//屏蔽掉原属性
person1.name = "Greg";
print(person1.name);	//Greg
print(person2.name);	//Nicholas

//删除添加的属性
delete person1.name;
print(person1.name);	//Nicholas

</script></head></html>
关系如下:

JavaScript高级程序设计:学习笔记3--面向对象设计和函数_第4张图片

2. 原型与in操作符

    in操作符会在通过对象能够访问给定属性时返回true.

<html><head><meta charset="utf-8" /> 
<script type="text/javascript">
function print(str) {
	document.write(str + "<br />");
}

//hasOwnProperty只有在实例中才返回true
function hasPrototypeProperty(object, name) {
	return !object.hasOwnProperty(name) && (name in object);
}

function Person(){}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Enginner";
Person.prototype.sayName = function(){
	print(this.name);
};

//函数原型中定义name
var person = new Person();
print(hasPrototypeProperty(person, "name"));	//true

//函数实例中定义name
person.name = "Greg";
print(hasPrototypeProperty(person, "name"));	//false

</script></head></html>
    而我们可以通过Object.keys()方法来枚举实例属性:
function Person(){}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Enginner";
Person.prototype.sayName = function(){
	print(this.name);
};

var keys = Object.keys(Person.prototype);
print(keys);	//name,age,job,sayName

3. 更简单的原型语法

function Person(){}

Person.prototype = {
	name: "Nicholas",
	age: 29,
	job: "Software Engineer",
	sayName: function() {
		print(this.name);
	}
};


var person1 = new Person();
    这里要注意一点是:constructor属性不再指向Person,而是指向Object.是因为赋值会断开原型链,所以constructor指向新的对象Person.prototype,而此对象为Object类型.

JavaScript高级程序设计:学习笔记3--面向对象设计和函数_第5张图片

    而更具体的解释如下:


<script type="text/javascript">
function print(str) {
	document.write(str + "<br />");
}

function Person(){}

Person.prototype = {
	name: "Nicholas",
	age: 29,
	job: "Software Engineer",
	sayName: function() {
		print(this.name);
	}
};

var person1 = new Person();

function Func(){}
Func.prototype.name = "lcj";
var person2 = new Func();
</script>
    firebug的调试如下:


JavaScript高级程序设计:学习笔记3--面向对象设计和函数_第6张图片

    这里对Person.prototype = ***;的赋值操作,实际上可以理解为继承:


<script type="text/javascript">
function print(str) {
	document.write(str + "<br />");
}

function Person(){}

function tempObject(){};
tempObject.prototype.name = "Nicholas";
tempObject.prototype.age = 29;
tempObject.prototype.job = "Software Engineer";
tempObject.prototype.sayName = function() {
	print(this.name);
};

Person.prototype = new tempObject();

var person1 = new Person();

</script>
    这里,tempObject模拟了所有对象的原始父类:Object对象.firebug调试如下:


JavaScript高级程序设计:学习笔记3--面向对象设计和函数_第7张图片

4. 原型的动态性

    实例和原型之间通过prototype指针连接.所以原型的任何修改会时时反应到实例上:

var friend = new Person();
Person.prototype.sayHi = function(){
	print("Hi");
};
friend.sayHi();	//输出:Hi
    但是,如果重写原型对象.由于断开了实例和原型之间的关联,则以下代码会出错:
function Person(){}

var friend = new Person();

Person.prototype = {
	constructor : Person,
	name : "Nicholas",
	age : 29,
	job : "Software Enginner",
	sayName : function(){
		print(this.name);
	}
};

friend.sayName();		//error
JavaScript高级程序设计:学习笔记3--面向对象设计和函数_第8张图片

5. 原型对象的问题

    共享性:

function Person(){}

var friend = new Person();

Person.prototype = {
	constructor : Person,
	name : "Nicholas",
	age : 29,
	job : "Software Enginner",
	friend : ["Shelby", "Court"],
	sayName : function(){
		print(this.name);
	}
};

var person1 = new Person();
var person2 = new Person();

person1.friend.push("Van");
print(person1.friend);	//Shelby,Court,Van
print(person2.friend);	//Shelby,Court,Van

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

    构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性.结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用.

<html><head><meta charset="utf-8" /> 
<script type="text/javascript">
function print(str) {
	document.write(str + "<br />");
}

function Person(name, age, job) {
	this.name = name;
	this.age = age;
	this.job = job;
	this.friends = ["Shelly", "Court"];
}

Person.prototype = {
	constructor : Person,
	sayName : function() {
		print(this.name);
	}
}

var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

person1.friends.push("Van");
print(person1.friends);		//Shelly,Court,Van
print(person2.friends);		//Shelly,Court
print(person1.sayName == person2.sayName);	//true

</script></head></html>

5. 动态原型模型

    动态原型模型把所有信息都封装在了构造函数中,而通过在构造函数中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点.

function Person(name, age, job) {
	this.name = name;
	this.age = age;
	this.job = job;
	this.friends = ["Shelby", "Court"];
	
	//只有在sayName不存在的情况下,执行sayName的初始化
	if (typeof this.sayName != "function") {
		Person.prototype.sayName = function() {
			alert(this.name);
		};
	}
}

6. 寄生构造函数模型

    思想为创建一个函数,作用仅仅是封装创建对象的代码,然后返回新创建的对象--除了new之外,和工厂函数没什么区别.

function Person(name, age, job) {
	var o = new Object();
	o.name = name;
	o.age = age;
	o.job = job;
	o.sayName = function() {
		alert(this.name);
	};
	return o;
}

var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = Person("Nicholas", 29, "Software Engineer");
    但是这里有一个疑问是:person1 === person2明显为false,但是它们不同的地方在哪里?

7. 稳妥构造函数模式

    稳妥对象,指的是没有公共属性,而且其方法也不引用this的对象:

function Person(name, age, job) {
	var o = new Object();
	o.sayName = function() {
		alert(name);
	};
	
	return o;
}
    这里直接对传入的参数进行操作,所以没有公共属性,也没不引用this对象.

3. 继承

1. 原型链

    构造函数,原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针.我们让原型对象包含一个指向构造函数的指针,而实例包含一个指向原型对象的内部指针,这就是原型链的继承方式.

function SuperType() {
	this.property = true;
}
SuperType.prototype.getSuperValue = function(){
	return this.property;
};

function SubType() {
	this.subproperty = false;
}

SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function() {
	return this.subproperty;
};

var instance = new SubType();
print(instance.getSuperValue());	//true
//这时候,构造函数为SuperType,而非SubType
print(instance.constructor);		//function SuperType() { this.property = true; }
JavaScript高级程序设计:学习笔记3--面向对象设计和函数_第9张图片

    而完整的继承图应该包含Object:

JavaScript高级程序设计:学习笔记3--面向对象设计和函数_第10张图片

    而我们可以通过instanceof操作符和isPrototypeOf方法来测试原型和实例之间的关系:

//通过instanceof来确定原型和实例的关系
print(instance instanceof Object);	//true
print(instance instanceof SuperType);	//true
print(instance instanceof SubType);	//true

//通过isPrototypeOf来确定原型和实例的关系
print(Object.prototype.isPrototypeOf(instance));	//true
print(SuperType.prototype.isPrototypeOf(instance));	//true
print(SubType.prototype.isPrototypeOf(instance));	//true
    我们需要谨慎的定义方法:给原型(这里为继承的超类型SuperType)添加的方法必须在替换原型的语句(SubType.prototype = new SuperType)的后面,因为原型链为:SubType的实例指向SubType.prototype原型,而 SubType.prototype原型包含一个SuperType的实例(new SuperType),而此实例又指向SuperType的原型(SuperType.prototype).所以,为定义超类(SuperType)的实例之前,为其添加方法是错误的:(但是我觉得这里有点莫名其妙!):

function SuperType() {
	this.property = true;
}
SuperType.prototype.getSuperValue = function(){
	return this.property;
};

function SubType() {
	this.subproperty = false;
}

SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function() {
	return this.subproperty;
};

//添加新方法
SubType.prototype.getSubValue = function() {
	return this.subproperty;
};

var instance = new SubType();

//重写超类中的方法
SubType.prototype.getSuperValue = function() {
	return "hello";
};

var instance = new SubType();
print(instance.getSubValue());		//false
print(instance.getSuperValue());	//hello

    之前自己对函数有一定的认知误区,目前总结如下:

1. 只有对象才能添加其属性和方法.

2. 函数本身是一个对象,函数名仅仅指向此函数,并不能通过函数名来添加属性和方法(即函数名并不是一个对象)

3. 要给函数添加方法,要么通过具有对象的函数原型(FUNC.prototype),要么对new FUNC()出来的对象进行添加.所以以下代码很有参考作用:

function SuperType() {
	this.property = true;
	this.show1 = function() {
		print("show1 function");
	};
}

//不能通过SuperType函数名来添加方法
SuperType.show2 = function() {
	print("show2 function");
};

var superInstance = new SuperType();
superInstance.show3 = function() {
	print("show3 function");
};

superInstance.show1();
//superInstance.show2();	//-此处报异常,show2非函数
superInstance.show3();

    但是,原型链有以下两个问题:1.共享机制(跟模型的原理一致) 

function SuperType() {
	this.colors = ["red", "blue", "green"];
}

function SubType() {
}

SubType.prototype = new SuperType();

var instance1 = new SubType();
instance1.colors.push("black");	
print(instance1.colors);		//red,blue,green,black

var instance2 = new SubType();
print(instance2.colors);		//red,blue,green,black
    第二个问题是:不能向超类中传递参数. 

2, 借用构造函数

    在子类型构造函数的内部调用超类型构造函数(但这种方法谈不上复用,因此很少使用)

function SuperType(name) {
	this.colors = ["red", "blue", "green"];
	this.name = name;
	
}

function SubType() {
	SuperType.call(this, "Nicholas");
	this.age = 29;
}

SubType.prototype = new SuperType();//此句没有任何的作用,删除也可以

//不共享原型,每个实例均有自己的原型副本
var instance1 = new SubType();
instance1.colors.push("black");	
print(instance1.colors);		//red,blue,green,black

var instance2 = new SubType();
print(instance2.colors);		//red,blue,green

print(instance1.name);			//Nicholas
print(instance1.age);			//29

3. 组合继承

    组合继承,也叫做伪经典继承,指的是将原型链和借用构造函数的技术组合在一起.其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承.这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性:

function SuperType(name) {
	this.colors = ["red", "blue", "green"];
	this.name = name;
}

SuperType.prototype.sayName = function() {
	print(this.name);
};

function SubType(name, age) {
	//继承属性
	SuperType.call(this, name);
	this.age = age;
}

//继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
	print(this.age);
};

var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
print(instance1.colors);		//red,blue,green,black
instance1.sayName();			//Nicholas
instance1.sayAge();				//29

var instance2 = new SubType("Greg", 27);	
print(instance2.colors);		//red,blue,green
instance2.sayName();			//Greg
instance2.sayAge();				//27

4. 原型式继承

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

function object(o) {
	function F(){}
	F.prototype = o;
	return new F();
}

例子如下:

function object(o) {
	function F(){}
	F.prototype = o;
	return new F();
}

var person = {
	name: "Nicholas",
	friends: ["Shelby", "Court", "Van"]
};

var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");

print(person.friends);	//Shelby,Court,Van,Rob,Barbie
    而ECMAScript5新增Object.create()方法,接收两个参数:一个用作新对象原型的对象和一个为新对象定义额外属性的对象.


var anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

    Object.create()方法的第二个参数与Object.defineProperties()方法的第二个参数格式相同:每个属性都是通过自己的描述符定义的.以这种方式指定的任何属性都会覆盖原型对象上的同名属性:

var person = {
	name: "Nicholas",
	friends: ["Shelby", "Court", "Van"]
};

//通过此方法会重新定义name的属性,所以如果name中不编写value: "true",则调用anotherPerson.name为undefined
var anotherPerson = Object.create(person, {
	name: {
		value: "true",
		writable: false
	}
});
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");

print(person.friends);	//Shelby,Court,Van,Rob,Barbie
print(anotherPerson.name);	//true



5. 寄生式继承

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

function object(o) {
	function F(){}
	F.prototype = o;
	return new F();
}

function createAnother(original) {
	var clone = object(original);
	clone.sayHi = function() {
		print("Hi");
	};
	
	return clone;
}

var person = {
	name: "Nicholas",
	friends: ["Shelby", "Court", "Van"]
};

var anotherPerson = createAnother(person);
anotherPerson.sayHi();


2. 函数表达式

1, 函数的两种声明方式

function functionName(arg0, arg1, arg2) {
	//函数体
}

var functionName = function(arg0, arg1, arg2) {
	//函数体
};

2. 递归

    经典的但是错误的递归函数(在javascript是错误的):

function factorial(num) {
	if (num <= 1) {
		return 1;
	} else {
		return num * factorial(num - 1);
	}
}

var anotherFactorial = factorial;
factorial = null;
print(anotherFactorial(4));	//出错!!
    而我们应该使用arguments.callee来指向正在执行的函数的指针.
function factorial(num) {
	if (num <= 1) {
		return 1;
	} else {
		return num * arguments.callee(num - 1);
	}
}

var anotherFactorial = factorial;
factorial = null;
print(anotherFactorial(4));	//24
    而我们通过函数表达式可以优化设计:
var factorial = (function f(num) {
	if (num <= 1) {
		return 1;
	} else {
		return num * f(num - 1);
	} 
});

print(factorial(4));		//24
var anotherFactorial = factorial;
factorial = null;
print(anotherFactorial(4));	//24

3. 闭包

    闭包指的是有权访问另一个函数作用域中的变量的函数.创建闭包的常用方式,就是在一个函数内部创建另一个函数:

function createComparisonFunction(propertyName) {
	return function(object1, object2) {
		var value1 = object1[propertyName];
		var value2 = object2[propertyName];
		
		if (value1 < value2) {
			return -1;
		} else if (value1 > value2) {
			return 1;
		} else {
			return 0;
		}
	};
}

var compare = createComparisonFunction("name");
var result = compare({name : "Nicholas"}, {name : "Greg"});
print(result);	//1
    作用域链如下:

JavaScript高级程序设计:学习笔记3--面向对象设计和函数_第11张图片

    从上图可以看出,闭包的对象并不会被销毁.闭包的对象会一直存在于声明它的实例中(在compare实例),直到手动设置为null.

//创建函数
var compare = createComparisonFunction("name");
//调用函数
var result = compare({name : "Nicholas"}, {name : "Greg"});
//解除对匿名函数的引用
compare = null;
JavaScript高级程序设计:学习笔记3--面向对象设计和函数_第12张图片

    作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值.闭包所保存的是整个变量对象,而不是某个特殊的变量.

function createFunctions() {
	var result = new Array();
	
	for (var i = 0; i < 10; i++) {
		result[i] = function() {
			return i;
		};
	}
	
	return result;
}

var result = createFunctions();
for (var i = 0; i < result.length; i++) {
	print(result[i]());	//输出10 10 10 10 10 10 10 10 10 10 
}

    而我们可以强制传递参数进行,让函数符合预期的结果:

function createFunctions() {
	var result = new Array();
	
	for (var i = 0; i < 10; i++) {
		result[i] = function(num) {
			return num;
		}(i);
	}
	
	return result;
}

var result = createFunctions();
for (var i = 0; i < result.length; i++) {
	print(result[i]);	//输出0 1 2 3 4 5 6 7 8 9
}

    而匿名函数的执行环境具有全局性:

var name = "The Window";

var object = {
	name : "My Object",
	getNameFunc : function() {
		return function() {
			//这里this是window
			return this.name;
		};
	}
};

print(object.getNameFunc()());	//The Window

    我们可以修改如下:

var name = "The Window";

var object = {
	name : "My Object",
	getNameFunc : function() {
		//这里this是object
		var that = this;
		return function() {
			return this.name + "---" + that.name;
		};
	}
};

print(object.getNameFunc()());	//The Window---My Object

3. 模仿块级作用域

    函数具有作用域,并且多次声明一个变量并不起作用:javascript对第一个声明有效,后续声明均视而不见.

function outputNumbers(count) {
	for (var i = 0; i < count; i++) {
		;
	}
	var i;	//再次声明忽略不计
	print(i);
}

outputNumbers(10);	//10

    而我们因此可以使用此特性编写私有作用域:

(function(){
	//这里是块级作用域
})();

4. 私有变量

    javascript本身并没有私有变量一说.但是由于存在函数作用域,则在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量.私有变量包括函数参数,局部变量和在函数内部定义的其他函数:

function Person(name) {
	this.getName = function() {
		return name;
	};
	this.setName = function(value) {
		name = value;
	};
}

var person = new Person("Nicholas");
print(person.getName());	//Nicholas
person.setName("Greg");
print(person.getName());	//Greg
    我们也可以定义静态私有变量:
(function(){
	var name = "";
	
	//未使用var,故为全局变量
	Person = function(value){
		name = value;
	};
	Person.prototype.getName = function(){
		return name;
	};
	Person.prototype.setName = function(value){
		name = value;
	};
})();

var person1 = new Person("Nicholas");
print(person1.getName());	//Nicholas
person1.setName("Greg");
print(person1.getName());	//Greg

var person2 = new Person("Michael");
print(person1.getName());	//Michael
print(person2.getName());	//Michael


你可能感兴趣的:(JavaScript高级程序设计:学习笔记3--面向对象设计和函数)