- 函数介绍
函数介绍
函数允许我们封装一系列代码来完成特定任务。当想要完成某一任务时,只需要调用相应的代码即可。方法(method)一般为定义在对象中的函数。浏览器为我们提供了很多内置方法,我们不需要编写代码,只需要调用方法即可完成特定功能。
var myArray = ['I', 'love', 'chocolate', 'frogs'];
var madeAString = myArray.join(' ');
var myNumber = Math.random()
函数的作用:
功能的封装,直接调用,代码复用率提高
构建对象的模板(构造函数)
函数实际上是对象,每个函数都是Function类型的实例,并且都与其他引用类型一样具有属性和方法,由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。
- 函数声明
自定义函数
函数由function关键字声明,后面紧跟函数名,函数名后面为形参列表,列表后大括号括起来的内容为函数体。也可以将一个匿名函数(没有函数名的函数)赋值给一个函数变量,这种方式称为函数表达式。 解析器在向执行环境中加载数据时,会率先读取函数声明,并使其在执行任何代码之前可用;当执行器执行到函数表达式的代码的时候才会真正的解释执行。
表示方法:
函数声明
function 函数名(形参列表){
//函数体
}
函数表达式
var 函数名 = function(形参列表){
//函数体
}
- 函数声明
函数声明与var变量声明类似,会进行提升
function add(a,b){
var result = a + b;
return result;//返回值//返回执行的结果给被调用的
}
var total = add(1,2
foo();//函数声明提升到代码的最前边,可以直接调用函数
function foo(){
console.log("hell world");
//return;//没有return:代码执行到大括号
//console.log("1");//return之后的语句不执行
//如果没有返回的内容,则在写代码的时候不关注返回值
}
//变量声明提升 变量声明提升到代码的前边,函数声明之后正常代码之前
console.log(a); //undefined 这里不报错,因为后边有var a的声明。变量的声明进行提升到前边
var a = 'hello';
console.log(a); //'hello'
//提升比较
/*
function test(){console.log('test');}
//var test; //有跟函数声明同名的变量声明的时候,变量声明会被忽略。
console.log(test); //function test
test = function(){console.log('test-----');}
test(); //'test-----'
*/
//函数声明提升和变量声明提升对比
console.log(test); //function test
test(); //'test'
function test(){
console.log('test');
}
var test = function(){
console.log('test----');
}
test(); //'test----'
- 函数表达式
函数表达式能更好的理解函数是一个引用类型的值
var foo = function(a,b){
console.log("result",a+b);
}
foo();
var bar=foo;//复制:引用传递,地址值
3.函数内部属性
函数的内部属性
只有在函数内部才能访问的属性。
arguments
是类数组对象,包含着传入函数中的实际参数,arguments对象还有一个callee的属性,用来指向拥有这个arguments对象的函数
this
指向的是函数赖以执行的环境对象
立即执行函数 IIFE (function(){})()
函数声明的提升 函数声明提升到最顶部,变量声明提升到顶部
局部变量与全局变量 函数内部使用var修饰的变量,是函数内部的局部变量。
length 表示函数希望接受的命名参数的个数,即形参的个数。
只有在函数执行的时候才能确定属性值的属性,称为内部属性。
- arguments
ECMAScript函数的参数与大多数其他语言中的函数的参数有所不同,ECMAScript函数不介意传递参数的个数以及参数类型,这是因为函数的参数在函数内容是使用一个类数组对象来表示的。这个类数组对象就是arguments。
arguments是一个类数组对象,包含着传入函数中的所有参数。arguments主要用途是保存函数参数,但是这个对象还有一个名为callee的属性,该属性是一个指针,指向拥有这个arguments对象的函数。
//length声明时希望的参数的个数
function add(a,b){
var result = a + b;
return result;
}
console.log(add.length);//表示函数希望接受的命名参数的个数,即形参的个数。
Object.getOwnPropertyDescriptor(add,'length');
add(22,42,12,44);
/*参数:接收实参的快捷方式
函数外部:实参保存的就是原始数据
函数内部:arguments保存真正的实参
*/
/*arguments类数组对象
arguments={
“0”:1,
“1”:33,
“2”:”zs”
};
arguments[0],arguments[“1”]
*/
function add(a,b){
console.log(arguments[0],arguments[1],arguments[2],arguments[3]);
console.log(a+b);
}
add(10);
//10 undefined undefined undefined
//NaN
add(10,20);
//10 20 undefined undefined
//30
add(10,20,30);
//10 20 30 undefined
//30
add(10,20,30,40);
//10 20 30 40
//30
foo(10, 20);
function foo(x, y, z) {
console.log(foo.length); //3
console.log(arguments.length); //2
console.log(arguments.callee === foo);//true
console.log(x === arguments[0]);//true
// 数据共享
console.log(x);//10
arguments[0] = 20;
console.log(x); //20
x = 30;
console.log(arguments[0]);//30
// 不会数据共享
z = 40;
console.log(arguments[2]); //undefind
arguments[2] = 50;
console.log(z); //40
console.log(arguments[2]);//50
}
// 求任意个数值的和
function add(){
var result = 0;
for(var key in arguments){
result += arguments[key];
}
return result;
}
console.log(add(1,2,3,4,5));
// 递归实现阶乘
function factorial(num) {
//return num*factiol(num-1);
/*
* return 10*fa(9)
* 10*9*fa(8)
* 10*9*8*7...*fa(2)
* 10*9*8*7*...*2*fa(1)
* 10*9*8*7*...*2*1*fa(-1)
*/
if(num<=1){
return 1;
} else {
return num * arguments.callee(num-1)
}
}
console.log(factorial(3));
- this
this指向函数据以执行的环境对象,当在网页的全局作用域中调用函数时,this对象指向的就是window,nodejs环境this对象指向的是global。this的取值与调用方式有关,一般情况下,如果使用"()"调用,那我们查看"()"前是不是函数名,如果是继续查看函数名前有没有"."如果有,"."前面的那个对象就是this,那么this指向这个对象;如果不是指向全局对象(global/window)
var name='zs';
var foo = function(){
var name='ls';
console.log(this.name);
}
var obj = {
name:"terry",
foo
}
foo();//全局变量 zs
obj.foo();//对象变量 terry
var sayName = function(){
console.log("hello i am "+this.name);
}
var p1 = {
name:"terry",
age:12,
sayName:sayName
}
var p2 = {
name:"larry",
age:12,
sN:sayName
}
sayName();//hello i am undfined
p1.sayName();//hello i am terry
p2.sayName();//error
p2.sN();//hello i am larry
//笔试题 node内部执行,和node运行文件执行:结果不一样
function test(a){
this.x = a;
return this;
}
var x = test(5); //window.x = 5 x = window
var y = test(6); //window.x = 6 y = window
console.log(x.x); //undefined 或者 6
console.log(y.x); //6
练习:
// 有一个叫boss的人,会开车,并且需要钥匙
// 有一个代驾,叫"代驾",同样会开车,并且需要钥匙
// 打印出"XXX拿着XX去开车"
var obj = {
name:"boss",
driver:function(key){
console.log(this.name+"拿着"+key+"去开车");
}
};
obj.driver("奥迪车钥匙");//boss拿着奥迪车钥匙去开车
var replaceDriver = {
name:"代驾",
replace:obj.driver
};
replaceDriver.replace("奥迪车钥匙");//代驾拿着奥迪车钥匙去开车
- IIFE
IIFE: Immediately Invoked Function Expression,意为立即调用的函数表达式,也就是说,声明函数的同时立即调用这个函数。
对比一下,这是不采用IIFE时的函数声明和函数调用:
function foo(){
var a = 10;
console.log(a);
}
foo();
下面是IIFE形式的函数调用:
(function foo(){
var a = 10;
console.log(a);
})();
函数的声明和IIFE的区别在于,在函数的声明中,我们首先看到的是function关键字,而IIFE我们首先看到的是左边的(。也就是说,使用一对()将函数的声明括起来,使得JS编译器不再认为这是一个函数声明,而是一个IIFE,即需要立刻执行声明的函数。
两者达到的目的是相同的,都是声明了一个函数foo并且随后调用函数foo。
为什么需要IIFE?
如果只是为了立即执行一个函数,显然IIFE所带来的好处有限。实际上,IIFE的出现是为了弥补JS在scope方面的缺陷:JS只有全局作用域(global scope)、函数作用域(function scope),从ES6开始才有块级作用域(block scope)。对比现在流行的其他面向对象的语言可以看出,JS在访问控制这方面是多么的脆弱!那么如何实现作用域的隔离呢?在JS中,只有function,只有function,只有function才能实现作用域隔离,因此如果要将一段代码中的变量、函数等的定义隔离出来,只能将这段代码封装到一个函数中。
在我们通常的理解中,将代码封装到函数中的目的是为了复用。在JS中,当然声明函数的目的在大多数情况下也是为了复用,但是JS迫于作用域控制手段的贫乏,我们也经常看到只使用一次的函数:这通常的目的是为了隔离作用域了!既然只使用一次,那么立即执行好了!既然只使用一次,函数的名字也省掉了!这就是IIFE的由来。
(function(){
var a = 10;
console.log(a);
})();
小结
IIFE的目的是为了隔离作用域,防止污染全局命名空间。
ES6以后也许有更好的访问控制手段
- 作用域
- 函数作用域:函数内部声明的变量,在函数外部不能访问
-
- 全局作用域:函数外部声明的变量,在函数内部可以访问。
当函数嵌套,在这个时候,内部函数与外部函数的这个变量就组成了闭包。
-
- 在js中函数内部不存在块级作用域
//ES5中没有块级作用域
{
var a=10;
}
console.log(a);
if(10>5){
var b=20;
}
console.log(b);
for(var i=0;i<10;i++){
var c=30;
}
console.log(i,c);
//函数作用域/局部作用域
function foo(){
if(true){
var a = 3;
}
console.log(a); //3
}
//全局作用域:global/window/本文件内
var v1=10;
v2=20;//可跨文件使用,真正意义上的全局变量
-
- 创建一个具有局部作用域/块级作用域的变量(大括号)
- 使用ES6里面let声明变量
- 创建一个具有局部作用域/块级作用域的变量(大括号)
function foo(){
let b = 4;
if(true){
let a = 3;
}
console.log(a); //报错
}
-
-
- IIFE使用匿名函数的执行来虚拟局部变量
-
(function(){
if(true){
var b = 1;
}
})();
console.log(b);//报错
4.函数调用
函数的调用
函数声明好之后并不会直接运行,需要进行调用才能运行。
调用函数
函数名(实参列表);
函数名.call(执行环境对象,实参列表);
函数名.apply(执行环境对象,实参列表数组);
函数名.bind(执行环境对象)(实参列表);
函数名.bind(执行环境对象,实参列表)();
在JS中,函数没有重载的概念,两个函数的函数名相同即是同样一个函数,但是Java中不是
foo(a,b);//通过小括号进行调用
foo.call(this,a,b);//通过call()调用
foo.apply(this,[a,b]);//通过apply()调用
var say = function(age){
console.log(this,this.name,age)
}
var obj={
name:'zs',
say
}
say(10);
//对象中维护函数的地址 对象--使用-->函数
obj.say(20);
say.call(this,30);
say.call(obj,40);//调用指定函数名的函数,并且将obj赋值给该函数中的this js中常用的
say.apply(obj,[50]);//调用指定函数名的函数,并且将obj赋值给该函数中的this
say.bind(obj)(60);
say.bind(obj,70)()
//对象中不维护函数的地址,还能使用函数 函数--回调-->对象
function sayName(color1,color2){
console.log(this.name+" color:"+color1+"\t"+this.age+" color:"+color2);
}
var p3={name:"ww",age:12};
sayName.call(p3,null);
sayName.apply(p3,null);
sayName.call(p3,"red","green");//改变函数内部的this指向
sayName.apply(p3,["red","green"]);
5.函数本质
函数属性和方法
函数本质上也是一种对象,拥有属性和方法。
函数在内存中的表现形式:
函数当做特殊的对象,栈中保存函数名,堆中有两块区域:函数本身、函数原型,两块区域互相指向,你中有我我中有你,类似于Java的方法区。原型主要用在构造函数中,后面会详细介绍。
函数本质是一个对象,每个函数都有一个原型对象,通过"函数名.prototype"来访问该原型对象;原型对象中有个属性"constructor"指向函数
//普通变量名:首字母小写,后面驼峰
function foo(){}
console.log(foo);//function
console.log(foo.prototype);//object
console.log(foo.prototype.constructor);//function
console.log(foo.prototype.constructor==foo);//true 你女朋友的男朋友是不是你
//构造函数-->对象
//约定习惯:函数的首字母大写
function Dog(){
console.log("我是狗妈妈");
}
var d1=new Dog();
var d2=new Dog();
d1.name="小白狗";
d2.age=12;
console.log(d1,d2);
//面向对象设计思想
function Person(name,age){
this.name=name;
this.age=age;
}
var p1=new Person(“zs”,12);//构造函数的使用,p1对象
//内存关系:
console.log(p1.__proto__,Person.prototype);//父亲
console.log(p1.__proto__.constructor);//母亲
console.log(p1.__proto__.__proto__.constructor);//奶奶
console.log(Person);//函数名,函数的引用,函数名指向函数 typeof:function
console.log(Person.prototype);//每一个函数都有一个原型对象 typeof:object
console.log(Person.prototype.constructor==Person);//每一个原型对象的constructor指向函数本身
console.log(Person.prototype == p1.__proto__)
//创建对象过程中属性name的所有者
console.log('name' in p1);//true
console.log(p1.hasOwnProperty('name'));//true
console.log(Person.prototype.hasOwnProperty('name'));//false
//静态成员、实例成员的新建与使用
Person.dog="dog";
Person.work=function(){console.log(this.name+' work '+this.age)}
Person.prototype.cat="cat";
Person.prototype.say=function(){
console.log(this.name+' say '+this.age)}
console.log(Person,Person.prototype);
console.log(Person.dog,p1.cat);
Person.work();//Person work undefined
p1.say();//zs say 12
//p1.work(); error!
//Person.say(); error!
var p2=new Person('王五',22);
p2.say();
Person.work();
6.函数的应用
函数的应用
函数本质上是一种对象,可以将其当做普通对象来使用。
作为参数
由于函数名本身就是变量,所以函数可以当做值来使用(参数,返回值)。 function callOther(fun,args){
return fun(args);
}
function show(msg){
alert(msg);
}。
作为返回值()
function getFunction(){
return function(){
alert(hello);
}
}
- 回调函数
函数作为参数(实参),当我们调用一个方法,该方法在执行过程中又需要调用我们的方法,这时候我们的方法可以通过匿名函数的方式传递给该方法。
//简单使用
function add(a,b){console.log(a,b)}
function foo(fun,a,b){
fun(a,b);
}
foo(add,1,2);
//模拟绑定监听
function callOther(fun,args){
return fun(args);
}
function onClick(msg){
console.log(msg+"***");
}
function OnMove(msg){
console.log(msg+"===");
}
callOther(onClick,'哈哈');
callOther(OnMove,'哈哈');
var arr = [{
name:"terry",
temp:'35.5'
},{
name:"larry",
temp:'36.1'
}];
// 获取所有的温度,并且组成新的数组返回
arr.map(function(item){
return item.temp;
});
///jQuery
/*
myAjax({
url:'',
method:'get',
data:{},
success:function(res){
console.log(res,'拿到的数据');
}
})
function myAjax(obj){
obj.success({name:'zhangsan',age:12});
}
*/
- 作为返回值
作为值 : var test=function(){} 变量名=匿名函数
作为返回值:
function getFunction(){
return function(){
console.log('hello');
}
}
var foo=getFunction();
console.log(foo);//匿名函数
foo();
function result(){
var func=function(){
console.log("world");
}
return func;
}
var bar=result();
console.log(bar);//[Function func]
bar();
比较器函数-工厂函数
--不讲,数组章节会详细介绍
比较器函数
[{id:1,name:"terry",age:12},
{id:2,name:"larry",age:9},
{id:2,name:"tom",age:19}]
按照age来排序
function(a,b){
if(a.age>b.age){
return 1;
} else {
return -1;
}
}
按照编号来排序
function(a,b){
if(a.id>b.id){
return 1;
} else {
return -1;
}
}
工厂函数
function factory(prop){
return function(a,b){
if(a[prop]>b[prop]){
return 1;
} else {
return -1;
}
}
}
factory("age");
7.闭包
闭包是指有权访问另一个函数作用域中的变量的函数,闭包的创建方式,就是在一个函数内部创建另外一个函数。副作用:闭包只能取得包含函数中任何变量的最后一个值。
function outer(num){
var result = [];
for(var i=0;i<num;i++){
//result[i] = function(){console.log(i)}
result[i] = (function(num){
return function(){console.log(num)}
})(i);
}
return result;
}
var arr=outer(3);
for(var i=0;i<3;i++){
arr[i]()
}