精华 JavaScript 函数基础。为什么说函数是 JavaScript 世界的第一公民。
发布于 3个月前 作者 russj 849 次浏览 来自 分享

一些使用 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 水平才能上一个台阶。

17 回复

总结的挺好哒,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 你赢了

回到顶部