断断续续自学了一个月的 JS,前后写了几个特效,一个留言板和一个小插件,到现在看别人的程序还是很吃力,甚至完全不懂,大概这就是学而不思则罔吧,停下来思考总结一下 JS 函数的基本问题。
JS 是面向对象的动态语言,变量、字符串、数组、大括号{}里定义的数据、function 函数都是对象,这里先说说 function 函数对象。JS 函数分为有名函数和无名(匿名)函数,都用 function 来声明。调用也很简单,名字加一对括号,例如:
aaa('hello','world');
function aaa(a, b){console.log(a, b);}
运行这两句将会在浏览器控制器里生成一个 hello world, 需要注意的是,上面函数的调用是在函数声明之前,说明 function 的作用可以将有名函数前置,级别提高,脱离顺序流。js 引擎在编译解释阶段会将有名函数放在所有执行语句之前,以供函数任意语句位置可以被正确调用。
function 后面没有名字的叫匿名函数,声明后如果不立刻调用,该函数就废了,因为你再也找不到它。lisp 和 python 语言也有匿名函数,专门用 lambda 来声明。相应的,JS 匿名函数的调用为一对括号紧跟在匿名函数后面,但是解释器会产生一个语法错误,因此需要将匿名函数用一对括号包起来,JS 里叫闭包。例如:
(function (a, b){console.log(a, b);})('hello','world');
实际上,括号的作用就是将匿名函数变成一个表达式。四则运算符号,逻辑运算符号都有这个作用,采用不同的符号浏览器调用时会有性能差异,好像是 () 和 + 执行速度最快,也有人喜欢用 !(逻辑否)来闭包。
+function (a, b){console.log(a, b);}('hello','world');
用 + 闭包,效果一样。就如同将字符串 “5” 转换为数字可以用 +“5” ,js 会动态转换类型。
在这里,本人觉得有个理解问题,并不是 function 后不给名字就是匿名函数,而是函数作为右值使用时,就会自动降级为匿名函数,右值会让 function 失去声明前置作用。例如给匿名函数一个名字:
(function aaa (a, b){console.log(a, b);})('hello','world');
上面函数定义、运行正确,虽然函数有名字,而在函数闭包后(变成右值),名字将被无视, 完全被当成匿名函数使用,aaa 显示未定义。说明有名无名不是定义匿名的关键,而是它屁股坐在哪里决定的,大概匿名函数被叫习惯了,准确的说法应叫做右值函数。右值就是用来计算的,可以被赋值给左值。如下:
var bbb = (function (a, b){console.log(a, b);});
bbb('hello','world');
上面的赋值语句,既然匿名函数已经处于等号右侧了,那毫无疑问就是右值,此时可以省略括号,不需要再闭包进行右值化。带上括号 JS 解释器也不认为错误,但是此时它却不能再用匿名函数的 () 的方法调用了,因为它有名字了。
匿名函数被命名(赋值)后,左值的数据类型是函数,JS 函数(包括有名和匿名)有个神奇的特性,可以被当成类使用:
var ccc = new bbb(); //bbb 由匿名函数赋值,用 function 定义的有名函数也可实例化。
此时,函数 bbb 被当成类,实例化给 ccc 后,ccc 变成一个对象,typeof 可以查看。对象显然不能当函数使用,对象的特点就是属性和方法。而 ccc 实例化前的函数未定义属性和方法咋办,幸好 js 提供了一个 prototype 方法。本人认为这是 js 功能最强大的一个方法,有利于对象属性和方法的封装。
唯一令人迷惑的是,prototype 是函数继承而来的方法,并不能使用于对象。也就是 bbb 可以用, ccc 无效。好在给函数用 prototype 添加属性后,bbb 会自动继承,而不需要重新用 new 实例化。如下:
bbb.prototype.num = 5; //添加一个属性
bbb.prototype.ss = function(){console.log(this.num);}; //封装一个函数
ccc.ss();
给 bbb 函数封装一个 ss 方法,ccc 对象将自动继承。而且,bbb 自身用不上,因为函数无法调用属性和方法!这种不为自己专利他人,是多么高尚的品质!朴素的理解,大概就是 new 建立了对象到函数的指针,所以,函数改变,对象也就跟着改变,而且函数内部的 this 直接指向对象指针。
这样做有个好处,一个函数被实例化成任何多个对象后,当函数被 prototype 封装新方法,所有的对象就同时继承。如果这个方法具有回调作用,这将产生一个激动人心的效果,目前仅想想,还没实现。