函数基础与种类、形实参及映射、变量类型

76 阅读9分钟

1. 函数的作用

  1. 一个固定的功能或者程序段被封装的过程,实现一个固定的功能或者是程序,这个封装体中需要一个入口和一个出口。入口就是参数,出口就是返回值。
  2. 解耦合 --> 耦合:代码的重复度太高,函数编程能让一个模块具有高利用率,拥有独立性,拥有模块的单一责任制。

2. 函数的声明和命名规范

1.1 命名规范

  1. 函数的命名规范和变量的命名规范相同
  2. 不能以数字开头,可以包含数字
  3. 可以字母_$符号开头
  4. 小驼峰命名法,myWonderfulTest
  5. 区分大小写

1.2 声明方式

1. 函数声明方式

  1. 函数普通声明方式,函数被调用才会执行,不调用不执行
function 函数名(参数) {
	// 函数体
}
  1. 声明匿名函数的语法错误 Uncaught SyntaxError: Function statements require a function name
function() {
  console.log(1); // Uncaught SyntaxError: Function statements require a function name
};
  1. 函数体内的连等声明变量情况

函数内部连等声明变量,由于没用var进行变量b的声明,变量b会默认暗示为全局变量;连等声明变量,先声明变量后进行赋值;

function test() {
  var a = b = 1;  // 不推荐这种写法
  coonsole.log(1); 
}

2. 函数字面量/函数表达式方式(重要)

  1. 具名函数表达式声明函数
var test = function res() {
  console.log(1);
}
  1. 匿名函数表达式声明函数,也叫做真正的函数字面量表达式
var test = function () {
  console.log(1);
}

3. 函数表达式声明函数,自动忽略函数名称

  1. 函数表达式声明函数,此时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. 函数内部通过函数名去调用函数本身

  1. 函数内部通过函数名调用函数自身;
var test = function test1(){
  console.log(test1());
}
test();
var test = function test1(){
  console.log(test);
}
test();
  1. 由于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)的方式获取当前被调用的函数名称

  1. 具名函数使用函数表达式声明函数,获取被调用的函数名称;用函数表达式利用具名函数进行声明函数时,此时被调用的函数名为具名函数的名称;
var res = function test(){
  console.log(test.name); // test
  console.log(arguments.callee.name); // test
}
res();
console.log(res.name);
  1. 匿名函数使用函数表达式声明函数,获取被调用的函数名称;用函数表达式利用匿名函数进行声明函数时,此时被调用的函数名为保存匿名函数的变量名称;
var res = function(){
  console.log(res.name); // res
  console.log(arguments.callee.name); // res
}
res();
console.log(res.name); // res
  1. 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)打印出函数名? 为什么函数有属性呢 ?

  1. 函数不仅能够像对象一样添加方法和属性,还能够充当作用域的容器,在函数内部声明的变量并不是函数的属性,是属于作用域中的变量。
  2. 通过(函数名.语法)创建函数的属性和方法,都属于函数的静态方法。什么是静态方法和属性呢?静态方法和属性: 在调用的时候或者实例化的时候,不会影响到这些属性和方法的改变。

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:falseenumerable:falseconfigurable: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. 在函数中形式参数与实际参数的映射关系

  1. 函数内部是可以更改实际参数的值,例如用户填的数不合法,我们可以更改用户传递的值;
function res(a, b) {
  	a = 3;
  	console.log(a); // 3
  	console.log(arguments[0]); // 3
}
res(1, 2);
  1. 实际参数数量 < 形式参数的数量,由于实际参数的数量 < 形式参数的数量,其中形式参数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);
  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. 实际参数和形式参数映射关系的深层度理解

  1. 问题呈现:实际参数和形式参数在函数执行的时候进行赋值操作,此时函数内部实际参数与形式参数形成映射关系,当形式参数在函数内部作用域改变时,为什么传递的实际参数在函数外部不随着形式参数改变而改变呢?
  2. 问题的根本:实际参数和形式参数在函数内部才具有映射关系,函数中的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
  1. 下面例子其实看似是映射关系能存在函数外部作用域中,其实不然。实际参数与形式参数赋值时,将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 关键字的注意事项

  1. 如果不指定返回值,系统会隐式添加return
  2. 函数遇到return关键字立即终止函数运行
  3. return 后面的代码不会执行
  4. 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. 暗示全局变量初识

  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;原因和作用域有关,之后章节介绍