[记录] JavaScript 中的函数

函数:[JavaScript 教程]
函数是一段可以反复调用的代码块。可以传递参数,不同的参数会返回不同的值。

函数声明的三种方法:
1. function 命令
function 命令声明的代码块,就是一个函数。 function 命令后面是函数名,函数名后面是一对圆括号(), 里面可以传入参数。函数体放在大括号里面。

function show(name) {
	// 代码块...
	console.log( name );
}


2. 函数表达式
除了用 function 命令声明函数, 还可以采用变量赋值的写法。

var show = function(name) { 
	// 代码块...
	console.log( name );
};

这种写法是将一个匿名函数赋值给变量。因为赋值语句的等号右侧只能放表达式,所有这个匿名函数称函数表达式。

带名的函数表达式,函数名在函数体内有效,在函数体外部无效。

var show = function abc() {
	// 代码块...
    // 命名函数表达式 
	// 每个函数都会有一个name的属性,这里的区别就在于 show.name  是 abc
};

注意: 函数作为表达式出现,则会忽略名称。函数表达式需要在语句结尾加上分号,表示语句结束。

3. Function 构造函数

Function 构造函数可以不使用new命令,返回结果完全一样。这种声明方式少人使用。
var show = new Function(
	"x",
	"y",
	"return x + y"
);
// 等同于
function show(x, y) {
	return x + y;
}
Function 构造函数的最后一个参数会被当作函数体,如果只有一个参数,该参数就是函数体。


函数调用:
有4种调用模式:函数调用模式、方法调用模式、构造器调用模式、间接调用模式;

1. 函数调用模式

function init(){
	// 代码块...
	// 非严格模式下,this指向全局对象window
	// 'use strict'
	// 严格模式下, this是undefined
}
init(); // window.init();
可以通过 this 来判断当前是否是严格模式
var strict = (function(){ return !this; }());


2. 方法调用模式
当一个函数被保存为对象的一个属性时,我们称它为一个方法。当一个方法被调用时,this被绑定到该对象。

var myObject = {
	value: 100,
	init: function() {
		console.log( this.value );
	}
}
myObject.init(); // 100


3. 构造器调用模式,用new关键字来新建一个函数对象的调用; (this指向被绑定到的构造函数实例上)

var init = function (status) {
	this.status = status;
}
init.prototype.getStatus = function () {
	return this.status;	
};
var test = new init('构造函数');
console.log( test.getStatus() ); // 构造函数; this指向test


4. 间接调用模式 (this指向指定调用的对象)
call()、apply()是函数的两个方法,可以用来间接地调用函数。

var obj = {};
function show(x, y) {
	return x + y;
}
show.call(obj, 1, 2); //3 this => obj
show.apply(obj, [1, 2]); //3 this => obj


函数的重复声明:

变量的重复声明是无用的,不会覆盖之前同一作用域声明的变量。
但函数的重复声明会覆盖前面的声明的同名函数或同名变量。
变量的重复声明变量无用

 
var num = 10;
var num;
console.log( num ); // 10 

覆盖同名变量

 
var num;
function num() {
    console.log(1);
}
num(); // 1

覆盖同名函数

 
function num() {
    console.log("A");
}
num(); // B

function num() {
    console.log("B");
}


JavaScript 引擎将函数名视同变量名,所有采用 function 命令声明函数,都会被提升到代码头部。

// 先调用
show();
// 后声明,由于"变量提升", 所以代码可以正常执行
function show() { .. }

采用赋值语句定义函数,先调用,再声明就会报错。
show();
var show = function() {}
// TypeError: show is not a function
等同以下形式
var show;
show();
show = function() {};

注意: 同时采用 function 命令 和 赋值语句 声明同一个函数,最后采用的总是赋值语句定义的。
var show = function() {
	console.log("A");	
};
function show() {
	console.log("B");
}
show();  // A


函数本身的作用域:
函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。

函数的属性和方法:
A. name 属性

函数的 name 属性返回函数的名字
function show() {}
show.name; // show

变量赋值定义的函数,name 属性返回变量名。
var show = function () {
	// 匿名函数表达式
};
show.name; // show

var show = function abc() {
	// 有名函数表达式
}
show.name; // abc


B. length 属性 (函数形参的个数)

function show(a, b){
	// a,b 是函数的形参
}
show.length;  // 2


C. toString() 方法: 返回一个字符串,内容是函数的源码;

function show() {
	// 注释
	hide();
}
show.toString();
结果:
"function show() {
	// 注释
	hide();
}"


关于删除:

function 命令声明函数无法删除,和变量声明一样
function show() {
	console.log("A");
}
delete show; // false
show(); // A


关于函数返回值:(return 有终止程序的作用)
所有函数都有返回值,没有return语句时,默认返回内容是undefined,return语句不会阻止finally的执行

function show() {
	try{
		return "A";
	}catch(error) {
		return "B";
	}finally{
		return "C";
	}
}
show(); // C


作为构造函数使用时,在调用之前前面加上了new,如果有return语句,返回值不是一个对象,则会忽略,则返回值是this(new出来的新对象);

function show() { // 约定:如果一个函数要做为构造函数的话,首字母需大写。
	this.num = 2;
	return 1; // 返回的是一个基本类型,构造函数会忽略
}
var test = new show();
console.log( test ); // { num: 2 }
console.log( test.constructor ); // show(){ this.num = 2; return 1; }


如果返回值是一个对象,则返回该对象。

function show() {
	this.num = 2;
	return { num: 1 };
}
var test = new show();
console.log( test ); // { num: 1 }
console.log( test.constructor ); // Object(){ [native code] }


关于函数的参数:
函数运行的时候,有时需要提供外部数据,不同的外部数据会得到不同的结果,这种外部数据就叫参数。

// 在函数声明时圆括号内的参数,叫形式参数 - 简称形参;

function test(a, b, c){
	// 形参 - 在函数内部相当于新定义的变量
	// var a, b, c;
	// 获取形参的长度: test.length;

	// arguments 是函数的实参列表, 记录的是函数实际传入的参数
    // [1,2,3] arguments一个类数组 

	// 形参个数 和  实参列表arguments一一对应,如有一方修改则都改,
    // 例外:当实参小于形参时,修改形参变量,对应的arguments为undefined 
	
	return a;
}


// 函数调用的时候掺入的参数,叫做实际参数 - 简称实参;

test(1, 2); // 1; 实参比形参多, 则会忽略多传入的实参
test(); // undefined; 形参比实参多, 内部用到则默认为undefined
test( , 1); // 不能省略靠前的参数,如果要省略只有显示传入undefined
// 报错 SyntaxError: Unexpected token ,(...)
test(undefined, 1); // undefined


同名形参: (在实际开发过程中肯定是不允出现的)
A. 在非严格模式下,函数中可以出现同名形参,且只能访问最后出现的该名称的形参。

function test(a, a, a) {
	console.log(a);
}
test(1,2,3); // 3
test(1,2); // undefined


B. 在严格模式下,出现同名形参会抛出语法错误。

function test(a,a,a){
	'use strict';
	return a;
}
test(1,2,3); // SyntaxError: Duplicate parameter name not allowed in this context


以对象为参数:
当一个函数的形参过多时,在调用的时候必须传入和形参对应的数据。

function show(name, sex, age) {
	// 在传入实参数是必须一一对应,name(名称),sex(性别),age(年龄)
	console.log('姓名:' + name ',性别:' + sex + ', 年龄:' + age);
}
正常顺序
show('jing', '女', '18'); // 姓名:jing,性别:女, 年龄:18
错误顺序
show('jing', '18', '女'); // 姓名:jing,性别:18, 年龄:女

通过key/value对的形式来传入参数,参数的顺序就无关紧要了。
function show(option) {
	console.log(
        '姓名:' + option.name + 
        ',性别:' + option.sex +
        ', 年龄:' + option.age
    );
}
show({age: 18, name:'jing', sex: '女'}); // 姓名:jing,性别:女, 年龄:18


以函数为参数:
函数本身是一个对象,因此可以将函数作为另一个函数的参数,实现函数回调。

function show( fn ) {
	// 判断当前传入的参数类型是否是函数,如果是函数则立即执行;
	if(typeof(fn) === 'function'){
		fn(); // 执行回调
	}
}
show(function(){
	console.log('A');
}); // A


arguments对象:(函数内部的实参数列表)
由于JavaScript允许函数有不定数目的参数,所以需要一种机制,可以在函数体内部读取所有参数。这是就arguments对象的由来。
arguments对象包含了函数运行时的所有参数,arguments[下标]来获取,只有在函数体内才可以使用。

function show() {
	console.log(arguments[0]);
	console.log(arguments[1]);
}
show("A", "B"); 
// A
// B


当形参和实参的格式相同时,arguments对象的值和对应的形参的值保持同步

function show(num1, num2) {
	console.log( num1, arguments[0] ); // 1 1
	arguments[0] = 7;
	console.log( num1, arguments[0] ); // 7 7
	num1 = 10;
	console.log( num1, arguments[0] ); // 10 10
}
show(1);
注意: 虽然形参和实参列表的值相同,但不是相同的命名空间。他们的命名空间是独立的,但值是同步的。


在严格模式下,arguments对象的值和形参的值是 独立的

function show(num1, num2) {
	'use strict'
	console.log( num1, arguments[0] ); // 1 1
	arguments[0] = 7;
	console.log( num1, arguments[0] ); // 1 7
	num1 = 10;
	console.log( num1, arguments[0] ); // 10 7
}
show(1);


当形参并没有对应的实参时,arguments对象的值和形参的值并不对应

function show(num1, num2) {
	console.log( num1, arguments[0] ); // undefined, undefined
	num1 = 10;
	arguments[0] = 7;
	console.log( num1, arguments[0] ); // 10 7
}
show();


arguments对象的内部属性callee
该属性是一个指针,指向拥有这个arguments对象的函数。

var show = function() {
	console.log( arguments.callee === show );
}
show(); // true
可以通过 arguments.callee 达到调用函数自身的目的。
注意:这个属性在严格模式下是禁用的。


通过递归(函数调用自身)实现的阶乘函数

function factorial(num) {
	if( num <= 1 ){
		return 1;
	}else{
		return num * factorial(num-1);
	}
}
factorial(5); // 120


使用arguments.callee消除函数解耦

function factorial(num) {
	if( num <= 1 ){
		return 1;
	}else{
		return num * arguments.callee(num-1);
	}
}
factorial(5); // 120

闭包:
理解闭包,必须理解变量作用域。
JS的两种作用域: 全局作用域和函数作用域。函数内部可以直接读取全局变量。
// 函数内部读取全局变量

var num = 10;
function show() {
	cosnole.log( num );
}
show(); // 10


函数外部无法读取函数内部声明的变量。

function show() {
	var num = 10;
}
console.log( num );
// ReferenceError: num is not defined


链式作用域,子对象可以向上寻找父对象的变量。
// 函数内部嵌套函数,内部函数可以访问外部的变量

function show(){
	var num = 10;
	function show2() {
		console.log( num ); // 10
	}
	return show2;
}


函数 show 的返回值是函数 show2, 由于 show2 可以读取 show 的内部变量, 所以就可以在外部获得 show 的内部变量了。 show2 函数就是闭包, 能够读取其他函数内部变量的函数。
只有函数内部的子函数才能读取内部变量,简单理解成,定义在一个函数内部的函数。
闭包最大的特点,就是它可以记住诞生的环境,比如 show2 记住了它诞生的环境 show,所以从show2可以得到show的内部变量。 闭包就是将函数内部和函数外部连接起来的一座桥梁。

闭包的最大的两个用处,1. 可以读取函数内部变量。 2. 让这些变量始终保持在内存中。

function count(start) {
	return function () {
		return start++;
	}
}
var init = count(5);
init(); // 5;
init(); // 6;
init(); // 7
通过闭包,start的状态被保留了,
每一次调用都是在上一次调用的基础上进行计算。


闭包的另一个用处: 是封装对象的私有属性和私有方法。

function Person(name) {
	var _age;
	function setAge(n) {
		_age = n;
	}
	function getAge() {
		return _age;
	}

	return {
		name: name,
		getAge: getAge,
		setAge: setAge
	};
}
var p1 = Person('yuxi');
p1.setAge(18);
p1.getAge(); // 18
函数 Person 的内部变量 _age, 通过闭包 getAge 和 setAge,变成了返回对象 p1 的私有变量。

注意: 外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以会消耗很大内存。因此不能滥用闭包,否则会造成网页的性能问题。

posted @ 2018-08-28 18:09  yuxi2018  阅读(199)  评论(0编辑  收藏  举报