一些使用 JavaScript 函数的基本注意事项,主要是 Secrets of the JavaScript Ninja 第三章的小结。适合有其他语言编程经验的人阅读。
要想真正掌握 JavaScript 编程,必须把 JavaScript 作为一种函数式编程语言来理解。
函数是 JavaScript 世界的第一类对象(first-class object)。所有对象(object)有如下功能:
- 可以通过字面量(literals)创建
- 可被赋于变量,数组元素和其他对象的属性(property)
- 可以作为参数传递给函数
- 可以作为函数的返回值
- 可以含有能被动态创建和赋值的属性 JavaScript 函数拥有所有以上能力,能像其他对象一样使用。因此,我们说函数是第一类对象。 除了上述对象的功能,函数区别对象之处是能被调用。
浏览器的事件循环(event loop)是单线程。
函数有三种
1. 普通函数
function myFunc( ){
...
}
2. 内联函数。普通函数被赋给一个变量后变为内联函数
var iFunc = function myFunc( ){
...
}
内联函数有函数名但是不能用来调用,只有使用变量来调用。性能上类似匿名函数,会有性能的损失。
3. 匿名函数。
function( ){
...
}
//或者赋给一个变量
var nFunc = function( ){
...
}
JavaScript 里匿名函数使用非常普遍。
JavaScript 语言的一个重要特点是可以在任何表达式允许出现的地方创建函数。
JavaScript 使用函数字面量(function literal)申明函数和数值字面量(numeric literal)创建数值是一样的过程。记住,作为第一类对象,函数可以和其他字符串和数值一样被使用。
作用域 (scope)
JavaScript 的作用域是整个函数,而不是 { }。这一点和其他多数语言都不一样。很多有经验的程序员新入手 JavaScript 的时候被坑。例如
if (true){
var account = 100;
}
var num = account;
console.log( num ); // 这里会输出 100,account仍然可用
account 变量在 if 之外还继续存在,可以继续的合法使用。WTF!
函数作用域提升(hoisting)
就是非匿名函数可以在声明之前引用 (forward-referenced),当然前提是在同一个作用域内。注意,如果函数被赋予给了一个变量,例如:
var fval1 = function func1(){ return 100; }
就不会被提升了,因为变量的作用域不能被提升。fval1 和 func1 都不会被提升。实际上,在被赋予一个变量后, func1 名字已经失效,不能再用来调用函数了,虽然 console.log( fval1.name ) 还是会输出 func1,但是把 func1赋值给 fval1的行为让 func1 变为了内敛函数( inline function), 类似匿名函数,名字虽然存在但是不能用来调用。 函数在 JavaScript 里真的比较特殊。而下面的
var fval2 = function (){ return 101; }
是匿名函数,当然也不会被提升。
只有如下这样的才能被提前引用:
function namedFunc(){ return 102; }
一共有 4 种调用函数的方式
- 直接调用
- 作为对象的方法调用
- 作为对象的构造函数调用
- 通过 apply( ) 或 call( ) 调用
调用 JavaScript 函数时如果形参(parameter)和实参(augment)的数目对不上,不会报错。
- 如果是实参多于形参,多出来的部分被忽略掉。
- 如果是形参多于实参,没被赋值的会被设为 undefined。
所有的函数调用都会有两个隐含的形参:arguments 和 this
- arguments: 神似数组但不是数组。它有 length 属性,得到arguments 的长度,也可以用 index,例如 arguments[0] 访问第一个元素,但就只有这些了,没有数组具有的其他方法。
- this:函数上下文(function context),具体是啥得看怎么被调用的
函数的第一种和第二种调用的方式 (直接调用和作为对象方法调用)其实是一样的。因为在浏览器里,第一种其实就是 window 对象的方法,如果函数是全局函数的话。
第三种,通过 new 关键字来调用构造函数。调用后,以下会发生:
- 一个新的对象被创建
- 这个对象作为 this 参数被传递给构造函数,变成这个构造函数的函数上下文
- 没有显式返回值,这个新对象就作为这个构造函数的值被返回
构造函数的目的是创建一个新对象,初始化,然后作为构造值返回。
最后一种,apply( ) 和 call( ) 。当我们调用一个函数时,使用他们来显示指定任何对象作为函数的上下文。 apply( ) 和 call ( ) 的区别只是 apply( ) 的第二参数是数组,而 call( ) 是一串单独的元素。
最后,思考问题的时候要把函数作为基本的组件而不是祈使语句,这样你的 JavaScript 水平才能上一个台阶。
@alsotang this,new,apply/call这些都是oo之用,所以这里扯函数,拿这些举例说函数是一等公民是有问题的。我真心不觉得js的函数和ruby的函数有啥区别
哈哈,发现一个bug,一行文字输入太长的时候,不会自动换行
@i5ting 我不否认 oo 在 js 里面十分重要,我自己本身也是 oo 出身的转的 js。但是 js 自身的 oo 体现么那么强烈,很多 js 出身的程序猿都不会太在意这样的写法,入乡随俗吧,这东西争不出什么,本身它用纯函数就能实现非常多的东西。虽然我更喜欢写原型链,但是我依然承认函数在 js 里面比对象重要,对象在 js 里面只是函数的子集而已。
@i5ting 我只想说我是 c++ 游戏开发转过来的,面向对象对于我的意义可想而知。我只是赞同各人的观念,不会说哪边一定是对的哪里是错的。存在即合理。既然那么多用函数就解决的事情,那么一定有它的道理。我虽然自己很多时候都是用类去解决,但是也不会去否认面向过程。
@i5ting 你是不是之前用 Java 的?(不是黑,只是问问)
个人看法,OO-Oriented,Design Pattern-Oriented,Agile-Oriented,Process-Oriented 很多时候都 over rated 了。更多可以参见 coolshell的这篇文章
这篇帖子是 Secrets of the JavaScript Ninja 第三章的小结,是比较符合 JS 的主流想法的。