1 概念
1.1 定义
函数实际上是对象,每个函数都是Function 类型的实例。
- 函数声明:JavaScript 引擎能把函数声明提升到顶部,解析器会率先读取函数声明
- 函数表达式:等到解析器执行到它所在的代码行,才会真正被解释执行
alert(sum(10,10));//20
function sum (num1, num2) {
return num1 + num2;
}
alert(sum(10,10));//报错
var sum = function(num1, num2){//在把函数当成值来使用的情况下,都可以使用匿名函数。不过,这并不是匿名函数唯一的用途。
return num1 + num2;
};
- 块级作用域与函数声明(es6)
//源代码
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
f();
}());
// ES5 环境
function f() { console.log('I am outside!'); }
(function () {
function f() { console.log('I am inside!'); }
if (false) {
}
f();//“I am inside!”:函数f会被提升到函数头部
}());
// 浏览器的 ES6 环境
function f() { console.log('I am outside!'); }
(function () {
var f = undefined;
if (false) {
function f() { console.log('I am inside!'); }
}
f();// Uncaught TypeError: f is not a function:允许在块级作用域内声明函数,会提升到全局作用域或函数作用域、块级作用域的头部
}());
// 块级作用域内部,优先使用函数表达式
{
let a = 'secret';
let f = function () {
return a;
};
}
1.2返回值
- 位于return 语句之后的任何代码都永远不会执行
- return 语句也可以不带有任何返回值。在这种情况下,函数在停止执行后将返回undefined
function sayHi(name, message) {
return;
alert("Hello " + name + "," + message); //永远不会调用
}
1.3 参数默认值(es6)
1.3.1用法
- 参数默认值是惰性求值的
- 传入的参数严格等于undefined,才会触发该参数等于默认值
function log(x, y = 'World') {//普通函数
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
function Point(x = 0, y = 0) {//构造函数
this.x = x;
this.y = y;
}
const p = new Point(); // { x: 0, y: 0 }
function fetch(url, { body = '', method = 'GET', headers = {} } = {}) {
console.log(method);//与解构赋值的默认值结合起来使用
}
fetch('http://example.com')// "GET":没有第二个参数时,函数参数的默认值就会生效,然后才是解构赋值的默认值生效
let x = 99;
function foo(p = x + 1) {
console.log(p);
}
foo() // 100:参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的。
x = 100;
foo() // 101
function foo(x = 5, y = 6) {
console.log(x, y);
}
foo(undefined, null)// 5 null:如果传入undefined,将触发该参数等于默认值,null则没有这个效果
[1, undefined, 3].map((x = 'yes') => x);
// [ 1, 'yes', 3 ]:undefined就会触发函数参数的默认值。
1.3.2 作用域
一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。
var x = 1;
function f(x, y = x) {
console.log(y);
}
f(2) // 2:调用函数f时,参数形成一个单独的作用域。在这个作用域里面,默认值变量x指向第一个参数x
let x = 1;
function f(y = x) {
let x = 2;
console.log(y);
}
f() // 1:函数f调用时,参数y = x形成一个单独的作用域。这个作用域里面,变量x本身没有定义,所以指向外层的全局变量x
1.3.3 尾逗号(es6)
ES2017 允许函数的最后一个参数有尾逗号
function clownsEverywhere(//定义
param1,
param2,
) { /* ... */ }
clownsEverywhere(//调用
'foo',
'bar',
);
1.4 严格模式(es6)
1.4.1 规则
只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
// 报错
function doSomething(a, b = a) {
'use strict';
// code
}
// 报错
const doSomething = function ({a, b}) {
'use strict';
// code
};
// 报错
const doSomething = (...a) => {
'use strict';
// code
};
const obj = {
// 报错
doSomething({a, b}) {
'use strict';
// code
}
};
1.4.2 解决
- 设定全局性的严格模式
- 把函数包在一个无参数的立即执行函数里面
'use strict';
function doSomething(a, b = a) {
// code
}
const doSomething = (function () {
'use strict';
return function(value = 42) {
return value;
};
}());
1.4 尾调用(Safari)
指某个函数的最后一步是调用另一个函数。
function f(x){
return g(x);
}
2 内部属性
2.1 arguments
- 参数在内部是用一个数组来表示的,在函数体内可通过arguments 对象来访问参数数组。
- arguments 对象可以与命名参数一起使用,它们的内存空间是独立的,但它们的值会同步。没有传递值的命名参数将自动被赋予undefined 值。
-
这个对象还有一个名叫 callee 的属性,该属性是一个指针,指向拥有这个 arguments 对象的函数。
function sayHi() {
alert("Hello " + arguments[0] + "," + arguments[1]);//方括号语法访问每一个元素
alert(arguments.length);//length访问参数个数
}
//递归函数:在一个函数通过名字调用自身的情况下构成的(非严格)
function factorial(num){
if (num <=1) {
return 1;
} else { //消除函数耦合
return num * arguments.callee(num-1) //等价于return num * factorial(num-1)
}
}
//严格模式
var factorial = (function f(num){
if (num <= 1){
return 1;
} else {
return num * f(num-1); //码创建一个名为 f()的命名函数表达式,然后将它赋值给变量factorial
}
});
2.2 this
-
隐式绑定:this 引用的是函数据以执行的环境对象
-
显式绑定:通过 call/apply/bind 修改 this 指向
-
new绑定: 实例对象
-
箭头函数:它的 this 是通过作用域链查到外层作用域的 this ,且指向函数定义时的 this 而非执行时
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
sayColor(); //"red"
o.sayColor = sayColor;
o.sayColor(); //"blue"
2.3 rest参数(es6)
rest 参数(形式为...变量名
),用于获取函数的多余参数。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
// 报错:rest 参数之后不能再有其他参数
function f(a, ...b, c) {
// ...
}
// arguments变量的写法:使用Array.from先将其转为数组
function sortNumbers() {
return Array.from(arguments).sort();
}
// rest参数的写法:rest 参数是一个真正的数组
const sortNumbers = (...numbers) => numbers.sort();
2.4 name属性(es6)
函数的name
属性,返回该函数的函数名。
var f = function () {};//将一个匿名函数赋值给一个变量
// ES5
f.name // ""
// ES6
f.name // "f"
const bar = function baz() {};//将一个具名函数赋值给一个变量
// ES5
bar.name // "baz"
// ES6
bar.name // "baz"
(new Function).name // "anonymous":Function构造函数返回的函数实例
function foo() {};
foo.bind({}).name // "bound foo":bind返回的函数,name属性值会加上bound前缀。
3 函数属性和方法
3.1 属性
- length 属性表示函数希望接收的命名参数的个数。将返回没有指定默认值的参数个数,不包括 rest 参数。(es6)
-
prototype 是保存它们所有实例方法的真正所在,prototype 属性是不可枚举的,因此使用 for-in 无法发现
(function (a, b, c = 5) {}).length // 2
(function (a = 0, b, c) {}).length // 0:如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了
(function(...a) {}).length // 0
(function(a, ...b) {}).length // 1
3.2 方法
- call()和apply()第一个参数是函数的执行上下文,call()第二个参数后面传入多个参数,而apply传入数组。会立即执行。能够扩充函数赖以运行的作用域。
-
bind()参数同call。返回执行上下文被改变的函数,不会立即执行。
-
toString()
返回函数代码本身,以前会省略注释和空格。修改后,返回一模一样的原始代码。(es6) -
valueOf() 返回函数的代码。
window.color = "red"; var o = { color: "blue" }; function sayColor(){ alert(this.color); } sayColor(); //red sayColor.call(this); //red sayColor.call(window); //red sayColor.call(o); //立即调用:blue var objectSayColor = sayColor.bind(o); objectSayColor(); //没有立即调用:blue
function sum(num1, num2){ return num1 + num2; } function callSum(num1, num2){ return sum.call(this, num1, num2); //call,传递给函数的参数必须逐个列举出来 } alert(callSum(10,10)); //20 function sum(num1, num2){ return num1 + num2; } function callSum2(num1, num2){ return sum.apply(this, [num1, num2]); //apply,传入数组 } alert(callSum2(10,10)); //20
function /* foo comment */ foo () {} foo.toString() // function foo() {}:es5 function /* foo comment */ foo () {} foo.toString() // "function /* foo comment */ foo () {}":es6
4 函数类型
4.1普通函数
调用方式:直接调用
作用:创建对象需要return
this:window,内部的
this
指向函数运行时所在的对象// 作为普通函数调用 Person("Greg", 27, "Doctor"); // 添加到window window.sayName(); //"Greg" // 在另一个对象的作用域中调用 var o = new Object(); Person.call(o, "Kristen", 25, "Nurse"); o.sayName(); //"Kristen":这里是在对象o 的作用域中调用的,调用后o 就拥有了所有属性sayName()方法
4.2 构造函数
调用方式:new 操作符来调用
作用:创建实例对象无需return
this:实例对象
// 当作构造函数使用 var person = new Person("Nicholas", 29, "Software Engineer"); person.sayName(); //"Nicholas"
4.3 箭头函数
4.3.1 基本用法
var f = v => v;//一个参数不加圆括号,没有花括号无需return // 等同于 var f = function (v) { return v; }; var sum = (num1, num2) =>{ return num1 + num2};//多个参数加圆括号,多条语句加花括号需return // 等同于 var sum = function(num1, num2) { return num1 + num2; }; let getTempItem = id => ({ id: id, name: "Temp" });//直接返回一个对象,必须在对象外面加上括号
4.3.2 注意点
- 箭头函数没有自己的
this
对象,内部的this
就是定义时上层作用域中的this
。 - 不可以对箭头函数使用
new
命令,不可以使用arguments
对象。 - 箭头函数没有自己的
this
,所以当然也就不能用call()
、apply()
、bind()
这些方法去改变this
的指向
var handler = { id: '123456', init: function() {//箭头函数里面的this,总是指向handler对象。如果回调函数是普通函数,此时this指向document对象。 document.addEventListener('click', event => this.doSomething(event.type), false); }, doSomething: function(type) { console.log('Handling ' + type + ' for ' + this.id); } }; const cat = {//对象的属性建议使用传统的写法定义,不要用箭头函数定义 lives: 9, jumps: () => {//普通函数,该方法内部的this指向cat;箭头函数,使得this指向全局对象 this.lives--; } } var button = document.getElementById('press'); button.addEventListener('click', () => {//需要动态this的时候,也不应使用箭头函数 this.classList.toggle('on'); });
- 箭头函数没有自己的