1. 函数的作用
- 一个固定的功能或者程序段被封装的过程,实现一个固定的功能或者是程序,这个封装体中需要一个入口和一个出口。入口就是参数,出口就是返回值。
- 解耦合 --> 耦合:代码的重复度太高,函数编程能让一个模块具有高利用率,拥有独立性,拥有模块的单一责任制。
2. 函数的声明和命名规范
1.1 命名规范
- 函数的命名规范和变量的命名规范相同
- 不能以数字开头,可以包含数字
- 可以字母_$符号开头
- 小驼峰命名法,myWonderfulTest
- 区分大小写
1.2 声明方式
1. 函数声明方式
- 函数普通声明方式,函数被调用才会执行,不调用不执行
function 函数名(参数) {
// 函数体
}
- 声明匿名函数的语法错误 Uncaught SyntaxError: Function statements require a function name
function() {
console.log(1); // Uncaught SyntaxError: Function statements require a function name
};
- 函数体内的连等声明变量情况
函数内部连等声明变量,由于没用var进行变量b的声明,变量b会默认暗示为全局变量;连等声明变量,先声明变量后进行赋值;
function test() {
var a = b = 1; // 不推荐这种写法
coonsole.log(1);
}
2. 函数字面量/函数表达式方式(重要)
- 具名函数表达式声明函数
var test = function res() {
console.log(1);
}
- 匿名函数表达式声明函数,也叫做真正的函数字面量表达式
var test = function () {
console.log(1);
}
3. 函数表达式声明函数,自动忽略函数名称
- 函数表达式声明函数,此时JS会自动忽略函数名称;变量test在栈内存中储存的是函数test1的堆内存地址,函数表达式声明函数时,会自动忽略声明的函数名称test1,因为通过变量test即可获取函数test1;
var test = function test1(){
var a = 1,
b = 2;
console.log(a, b);
}
test1(); // Uncaught ReferenceError: test1 is not defined;
-->
var test = function(){
var a = 1,
b = 2;
console.log(a, b);
}
test1();
4. 函数内部通过函数名去调用函数本身
- 函数内部通过函数名调用函数自身;
var test = function test1(){
console.log(test1());
}
test();
var test = function test1(){
console.log(test);
}
test();
- 由于test变量中存储的时函数res的引用地址,所以res是全等于test变量的;
var test = function res() {
console.log(1);
// 函数可以通过函数名在函数内部调用自身
console.log(test()); // 1
// test变量指向res函数,所以res全等于test
console.log(res === test); // true
}
test();
5. 函数通过name属性获取函数的名称
通过(.name)的方式获取当前被调用的函数名称
- 具名函数使用函数表达式声明函数,获取被调用的函数名称;用函数表达式利用具名函数进行声明函数时,此时被调用的函数名为具名函数的名称;
var res = function test(){
console.log(test.name); // test
console.log(arguments.callee.name); // test
}
res();
console.log(res.name);
- 匿名函数使用函数表达式声明函数,获取被调用的函数名称;用函数表达式利用匿名函数进行声明函数时,此时被调用的函数名为保存匿名函数的变量名称;
var res = function(){
console.log(res.name); // res
console.log(arguments.callee.name); // res
}
res();
console.log(res.name); // res
- window上的函数获取被调用的函数名称
// 匿名函数
window.res = function(){
console.log(res.name); // ''
console.log(arguments.callee.name); // ''
}
res();
console.log(res.name); ''
// 具名函数
window.res = function fn(){
console.log(res.name); // fn
console.log(arguments.callee.name); // fn
}
res();
console.log(res.name); // fn
6. 函数可以通过(函数名.name)打印出函数名? 为什么函数有属性呢 ?
- 函数不仅能够像对象一样添加方法和属性,还能够充当作用域的容器,在函数内部声明的变量并不是函数的属性,是属于作用域中的变量。
- 通过(函数名.语法)创建函数的属性和方法,都属于函数的静态方法。什么是静态方法和属性呢?静态方法和属性: 在调用的时候或者实例化的时候,不会影响到这些属性和方法的改变。
7. 函数名称为什么修改无效呢?
MDN中已经给出答案,此时Function.name的描述符配置项中writable:false, 也就是说明函数的名称是只读的属性,不可以更改;
function res() {}
res.name = 'newRes';
console.log(res.name); // res
8. 函数的length属性
函数的length属性指明函数的形式参数的个数。length是函数对象的一个属性值,指该函数期望传入的参数数量,即形参的个数。形参的数量不包括剩余参数的个数,仅包括第一个具有默认值之前的参数个数。与之对比的是,arguments.length是函数被调用时实际传参的个数😄。
Function构造器本身也是Function。它的length属性值为1。该属性writable:false,enumerable:false,configurable:true。
Function.length; // 1
Function.prototype对象的length属性值为0。
Function.prototype.length; // 0
3. 函数的组成部分
1. 函数必须具有返回值,默认返回值undefined
// 函数的组成部分
function 函数名(参数) {
// 必须有返回值
// 系统默认return undefined;
}
4. 函数的形式参数和实际参数
1. 形式参数
(占位-->形式上占位),相当于在函数体内(函数作用域)中的变量;
// a b c就是形式参数
function res(a, b, c) {
}
2. 实际参数
实际参数就是给形式参数赋值用的;
function res(a, b, c) {
console.log(a + b + c);
}
res(1, 2, 3); // 1 2 3就是实际参数
3. 形参和实参的注意事项
1. 实际参数在给形式参数赋值的时候,参数传递是一一对应的规则;
function res(a, b, c) {
console.log(a + b + c);
}
res(1, 2, 3); // 1--> a , 2--> b, 3--> c
2. 函数参数在传递数据的时候,不在乎数据的类型,实际参数没有数据类型的规范,任何数据类型都可以传递
function res(a, b, c) {
console.log(a, b);
}
res('false', NaN);
3. 函数传递参数时,形式参数与实际参数的数量可以不相等,未接收到实际参数赋值的形式参数,值为undefined
function res(a, b, c) {
console.log(a, b, c); // 1 2 undefined
}
res(1, 2);
function res(a, b) {
console.log(a, b); // 1 2
}
res(1, 2, 3);
4. arguments参数列表(类数组对象),函数内部获取实际参数
function res(a, b){
console.log(arguments);
}
res(1, 2);
5. 函数内部获取形式参数和实际参数长度
arguments.callee表示被调用的函数;
function res(a, b) {
console.log(arguments.length); // 实参 3
console.log(arguments.callee.length); // 形参 2
console.log(res.length); // 形参
}
res(1, 2, 3);
6. 在函数中形式参数与实际参数的映射关系
- 函数内部是可以更改实际参数的值,例如用户填的数不合法,我们可以更改用户传递的值;
function res(a, b) {
a = 3;
console.log(a); // 3
console.log(arguments[0]); // 3
}
res(1, 2);
- 实际参数数量 < 形式参数的数量,由于实际参数的数量 < 形式参数的数量,其中形式参数b并没有实际参数与之传递参数,在函数体内部,形式参数b被赋值为3,按照之前的定义实际参数arguments[1]应该与b随之发生改变,但是实际上由于形式参数b并没有实际参数与之对应,导致在函数体内部丢失掉实际参数与形式参数的映射关系,所以arguments[1]不会发生变化,依旧是undefined;
function res(a, b) {
b = 3;
cosole.log(b); // 3
console.log(arguments[1]); // undefined
}
res(1);
- 函数体内部,形式参数a和arguments[0]实际参数是同一个参数吗?
虽然二者输出的结果相同,但是二者并不是同一个变量。因为在形式参数a是储存在栈内存中,arguments[0]是储存在堆内存中的,但是在函数体内部,系统自动将形式参数与实际参数建立起映射的关系,如果实际参数与形式参数数量相同的情况下,无论实际参数的值怎么变化,形式参数都会随之改变;
function res(a, b) {
a = 3;
console.log(a); // 3
console.log(arguments[0]); // 3
}
res(1, 2);
7. 实际参数和形式参数映射关系的深层度理解
- 问题呈现:实际参数和形式参数在函数执行的时候进行赋值操作,此时函数内部实际参数与形式参数形成映射关系,当形式参数在函数内部作用域改变时,为什么传递的实际参数在函数外部不随着形式参数改变而改变呢?
- 问题的根本:实际参数和形式参数在函数内部才具有映射关系,函数中的arguments中储存着实际参数的值,虽然在函数内部能够通过arguments进行改变实际参数的值影响形式参数的值,但是arguments和实际参数并不是同一个东西;(实际参数与形式参数的映射关系只存在于函数内部作用域中,在函数外部作用域中并不具有映射关系。)
// 原始值
var a = 1;
function test(a) {
a = 3;
console.log(a); // 3
console.log(arguments[0]); // 3
}
test(a);
console.log(a); // 1
// 引用值
var a = {
name: 'tom'
}
function test(a, b) {
a = new Object({ name: 'Shelly' }); // 改变形式参数的引用
console.log(a); // shelly
console.log(arguments[0]); // 实际参数随着改变 shelly
}
test(a);
console.log(a); // 没改变 tom
- 下面例子其实看似是映射关系能存在函数外部作用域中,其实不然。实际参数与形式参数赋值时,将a对象的引用赋值给形式参数,函数内部只是修改了a对象引用中的一个属性。所以外界打印对象a中name属性随之改变,但不属于映射关系,只是修改引用中的属性罢了。
var a = {
name: 'tom'
}
function test(a,b) {
a.name = 'Jack';
console.log(a); // jack
console.log(arguments[0]); // jack
}
test(a);
console.log(a); // jack
8. 例题(函数调用时,不明确实参的个数,打印传入实参的累加值)
function addSum() {
var sum = 0;
for(var i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
console.log(sum);
}
addSum(1, 2, 3, 4);
5. 函数的return
1. return 关键字的注意事项
- 如果不指定返回值,系统会隐式添加return
- 函数遇到return关键字立即终止函数运行
- return 后面的代码不会执行
- return name || '您没有输入姓名';通常以这种方法设置默认值
function test() {
console.log('执行');
console.log('执行完就结束');
// 如果不写return,系统会自动添加,并且终止函数执行。
}
2. return关键字终止函数的执行,并返回一个直接的值给函数调用者
return;
return true;
return false;
return x;
return x + y / 3;
3. return关键字的特殊注意事项
自动插入分号(ASI)规则会影响return语句。在return关键字被返回的表达式之间不允许使用行终止符。
function fn() {
return // 不能有换行,行终止符,不然默认返回undefined,并且不会执行之后的代码;
{
message : 'a'
}
}
console.log(fn()); // undefined
7. 初始作用域[[scope]],之后章节会详细介绍
1. 暗示全局变量初识
- 未被var定义的变量赋值,系统暗示是全局变量
b = 1; // 全局变量
function test() {
console.log(b); // 1
}
test();
console.log(b); // 1
2. 局部变量初识
function test() {
var a = 2; // 局部变量
}
test()
console.log(a); // Uncaught ReferenceError: a is not defined;原因和作用域有关,之后章节介绍