函数的一些内部属性
1.arguments
arguments保存了函数的参数,是一个类数组对象。该对象有一个属性callee,指向arguments所属的函数对象,下面是一个阶乘的例子:
function factorial(num){
if (num <= 1) {
return 1;
} else {
return num * arguments.callee(num-1)
}
}
var trueFactorial = factorial;
factorial = function(){
return 0;
};
alert(trueFactorial(5)); //120
alert(factorial(5)); //0
2.this指针
this指针指向了调用该函数的上下文对象
3.caller
caller返回一个对函数的引用,该函数调用了当前函数。如果函数是由顶层调用的,那么 caller 包含的就是 null 。
3.length
length属性返回函数希望接受的命名参数的个数。
4.prototype属性
prototype下面有两个属性:apply和call。aplly和call的目的相同:都是为了在特定的作用域中调用函数,实际上等于设置函数体内this的值。但用法稍微有些区别。
apply接受的参数是:在其中运行的作用域,和一个参数数组(可以是一个Array,也可以是arguments对象)
call接受的参数:第一个也是this,后面是直接传进来的列举出来的参数。
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
sayColor(); //red
sayColor.call(this); //red
sayColor.call(window); //red
sayColor.call(o); //blue
bind()创建了一个函数,当这个函数在被调用的时候,它的 this 关键词会被设置成被传入的值(这里指调用bind()时传入的参数)。bind()的一种实现方式如下:
Function.prototype.bind = function (scope) {
var fn = this;
return function () {
return fn.apply(scope);
};
}
##JavaScript面向对象程序设计
1.Object与属性特性
对象的属性里含有各种特性:可以通过Object对象提供的API来进行设置。下面是一些常用的特性(attribute)
*
[[Value]]: 属性的数据值
*
[[Writable]]:是否能够修改属性的数据值
*
[[get]]:读取属性时调用的函数
*
[[set]]:写入属性时调用的函数
可以使用Object.defineProperty(),Object.defineProperties()和Object.getOwnPropertyDescriptor()方法对这些特性进行修改及获取。
2.创建对象的模式
2.1使用Object对象
var person=new Object();
person.name="fsk";
person.age=25;
person.info=function(){
alert("name: "+this.name +" "+"age: "+this.age);
};
2.2字面量创建
var person={
name:"fsk",
age:25,
info:function(){
alert("name: "+this.name +" "+"age: "+this.age);
}
};
2.3工厂模式
function createPerson(name,age)
{
var o=new Object();
o.name=name;
o.age=age;
o.info=function(){
alert("name: "+this.name +" "+"age: "+this.age);
}
return o;
}
//产生一个对象。
var person=createPerson("fsk",25);
2.4构造函数模式
function Person(name, age) {
this.name = name;
this.age = age;
this.info = function() {
alert("name: " + this.name + " " + "age: " + this.age);
};
}
var person = new Person("fsk", 25);
这种方式构造对象经历的步骤如下:
*
创建一个新对象;
*
将构造函数的作用域赋给新对象(this就指向了这个新对象);
*
执行构造函数中的代码(为这个新对象添加属性);
*
返回新对象;
这种模式相对于2.3的好处在于,可以识别对象的类型。通过构造函数创建的新实例都有一个constructor属性,该属性指向Person。instanceof操作符也可以判别构造出对象的类型。
构造函数也可以直接作为函数来使用
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person = new Person("Nicholas", 29, "Software Engineer");
person.sayName(); //"Nicholas"
Person("Greg", 27, "Doctor"); //adds to window
window.sayName(); //"Greg"
var o = new Object();
Person.call(o, "Kristen", 25, "Nurse");
o.sayName(); //"Kristen"
使用构造函数的主要问题,就是在每一个方法都要在每个实例上创建一遍
2.5原型模式
创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以有特定类型的所有实例类型共享的属性和方法。
在默认情况下,所有原型对象都会自动获得一个constructor属性,这个属性包含一个指向prototype属性所在函数的指针。Person.prototype.constructor指向Person自身。
通过构造函数创建的实例包含一个指针,指向构造函数的原型对象。这个指针的名字叫[[Prototype]]。这个指针对于脚本是不可见的。原型对象的结构如下图所示
!
[](file:///C:/Users/由美/AppData/Local/Temp/calibre_oihsxy/bjpgnj_ebook_iter/OEBPS/images/f185-01.jpg)
对原型对象所做的任何修改都会立即从实例上反应出来,即使是先创建了实例后修改原型也是这样:
function Person(){
}
var friend = new Person();
Person.prototype = {
constructor: Person,
name : "Nicholas",
age : 29,
job : "Software Engineer",
sayName : function () {
alert(this.name);
}
};
friend.sayName(); //error
重写原型对象切断了现有原型与之前已经存在的对象实例的联系,它们引用的仍然是最初的原型。
!
[](file:///C:/Users/由美/AppData/Local/Temp/calibre_oihsxy/bjpgnj_ebook_iter/OEBPS/images/f195-01.jpg)
原型模式缺点:它省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。原型中所有属性是被很多实例共享的,这种对于函数非常适合,对那些包含基本值的属性到也说得过去。可以通过在实例上添加一个同名属性,可以隐藏原型中的对应属性。然而,对于包含引用类型值的属性,它是指向同一个指针的。
2.6组合使用构造函数模式和原型模式
此组合模式是创建自定义类型最常见方式,构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype = {
constructor: Person,
info: function() {
alert("name: " + this.name + " " + "age: " + this.age);
}
}
var person = new Person("singsong", 23);
person.info();
}
2.7动态原型模式
动态原型模式把所有信息都封装在构造函数中。通过在构造函数中初始化原型,有保持了同时使用构造函数和原型的优点。
function Person(name, age) {
this.name = name;
this.age = age;
if (typeof this.info != "function") {
Person.prototype.info = function() {
alert("name: " + this.name + " " + "age: " + this.age);
}
};
}
var person = new Person("singsong", 23);
person.info();
使用动态原型模式时,不能使用对象字面量重写原型,如果在已经创建了实例的情况下重写原型,那么就会切断现有实例与新原型之间的联系。
2.8寄生构造函数模式
这种模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后返回新创建的对象。
function Person(name, age) {
var o = new Object();
o.name = name;
o.age = age;
o.info = function() {
alert("name: " + this.name + " " + "age: " + this.age);
};
return o;
}
var person = new Person("singsong", 23);
person.info();
除了使用new操作符并把使用的包装函数叫做构造函数之外,这个模式跟工厂模式其实是一模一样。构造函数在不返回值得情况下,默认会返回新对象实例。而通过在构造函数的末尾添加一个return语句,可以重写调用构造函数时返回的值。
这个模式可以在特殊的情况下用来为对象创建构造函数。如由于不能直接修改原生对象的构造函数,可以借助此模式,重创建构造函数来添加额外方法。
注意:寄生构造函数模式返回的对象与构造函数或者与构造函数的原型属性之间没有关系;也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么不同。因此,就不能依赖instanceof操作符来确定对象类型了。由于存在此上述问题,建议在可以使用其他模式的情况下,不要使用这种模式。
3.继承的实质
js将原型链作为实现继承的主要方法。如以下代码:
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//inherit from SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true
在上述代码中,subType的prototype作为了Supertype的实例出现。于是subType.prototype中的constructor属性消失了,它就像一个实例一样拥有了[[Prototype]]属性,该属性指向了SuperType.prototype.
!
[](file:///C:/Users/由美/AppData/Local/Temp/calibre_oihsxy/bjpgnj_ebook_iter/OEBPS/images/f203-01.jpg)
因为所有的函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。这也正是所有自定义类型都继承toString(),valueOf()等默认方法的原因
!
[](file:///C:/Users/由美/AppData/Local/Temp/calibre_oihsxy/bjpgnj_ebook_iter/OEBPS/images/f204-01.jpg)
##闭包
闭包是指有权访问另一个函数作用域中变量的函数。如下列代码:
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;
}
};
}
内部函数的前两行访问了外部函数的变量propertyName,即使这个函数返回了,而且在其它的函数进行调用,它仍然可以访问变量propertyName。因为内部函数的作用域链包含了createComparisonFunction的作用域链。
createComparisonFunction()执行完毕后,其活动对象也不会销毁,因为匿名函数的作用域链仍然在引用这个活动对象。被销毁是它的作用域链,但是活动对象仍然在内存中,直到匿名函数被销毁后,createComparisonFunction()的活动对象才会被销毁。
!
[](file:///C:/Users/由美/AppData/Local/Temp/calibre_oihsxy/bjpgnj_ebook_iter/OEBPS/images/f223-01.jpg)
在闭包中传递this对象也可能会导致一些问题。下列代码执行时:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()()); //"My Object"
每个函数在被调用时都会获得两个特殊变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问到外部函数中的这两个变量。