断断续续,自学了一阵子 JS,写了一个图片滚动特效,一个留言板和一个小插件。到现在,看别人的程序还是很吃力,甚至完全不懂,大概这就是学而不思则罔吧,停下来思考总结一下 JS 函数的基本问题。
JS 是面向对象的动态语言,变量、字符串、数组、大括号 {} 里定义的数据、function 函数都是对象。JS 函数分为有名函数和无名(匿名)函数,都用 function 来声明。调用也很简单,名字加一对括号,例如:
fun('hello','world');
function fun(a, b){console.log(a, b);}
运行这两句将会在浏览器控制器里生成一个 hello world. 需要注意的是,上面函数的调用是在函数声明之前,说明 function 函数在预编译时被前置,基本上面向过程的语言都是这样。
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 fun (a, b){console.log(a, b);})('hello','world');
上面函数定义、运行正确,虽然函数有名字,而在函数闭包后(变成右值),名字将被无视, 完全被当成匿名函数使用,查询 fun 显示未定义。说明有名无名不是定义匿名的关键,而是它屁股坐在哪里决定的,大概匿名函数被叫习惯了,准确的说法应叫做右值函数。右值就是用来计算的,可以被赋值给左值。如下:
var fun = (function (a, b){console.log(a, b);});
fun('hello','world');
上面的赋值语句,既然匿名函数已经处于等号右侧了,那毫无疑问就是右值,此时可以省略括号,不需要再闭包进行右值化。带上括号 JS 解释器也不认为错误,但是此时它却不能再用匿名函数的 () 的方法调用了,因为它有名字了。
匿名函数被命名(赋值)后,左值的数据类型是函数,JS 函数(包括有名和匿名)有个神奇的特性,可以被当成类使用:
var abc = new fun(); //fun 由匿名函数赋值,用 function 定义的有名函数也可实例化。
此时,函数 fun 被当成类,实例化给 abc 后,abc 变成一个对象,typeof 可以查看。对象显然不能当函数使用,对象的特点就是属性和方法。而 abc 实例化前的函数未定义属性和方法咋办,幸好 js 提供了一个 prototype 方法。本人认为这是 js 功能最强大的一个方法,有利于对象属性和方法的封装。
唯一令人迷惑的是,prototype 是函数继承而来的方法,并不能使用于对象。也就是 fun 可以用, abc 无效。好在给函数用 prototype 添加属性后,fun 会自动继承,而不需要重新用 new 实例化。如下:
fun.prototype.num = 5; //添加一个属性
fun.prototype.ss = function(){console.log(this.num);}; //封装一个函数
abc.ss();
给 fun 函数封装一个 ss 方法,abc 对象将自动继承。而且,fun 自身用不上,因为函数无法调用属性和方法!这种不为自己专利他人,是多么高尚的品质!朴素的理解,大概就是 new 建立了对象到函数的指针,所以,函数改变,对象也就跟着改变,而且函数内部的 this 直接指向对象指针。
这样做有个好处,一个函数被实例化成任何多个对象后,当函数被 prototype 封装新方法,所有的对象就同时继承。