False Begginer 4.函数基础、形实参及映射、变量类型

157 阅读7分钟

4.1 函数的声明和命名规范

4.1.1 声明方式

1. 函数声明方式

function 函数名(参数) {
	// 函数体
}

// 例如:

function test() {
 	var a = b = 1;
  // 这种写法因为先进行赋值运算,声明全局变量b,赋值b=1;然后声明局部变量a,然后赋值a = b;
	console.log(1);
}

// 匿名函数直接声明报语法错误
function() {
  console.log(1); // Uncaught SyntaxError: Function statements require a function name
};

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

var test = function res() {
		console.log(1);
}

3. 关于函数字面量函数名称自动忽略的解释

  • 在对变量test赋值的时候,系统会自动忽略res函数名
// 是因为在栈内存中test变量存放的是function res(){}函数的地址,不是通过函数名去访问的,所以有没有函数名都不影响test去访问res函数,所以可以忽略。
var test = function res() {
	console.log(1); 
}
test(); // 1
res(); // Uncaught RefeenceError:res is not defined
  • 函数可以在函数内部通过函数名去调用本身
var test = function res() {
		console.log(1);
    // 函数可以通过函数名在函数内部调用自身
    console.log(test()); // 1
  
    // test变量指向res函数,所以res全等于test
		console.log(res === test); // true
  
  	// arguments.callee表示当前被调用的函数
  	console.log(arguments.callee.name); // res
}
test();
  • 函数可以通过(函数名.name)打印出函数名?为什么函数有属性?
// 函数可以通过.name的方式获取到当前被调用的函数名
// 第一种
var res = function test() {
		console.log(test.name); // test
  	console.log(arguments.callee.name); // test
}
res();
console.log(res.name); // test

// 第二种
var res = function() {
		console.log(res.name); // res
  	console.log(arguments.callee.name); // res
}
res();
console.log(res.name); // res

// 第三种
window.res = function() {
		console.log(1);
}

console.log(res.name); // ''


// 函数不仅能够像对象一样添加方法和属性,还能够充当作用域的容器,在函数内部声明的变量并不是函数的属性,是属于作用域中的变量。

// 通过.语法创建函数的属性和方法,都属于函数的静态方法。什么是静态方法和属性呢?静态方法和属性: 在调用的时候或者实例化的时候,不会影响到这些属性和方法的改变。Es6中在类中的static关键字就表示类的静态属性或者方法。

function res() {}
res.name = '我是方法名'; 
res.fn = function(){
	console.log('我是函数的fn方法');
}
res();
console.log(res.name); // res 改变函数名称,没有效果
res.fn(); // 我是函数的fn方法

4.1.3 函数的组成部分

// 函数的组成部分
function 函数名(参数) {
		// 必须有返回值
  	// 系统默认return undefined;
}

4.1.4 函数的形式参数和实际参数

1. 形式参数(占位-->形式上占位)

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. 在传递数据的时候,不在乎数据类型,实参没有数据类型的规范,任何数据类型都可以接收
//3. 形参和实参数量可以不相等
function res(a, b) {
		console.log(a, b); // 1 undefined
}
res(1);
//4. 函数内部获取实参和形参的长度
function res(a, b) {
		console.log(arguments.length); // 实参 3
  	console.log(arguments.callee); // 形参 2
  	console.log(res.length); // 形参 2
  	console.log(1, 2); // 1 2
}
res(1, 2, 3);

// 5. 在函数中可以更改实参的值
function res(a, b) {
		// 更改实参的值
  	a = 3;
  	console.log(a); // 3
  	console.log(arguments[0]); // 3
}
res(1, 2);

//6. 如果实参没有给形参赋值,然后在函数内更改形参的值,是没用的
function res(a, b) {
  // 给形参b赋值和函数参数默认值很像,没有传实参,arguments[1] = undefined, 设置默认值b = 3,所以输出b打印3
  // 此时arguments[1]实参并没有与之对应的形参,所以形参b改变,并不会改变实参的值
	b = 3;
  cosole.log(b); // 3
  // 为什么没有传递值,改变之后没有用?看第7条
  console.log(arguments[1]); // undefined
}
res(1);

// 7.在函数内形参 a 和 arguments[0]是同一个变量吗?
function res(a, b) {
	a = 3;
  // 虽然二者输出的结果相同,但是二者不是同一个变量。因为a是存在栈内存中的,arguments[0]是存在堆内存中的,但是系统设置将形式参数和实际参数建立起映射的关系,无论实参怎么变化,形参都会随之改变,但是形式参数必须要有实际参数与之对应,否则不会有改变(符合第6条);
  console.log(a); // 3
  console.log(arguments[0]); // 3
}
res(1, 2);

4.实际参数和形式参数映射关系的深层度理解

/*问题呈现:
	实际参数和形式参数在函数执行的时候进行赋值操作,此时实际参数与形式参数形成映射关系,当形式参数在函数内部作用域改变时,为什么实际参数不随着形式参数改变而改变呢?
		
问题的根本:
	实际参数和形式参数具有映射关系,函数中的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

/*8那下面这个例子为什么能够改变呢?
	这个例子其实看似是映射关系能存在函数外部作用域中,其实不然。
        实际参数与形式参数赋值时,将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

4.1.5 函数的return

  • 如果不指定返回值,系统会隐式添加return
  • 函数遇到return关键字立即终止函数运行
  • return 后面的代码不会执行
function test() {
		console.log('执行');
  	console.log('执行完就结束');
  	// 如果不写return,系统会自动添加,并且终止函数执行。
}

4.1.6 return关键字

return 语句终止函数的执行,并返回一个执行的值给函数调用者

下列return 语句都会终止函数的执行:
    return;
    return true;
    return false;
    return x;
    return x + y / 3;



自动插入分号(ASI)规则会影响return语句。在return关键字被返回的表达式之间不允许使用行终止符。
return 
a + b;
--------->
return;
a + b;
--------->

function fn() {
	return 
    {
      message : 'a'
    }
}
console.log(fn()); // undefined

4.1.7 初始作用域[[scope]],

// 全局变量,未被var定义的变量赋值是全局变量
b = 1; // 全局变量
function test() {
		var a = 2; // 局部变量
  	console.log(b); // 1
}
test();
console.log(a); // Uncaught ReferenceError: a is not defined;原因和作用域有关,之后章节介绍


  • 定义一个函数,从window.prompt()函数接收一个n,算出n的阶乘,不能用for循环;// 递归
// 逻辑思路:
// 5的阶乘相当于4的阶乘乘以5
// 5的阶乘: 1 * 2 * 3 * 4 * 5 ---> 4! * 5

// 所以: n的阶乘 n! ---> (n-1)! * n

var n = parseInt(window.prompt('N=?'));
function factorial(n) {
  	// 判断如果输入的小于0
  	if( n <= 0 ) {
    	return '您输入的有误';
    }
		if(n === 1) {
    	return 1;
    }
  return factorial(n-1) * n;
}
var mul = factorial(n);
console.log(mul);
  • 定义一个函数,从window.prompt()函数接收一个n,算出斐波那契数列的第n位,不能用for循环; // 递归
// 逻辑思路;
// 1 1 2 3 5 8 13 21
// 例如:输入的是5,那么应该是n = 5; 
// 那么第5位的值就是第3位值加上第4位值的和;
// 那么递归的出口就是 n = 1 和 n = 2的时候;

// 运算过程:
fobonaq(5) = fobonaq(3) + fobonaq(4);
fobonaq(4) = fobonaq(3) + fobonaq(2);
fobonaq(3) = fobonaq(2) + fobonaq(1);
// 出口
fobonaq(2) = 1;
fobonaq(1) = 1;

var n = parseInt(window.prompt('N=?'));
function fobonaq(n) {
  if( n <= 0 ) {
  	return '输入的有误';
  }
	if( n === 1 || n === 2) {
    	return 1;
  }	
  return fobonaq(n - 2) + fobonaq(n - 1);
}
var num = fobonaq(n);
console.log(num);