函数也是对象, 使对象不同于其它对象的决定性特点是函数存在一个被称为 [[Call]] 的内部属性.
类似于[[Call]] 这种内部属性无法通过代码访问, 他只是定义了代码执行时的行为.
ECMAScript 为 JavaScript 的对象定义了多种内部属性, 这些内部属性都用双重中括号来标注.
[[Call]] 属性是函数独有的, 表明该对象可以被执行. 由于仅函数拥有该属性, ECMAScript 定义 typeof 操作符对任何具有 [[Call]] 属性的对象返回 "function".
过去因某些浏览器曾在正则表达式中包含 [[Call]] 属性, 导致正则表达式被错误鉴别为函数.
2.1 声明还是表达式
函数有两种字面形式: 函数声明跟函数表达式.
两者的一个重要区别是: 函数声明会被提升至上下文 (要么是该函数被声明时所在的函数范围, 要么是全局范围) 的顶部.
2.2 函数就是值
函数是 JavaScript 的一大重点.
基本上只要是可以使用其他引用值的地方, 你就可以使用函数. 也就是说, 你可以像使用对象一样使用函数(因为函数本来就是对象).
2.3 参数
JavaScript 函数另一个独特之处在于你可以给函数传递任意数量的参数却不造成错误.
这是因为函数参数保存在类数组对象 arguments 中.
如同一个普通 JavaScript 数组, arguments 可以自由增长来包含任意个数的值, 这些值可通过数字索引来引用.
虽然 arguments 类似数组, 但是 Array.isArray(arguments) 返回 false
最后需要注意两点:
arguments 对象自动存在于函数中, 也就是说, 函数的命名参数不过是为了方便, 并不真的限制了该函数可接受参数的个数.
但 JavaScript 也没忽视那些命名参数, 函数的 length 属性表明其期望的参数个数.
2.4 重载
大多数面向对象语言支持函数重载, 它能让一个函数具有多个签名. 函数签名由函数的名字, 参数的个数及其类型组成.
而 JavaScript 可以接收任意数量的参数且参数类型完全没有限制. 这说明 JavaScript 函数根本就没有签名, 因此也不存在重载.
- function sayMessage(message){
- console.log(message);
- }
- function sayMessage(){
- console.log("Default Message");
- }
- sayMessage("Hello!"); // 输出 "Default Message";
在 JavaScript 里, 当你试图定义多个同名的函数时, 只有最后的定义有效, 之前的函数声明被完全删除(函数也是对象, 变量只是存指针).
- var sayMessage = new Function("message", "console.log(message)");
- var sayMessage = new Function("console.log(\"Default Message\");");
- sayMessage("Hello!");
当然, 你可以根据传入参数的数量来模仿重载.
2.5 对象方法
对象的值是函数, 则该属性被称为方法.
2.5.1 this 对象
JavaScript 所有的函数作用域内都有一个 this 对象代表调用该函数的对象.
在全局作用域中, this 代表全局对象(浏览器里的 Windows).
当一个函数作为对象的方法调用时, 默认 this 的值等于该对象.
this 在函数调用时才被设置.
- function sayNameForAll(){
- console.log(this.name);
- }
- var person1 = {
- name: "Nicholas",
- sayName: sayNameForAll
- }
- var name = "Jack";
- person1.sayName(); // 输出 "Nicholas"
- sayNameforAll(); // 输出 "Jack"
2.5.2 改变 this
有 3 种函数方法运行你改变 this 值.
- fun.call(thisArg[, arg1[, arg2[, ...]]]);
- fun.apply(thisArg, [argsArray]);
- fun.bind(thisArg[, arg1[, arg2[, ...]]])
使用 call 或 apply 方法, 就不需要将函数加入每个对象 -- 你显示地指定了 this 的值而不是让 JavaScript 引擎自动指定.
call 与 apply 的不同地方是, call 需要把所有参数一个个列出来, 而 apply 的参数需要一个数组或者类似数组的对象(如 arguments 对象).
bind 是 ECMAScript 5 新增的, 它会创建一个新函数返回. 其参数与 call 类似, 而且其所有参数代表需要被永久设置在新函数中的命名参数(绑定了的参数(没绑定的参数依然可以传入), 就算调用时再传入其它参数, 也不会影响这些绑定的参数).
- function sayNameForAll(label){
- console.log(label + ":" + this.name);
- }
- var person = {
- name: "Nicholas"
- }
- var sayNameForPerson = sayNameForAll.bind(person);
- sayNameForPerson("Person"); // 输出 "Person:Nicholas"
- var sayName = sayNameForAll.bind(person, "Jc");
- sayName("change"); // 输出 "Jc:Nicholas" 因为绑定的形参, 会忽略调用时再传入参数
2.6 总结
函数也是对象, 所以它可以被访问, 复制和覆盖.
函数与其他对象最大的区别在于它们有一个特殊的内部属性 [[Call]], 包含了该函数的执行指令.
函数声明会被提升至上下文的顶部.
函数是对象, 所以存在一个 Function 构造函数. 但这会使你的代码难以理解和调试, 除非函数的真实形式要直到运行时才能确定的时候才会利用它.
来源: https://juejin.im/post/5c15c2c3e51d45401f6677ca