精华 JavaScript 函数基础。为什么说函数是 JavaScript 世界的第一公民。
发布于 12 年前 作者 russj 13244 次浏览 最后一次编辑是 10 年前 来自 分享

一些使用 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 种调用函数的方式

  1. 直接调用
  2. 作为对象的方法调用
  3. 作为对象的构造函数调用
  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 水平才能上一个台阶。

29 回复

总结的挺好哒,apply和call属于进阶中的基本要素,每个js函数都有的方法~不知道这样说对不对

oo的年代讲函数,施主你确定没有犯错误?哇哈哈。对象才是第一等公民

@i5ting 你这话让 LISP 系情何以堪

@i5ting @xadillax 对象一直是。但就 js 来说,它的函数比 ruby 和 python 中的函数都更接近一等公民。

@alsotang this,new,apply/call这些都是oo之用,所以这里扯函数,拿这些举例说函数是一等公民是有问题的。我真心不觉得js的函数和ruby的函数有啥区别

哈哈,发现一个bug,一行文字输入太长的时候,不会自动换行

@xadillax 恕在下不会lisp,5555

@i5ting 这个问题我也发现了…还不懂怎么解

js不怎么适合oo写法,个人觉得还是习惯函数式的js编码

@snoopy oo为何出现啊?哈哈,看看javascript高级编程第三遍讲得是啥,你就习惯了

@i5ting 我不否认 oo 在 js 里面十分重要,我自己本身也是 oo 出身的转的 js。但是 js 自身的 oo 体现么那么强烈,很多 js 出身的程序猿都不会太在意这样的写法,入乡随俗吧,这东西争不出什么,本身它用纯函数就能实现非常多的东西。虽然我更喜欢写原型链,但是我依然承认函数在 js 里面比对象重要,对象在 js 里面只是函数的子集而已。

@xadillax

js本身是基于原型的oo,大部分前端工作中都没接触过oo,所以代码相对随意,但是这不能说就一定是对的呀,为什么要入乡随俗吧? 君不见前端angular里mvc和ioc遍地都是?

@i5ting @alsotang 这种问题你们都要争论一番,有意义么?

@soliury 我是觉得这帖子写的有问题

@i5ting 我只想说我是 c++ 游戏开发转过来的,面向对象对于我的意义可想而知。我只是赞同各人的观念,不会说哪边一定是对的哪里是错的。存在即合理。既然那么多用函数就解决的事情,那么一定有它的道理。我虽然自己很多时候都是用类去解决,但是也不会去否认面向过程。

@i5ting 你是不是之前用 Java 的?(不是黑,只是问问)

个人看法,OO-Oriented,Design Pattern-Oriented,Agile-Oriented,Process-Oriented 很多时候都 over rated 了。更多可以参见 coolshell的这篇文章

这篇帖子是 Secrets of the JavaScript Ninja 第三章的小结,是比较符合 JS 的主流想法的。

@russj 你们都没看过yui和ext-core么?

(^__^) 嘻嘻……我是java,ruby,ios好几圈的

好吧,Java + Ruby 你赢了

其实函数和对象都是一等公民哈

因为函数也是对象,类也是函数,所以也是对象。。。

@i5ting 我也是OO党,不过近两年确实身边有不少人开始膜拜函数式编程,个人感觉是两种设计思路吧,也不能一巴掌都拍死,曾经被认为“过时”的东西迎来第二春在技术圈也是时常存在的,JS近年来也是增加了一些对函数式编程友好的特性(如箭头函数,如果我说得不对,请斧正),JS也没有完全指明必须用哪种设计思想,还是有挺强的灵活性的,所以究竟是面向对象还是函数式编程还是要看具体的应用需求。

楼主很详细地介绍了JS函数的入门知识,不过确实楼主在文章中混用了函数式编程和面向对象编程的两种思想,概念上确实有所失误。

另外,建议不要重度使用var指令了,毕竟常规应用场景下很难遇到必须使用“作用域提升”这一特性的时候,大多数情况下我们还是希望声明的变量能老老实实呆在声明的作用域下。

呃。。。不好意思,没注意是两年前的帖子,被人顶上来以为是新帖子

@libook 不是近2年,是从2010年左右就开始了

大约从C#增加匿名函数开始

好像是在2007年,C#就加入了剪头函数

主要是原来编译器不能优化的,现在可以优化了,所以速度一样快了。原来函数式范式性能很差的。

first-class function 是指 function 可以和其他数据类型平等地作为参数传来传去 higher-order function 是指返回函数的函数 这是functional programming的两个基本概念

对象都是通过函数创建的

回到顶部