js之函数

  1. 函数介绍

函数介绍

函数允许我们封装一系列代码来完成特定任务。当想要完成某一任务时,只需要调用相应的代码即可。方法(method)一般为定义在对象中的函数。浏览器为我们提供了很多内置方法,我们不需要编写代码,只需要调用方法即可完成特定功能。

var myArray = ['I', 'love', 'chocolate', 'frogs'];

var madeAString = myArray.join(' ');

var myNumber = Math.random()

函数的作用:

功能的封装,直接调用,代码复用率提高

构建对象的模板(构造函数)

函数实际上是对象,每个函数都是Function类型的实例,并且都与其他引用类型一样具有属性和方法,由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。

  1. 函数声明

自定义函数

函数由function关键字声明,后面紧跟函数名,函数名后面为形参列表,列表后大括号括起来的内容为函数体。也可以将一个匿名函数(没有函数名的函数)赋值给一个函数变量,这种方式称为函数表达式。 解析器在向执行环境中加载数据时,会率先读取函数声明,并使其在执行任何代码之前可用;当执行器执行到函数表达式的代码的时候才会真正的解释执行。

 表示方法:

函数声明

function 函数名(形参列表){

  //函数体

}

函数表达式

var 函数名 = function(形参列表){

  //函数体

}

  1. 函数声明

函数声明与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----'

  1. 函数表达式

函数表达式能更好的理解函数是一个引用类型的值

var foo = function(a,b){

  console.log("result",a+b);

}

foo();

var bar=foo;//复制:引用传递,地址值

3.函数内部属性

 函数的内部属性

只有在函数内部才能访问的属性。

 arguments

是类数组对象,包含着传入函数中的实际参数,arguments对象还有一个callee的属性,用来指向拥有这个arguments对象的函数

 this

指向的是函数赖以执行的环境对象

 立即执行函数 IIFE         (function(){})()

 函数声明的提升        函数声明提升到最顶部,变量声明提升到顶部

 局部变量与全局变量        函数内部使用var修饰的变量,是函数内部的局部变量。

length 表示函数希望接受的命名参数的个数,即形参的个数。

只有在函数执行的时候才能确定属性值的属性,称为内部属性。

  1. 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));

  1. 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("奥迪车钥匙");//代驾拿着奥迪车钥匙去开车

  1. 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以后也许有更好的访问控制手段

  1. 作用域
    • 函数作用域:函数内部声明的变量,在函数外部不能访问
    • 全局作用域:函数外部声明的变量,在函数内部可以访问。

当函数嵌套,在这个时候,内部函数与外部函数的这个变量就组成了闭包。

    • 在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;//可跨文件使用,真正意义上的全局变量

    • 创建一个具有局部作用域/块级作用域的变量(大括号)
      1. 使用ES6里面let声明变量

function foo(){

let b = 4;

if(true){

 let a = 3;

}

console.log(a); //报错

}

      1. 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);

}

}

  1. 回调函数

函数作为参数(实参),当我们调用一个方法,该方法在执行过程中又需要调用我们的方法,这时候我们的方法可以通过匿名函数的方式传递给该方法。

//简单使用

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});

}

*/

  1. 作为返回值

作为值 : 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]()

}

https://www.jianshu.com/p/26c81fde22fb

https://blog.csdn.net/qq_21132509/article/details/80694517

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值