在开始这篇文章的主题之前,我先把之前对匿名函数的了解整理一下。
1. 匿名函数是一种没有名字的函数表达式。
2. 匿名函数有时候可以使代码变得更加简洁。
3. 在解析你的剩余代码之前声明式函数就已经被定义好了。
4. 函数表达式被解析的时候与普通代码一样级别,哪儿出现了函数表达式,解析到那里了才会去定义函数。
5. 我们可以将一个函数表达式(其实我觉得跟匿名函数一模一样)作为参数传给另外一个函数,也可以从函数中返回一个函数表达式。
6. function expression 实际上就是一个 function 的引用,所以我们在可以使用函数的地方使用 function expression.
7. Nested functions (嵌套函数)是一些被定义在函数内部的函数。
8. 嵌套的函数有自己的局部范围,比如嵌套函数中的变量就是局部变量。
9. Lexical scope 说明我们可以通过阅读我们的代码来决定变量的作用范围。
10. 要bind嵌套函数中一个变量的值,首先,在嵌套的函数中看有无定义,有则使用,如果没有就往父函数上找,直到全局范围为止。
11. 闭包是一个带着引用环境(reference function)的函数。
12. 在闭包被创建时,闭包就捕获到了范围内变量的值。
13. 函数体中的Free variables (自由变量)指的是定义在当前函数之外,却在当前函数中被使用的变量。
14. 如果在其他的场景中执行了闭包函数,那么该函数中变量的值取决于闭包所携带的环境。
15. 闭包经常被用于为事件处理捕获状态信息。
创建一个Object
我只前接触的创建一个对象都是使用var = {属性:属性值}这类的方法,使用这种方法创建对象放在小项目中倒是可以,可一旦遇到了比较大型的项目作业时,仍使用这种方法来创建对象就显得有点蹩脚了,毕竟这种方法很难实现我们创建许多个属性相同,类似这种形式的对象。亦或者说,使用这样的literal方法来创建许多属性相同的对象实在是需要写出许多相同的代码,使得代码变得不再简洁,并且容易遇到代码错误,拉下了开发的效率。
下面隆重介绍 <b>Object Constructor</b>
使用这种对象构造器可以轻松解决上述代码重复、不简练的问题。
创建一个constructor
<b>示例一:</b>
var duck = {
type: "redheaded",
canFly: true
}
将上面literal式创建对象的方法改写为构造器的形式, 示例如下:
function Duck (type, canFly) {
this.type = type; // 类型
this.canFly = canFly; // 能不能飞
}
构造器的使用
如上所示,我创建了一个对象构造器Duck,那么接下来的问题是:
<b>我要如何来使用构造器呢?</b>
通过这个构造器来创建一个对象,我们需要使用到new操作符。
以及该对象需要提供的实际参数值。
看完书中的例子,我们再来看看构造器Duck这个示例。
<b>示例二:</b>
function Duck (type, canFly) {
this.type = type; // 类型
this.canFly = canFly; // 能不能飞
}
var samllUglyDuck = new Duck("天鹅", "可以飞得很高,很远");
同样,我们可以使用new操作符来创建一批此我们想要的对象。
<b>示例三:</b>
function Duck (type, canFly) {
this.type = type; // 类型
this.canFly = canFly; // 能不能飞
}
var duck1 = new Duck("鸭子1","能飞");
var duck2 = new Duck("鸭子2", "不能飞");
var duck3 = new Duck("鸭子3", "能飞");
/* 通过循环创建一批对象出来 */
var ducks = []; // 声明一个可用的数组
for (var i = 0; i <= 50; i ++) {
ducks[i] = new Duck("眫鸭", "可以飞得很高,很远");
}
/* 使用literal式来创建三个鸭子 */
var duck5 = {
type : "1",
canFly : false
};
var duck6 = {
type : "2",
canFly : false
};
var duck7 = {
type : "3",
canFly : true
};
1. 这样我们是不是就创建了50只鸭子,并且都存放在了数组ducks里面了呢?
2. 我们会发现,数组中的所有Duck对象都具有相同的属性。
3. 使用构造器来创建对象是literal式创建对象相比,构造器constructor更优。
说说在使用构造器的时间,都发生了些什么?
结合这个语句来帮助理解:
var duck1 = new Duck("鸭子1","能飞");
观察这条语句的右手边部分,这一部分的关键从new开始。
1. 在代码执行时,new操作符首先创建了一个空的Object;
2. 接着,new操作符又为this指针分配新的对象Object,也就是例子中的Duck;
3. 当this设置好了之后,将函数需要用到的数据分别传入作为实参;
4. 函数Duck开始被激活,实参被分配到this所指向对象的对象属性。
5. 最后,一旦Duck中的函数体执行完毕,新的对象就创建成功了。new操作符会自动返回指向该对象的引用(指针),并按照语句所写的意思,将该new操作符返回的引用分配给了duck1, 所以我们没有必要在构造器中写上return语句。
为我们的Object添加方法
为对象属性添加方法等同于为一个对象属性赋予一个匿名函数。
一些要注意的地方
从Object的constructor构造器我们得知,它其实也是一个函数,但我们却不能像对待函数那样来对待我们的构造器:
1. 按照约定俗成的说法,通常将构造器名称的首字母大写
2. 在构造体中,我们可以向对待函数体一样,任意地定义变量,使用循环等等事情,但有一点需要知道的是,不建议在构造体中写上你的任何return语句,因为使用构造器的目的是最终能创建对象,如果构造器中不慎被写上了return true,return 1等等这样的语句,会给构造器带来伤害,使得构造器不能正常地返回我们想要的对象。
在使用构造器创建对象的时候,我们用到了new操作符。
如:
var duck = new Duck("天鹅", "可以飞得很高");
如果上面不使用new操作符,直接使用调用函数的方式来完成创建对象的任务,那么,我们的程序执行了之后会是什么样的呢?
下面是对上图错误的解析:
以下是我的翻译加深理解:
1. 记住在配置this变量之前,new操作符会去创建一个新的Object,接着再去调用构造器函数。如果没有使用new操作符,那么就意味着没有任何对象被创建。
2. 对象没有被创建,就说明this没有指向一个新的对象,有或者说,this此时指向的是我们的应用的全局对象(window)。
3. 如果没有使用new操作符,则构造器不会返回任何对象,也就没有什么可以去赋值给变量darkside。所以darkside 等同于 undefined。我们在darkside中调用play()方法时,就碰到了这种错误。
对象构造器并非完美
说这话的功夫,看着文章,JavaScript初学者可能也就看明白了,对象构造器也有不完美的一点:这一点来源于构造器参数(parameter)。
设想,我们写一个构造器便需要将一个对象所需要的属性都作为形参全部列到构造函数的参数列表中,以便传递进实际的值,要说这些属性五个、六个也还不算糟,但要遇上有十来二十个属性的对象,这得有多令人糟心呐……
再设想,如果一个函数的参数列表包含了许多变量,那这样的代码写起来该有多累呀,这样的代码看起来多不入目呀,这样的函数一旦被调用,我们又是得多小心才能保证一个数据不会被赋值到错误的变量上啊!
好在“世上无难事,只怕有心人”。通过看书——《Head First JavaScritp》的第十二章,我看到了针对这种问题的解决方法。
使用literal式创建对象的方法这个时候就用上了。它将与构造器相结合,用更加易懂的方式帮助我们理解。
下面听我新接触到创建对象的方法:
- 用literal字面量方式来创建一个对象,其中包括了对象应具备的属性或方法。
- 按照创建对象构造器的步骤正常进行,但是构造器的形参列表不再是好多个变量(相应对象的属性),而是把之前通过字面量方式创建出来的对象直接传入。
- 最后使用构造器创建对象,调用构造器函数时,传递给该函数的参数就可以直接使用第一步中literal式的对象。
如何知道对象的类型
要知道在javaScript中,存在可以检测基本数据类型的操作符typeof。
typeof可以返回不同变量所对应的数据类型,可是它不认识比如构造器为Dog或Cat的Object。用另外一种说法是:使用Dog构造器得到的对象在typeof看来其实就只是一个Object,它不知道这个Object就是Dog的一个实例(instance)。
如果我们在某些时候很想要知道一个对象所属的类型,那要怎么办呢?毕竟typeof在这一点上帮不到我们。
在学习JavaScript之前,我接触过Java的SE部分。在Java中,要想检验测试一个对象实例所属的类时,我们通常会借助instanceof操作符,通过其返回值的真假来判断分析。而在JavaScript中也是如此,我们依然可以借助instanceof操作符来检验对象实例的类型,比如:
我们使用了Cat构造器创建了cat对象实例,那么,
cat instanceof Cat
cat instanceof Cat就会返回true,因为cat确实是由Cat构造器创造出来的,确实属于Cat一类。反之,instanceof操作符便会返回false。
添加对象属性和删除对象属性
使用构造器创建的对象与使用字面量方式创建的对象其实都具有相同的性质,都可以随意为我们的对象添加属性或者删除属性,而且其操作方式也是一致的。
我们有时会担心,在添加或删除对象属性之后,这个对象实例还属于之前的那个类吗?
答案是肯定的,无论我们对这个对象的属性做出何等改变,都改变不了他生父就是他生父的事实。