【前端系列教程之JavaScript】05_JavaScript函数详解

JavaScript函数

为什么要有函数?

        如果要在多个地方求1-100之间所有数的和,应该怎么做?

JavaScript函数的定义

函数概念

        函数是由事件驱动的或者当它被调用时执行的可重复使用的代码块。通俗来讲:把一段相对独立的具有特定功能的代码块封装起来,形成一个独立实体,就是函数,起个名字(函数名)。

        函数的作用就是封装一段代码,将来可以重复使用

        函数就是包裹在花括号中的代码块:

函数语法

  • 函数声明

function 函数名(参数列表){
    // 函数体
}
  • 函数表达式

var fn = function () {
  // 函数体
}
  • 特点:

    函数声明的时候,函数体并不会执行,只要当函数被调用的时候才会执行。

    函数一般都用来干一件事情,函数名称一般使用动词

JavaScript声明式函数的应用

        函数的应用分为两步,一是先声明一个函数,再调用该函数。

声明函数:

1、声明无参函数语法

2、声明有参函数的语法

3、声明带有返回值的函数

函数的调用

  • 调用函数的语法:

函数名();
  • 特点:

    函数体只有在调用的时候才会执行,调用需要()进行调用。 可以调用多次(重复使用)

        在JavaScript中,调用函数时,如果没有返回值,就直接用函数方法名()调用。如果有参数,则必须给每个参数赋值,函数方法名(参数值),如果还有返回值,可以用一个变量来存储这个值。

代码示例:

// 声明无参函数:无参函数就是在函数名的小括号中不定义任何参数。
function sayHello() {
  console.log("吃了没?");
}
// 调用无参函数
sayHello();

// 求1-100之间所有数的和
function getSum() {
  var sum = 0;
  for (var  i = 0; i < 100; i++) {
    sum += i;
  }
  console.log(sum);
}
// 调用
getSum();

函数的参数

  • 为什么要有参数

function getSum() {
  var sum = 0;
  for (var i = 1; i <= 100; i++) {
    sum += i;
  }
  console.log();
}

// 虽然上面代码可以重复调用,但是只能计算1-100之间的值
// 如果想要计算n-m之间所有数的和,应该怎么办呢?
  • 语法:

// 函数内部是一个封闭的环境,可以通过参数的方式,把外部的值传递给函数内部
// 带参数的函数声明
function 函数名(var1, var2...) {
  // 函数体
}

上面的var1,var2就是参数,它代表一个不确定的值,这个值要参与函数代码块的运算,在调用函数时,可以给这些参数赋值。也可以在函数中定义多个参数,每个参数之间逗号 (,) 分隔:

当声明函数时,参数相当于作为变量来声明。

变量和参数必须以一致的顺序出现。第一个变量就是第一个被传递的参数的给定的值,以此类推。

// 带参数的函数调用
函数名(实参1, 实参2, 实参3); 
形参1 = 实参1
形参2 = 实参2
  • 形参和实参

    1. 形式参数:在声明一个函数的时候,为了函数的功能更加灵活,有些值是固定不了的,对于这些固定不了的值。我们可以给函数设置参数。这个参数没有具体的值,仅仅起到一个占位置的作用,我们通常称之为形式参数,也叫形参。

    2. 实际参数:如果函数在声明时,设置了形参,那么在函数调用的时候就需要传入对应的参数,我们把传入的参数叫做实际参数,也叫实参。

        需要注意的是,同一个函数,形参和实参是一一对应的。

        思考:如何区分实参和形参?

                1、出现的地方不一样,一个是在函数的声明中,一个出现在函数的调用中。

                2、代表的意义不一样,一个是声明一个变量,一个是该变量的值。

// 定义变量x和y
var x = 5, y = 6;
// 调用fn函数,把x和y传递到函数里面
fn(x,y); 

function fn(a, b) {
  console.log(a + b);
}
// x,y实参,有具体的值。函数执行的时候会把x,y复制一份给函数内部的a和b,函数内部的值是复制的新值,无法修改外部的x,y

代码示例:

// - 圆的面积 = pi * r * r;
function getArea(r) {
    var pi = 3.1415926;
    console.log(pi * r * r);
}
getArea(7);

// - 求2个数中的最大值
function getMax(a, b) {
    if (a > b) {
        console.log(a);
    } else {
        console.log(b);
    }
}
var x = 100;
var y = 1000;
getMax(x, y);

// - 求3个数中的最大值
function getMax(a, b, c) {
    if (a > b && a > c) {
        console.log(a);
    } else if (b > a && b > c) {
        console.log(b);
    } else if (c > a && c > b) {
        console.log(c);
    }
} 

getMax(11111, 100, 1000);

// 求n个数的最大值?
function getMax(arr) {
    // 参数arr  是数组
    var max = arr[0];
    for (var i = 1; i < arr.length; i++) {
        // 判断max和数组中之后的每一个元素进行比较
        if (max < arr[i]) {
            max = arr[i];
        }
    }
    console.log(max);
}
var array = [12, 2, 45, 1, 10];
getMax(array);

// - 判断一个数是否是素数(又叫质数,只能被1和自身整数的数)
function jdugePrimeNumber(number) {
    // 假设number是质数
    var isPrime = true;
    for (var i = 2; i < number; i++) {
        // 判断是否有能被number整数的数
        if (number % i === 0) {
            // 如果此处执行了。说明不是质数
            isPrime = false;
            break;
        }
    }
    if (isPrime) {
        console.log('质数');
    } else {
        console.log('不是质数');
    }
}
jdugePrimeNumber(13);

函数的返回值

        当函数执行完的时候,并不是所有时候都要把结果打印。我们期望函数给我一些反馈(比如计算的结果返回进行后续的运算),这个时候可以让函数返回一些东西,也就是返回值。函数通过return返回一个返回值。

语法如下:

//声明一个带返回值的函数
function 函数名(形参1, 形参2, 形参3...) {
  //函数体
  return 返回值;
}

//可以通过变量来接收这个返回值
var 变量 = 函数名(实参1, 实参2, 实参3...);

        函数的调用结果就是返回值,因此我们可以直接对函数调用结果进行操作。

        示范:利用函数计算两个数相乘后的值,并返回。

//1、声明一个带参数的方法
function myFunction(a,b) {
	return a*b;
}
//2、调用带参的方法
var mul=myFunction(5,6);

代码示例:

// - 求一组数中的最大值
function getMax(arr) {
    var max = arr[0];
    for (var i = 1; i < arr.length; i++) {
        if (max < arr[i]) {
            max = arr[i];
        }
    }
    // 函数的返回值
    return max;
}

// 可以通过一个变量接受函数的返回值
var array = [12, 1, 99, 10, 20];
var max = getMax(array);
console.log(max);

// 返回值直接参与运算 
var num = 5;
var result = getMax(array) + num;
console.log(result);

// - 求阶乘   5 * 4 * 3 * 2 * 1
function getJieCheng(n) {
    var result = 1;
    for (var i = 1; i <= n; i++) {
        // result *= i;
        result = result * i;
    }
    return result;
}

var num = getJieCheng(5);
console.log(num);


// - 求1!+2!+3!+....+n!    n!
function getJieCheng(n) {
    var result = 1;
    for (var i = 1; i <= n; i++) {
        // result *= i;
        result = result * i;
    }
    return result;
}

function getSum(n) {
    // 求1-n之间每一个数的阶乘的累加
    var sum = 0;
    for (var i = 1; i <= n; i++) {
        // 累加的是每一个数的阶乘
        sum += getJieCheng(i);
    }
    return sum;
}

function getFactorial(num) {  
    var sum = 0; //保存阶乘的值
    for(var i = 1;i <= num;i++){
        var factorial = 1; //千万不要像求和定义为0,否则所有的阶乘结果都是0
        for(var j = 1;j <= i;j++){
            factorial *= j;
        }
        sum += factorial;
    }
    return sum;
}
// // 第一次循环  i = 1    sum = 0 + 1 
// // 第二次循环  i = 2    sum = 1! + 2!

// // 1! + 2! + 3! + 4! + 5!

需要注意的是,仅仅希望退出函数时 ,也可使用 return 语句。返回值是可选的:

function myFunction(a,b){ ​

        if (a>b){ ​

                return; ​

        } ​

        x=a+b

}

如果 a 大于 b,则上面的代码将退出函数,并不会计算 a 和 b 的总和。

返回值详解:

如果函数没有显示的使用 return语句 ,那么函数有默认的返回值:undefined
如果函数使用 return语句,那么跟再return后面的值,就成了函数的返回值
如果函数使用 return语句,但是return后面没有任何值,那么函数的返回值也是:undefined
函数使用return语句后,这个函数会在执行完 return 语句之后停止并立即退出,也就是说return后面的所有其他代码都不会再执行。

推荐的做法是要么让函数始终都返回一个值,要么永远都不要返回值。

函数是一种数据类型

function fn() {}
console.log(typeof fn);
  • 函数作为参数

        因为函数也是一种类型,可以把函数作为另一个函数的参数,在另一个函数中调用

  • 函数做为返回值

        因为函数是一种类型,所以可以把函数可以作为返回值从函数内部返回。

function fn(b) {
  var a = 10;
  return function () {
    alert(a+b);
  }
}
fn(15)();

JavaScript arguments对象

        JavaScript中,arguments对象是比较特别的一个对象,实际上是当前函数的一个内置属性。也就是说所有函数都内置了一个arguments对象,arguments对象中存储了传递的所有的实参。arguments是一个伪数组,因此就可以进行遍历。

示例代码:

// 求任意个数的最大值
function getMax() {
    var max = arguments[0];
    for (var i = 1; i < arguments.length; i++) {
        if (max < arguments[i]) {
            max = arguments[i];
        }
    }
    return max;
}

var max = getMax(5, 10, 1, 5, 100);
console.log(max);


// 求任意个数的和
function getSum() {
    var sum = 0;
    for (var i = 0; i < arguments.length; i++) {
        sum += arguments[i];
    }
    return sum;
}

var sum = getSum(5, 1, 3, 4);
console.log(sum);

// 求斐波那契数列Fibonacci中的第n个数是多少?      1 1 2 3 5 8 13 21...
function getFib(n) {
    // 函数体
    var n1 = 1;
    var n2 = 1;
    var n3;
    for (var i = 3; i <= n; i++) {
        n3 = n1 + n2;
        n1 = n2;
        n2 = n3;
    }
    return n3;
}

var r = getFib(6);
console.log(r);

JavaScript函数作用域

        变量从作用域的角度划分,分为两种:局部变量和全局变量。

        什么是作用域?

                变量可以起作用的范围

局部变量

        在JavaScript函数内部声明的变量是局部变量,所以只能在函数内部访问它,该变量的作用域是局部的。

        可以在不同的函数中使用名称相同的局部变量,因为只有声明过该变量的函数才能识别出该变量。只要函数运行完毕,局部变量就会被删除。

function sayHello(){
	//该变量声明在一个函数内部,name就是一个局部变量,
	//局部变量只可以在本函数内调用
	var  name="H5";
	alert("Hello "+name);
}

全局变量

        在函数外声明的变量是全局变量,网页上的所有脚本和函数都能访问它。

//声明一个全局变量,任何函数都可以使用它
var  name="H5";
function sayHello(){
    alert("Hello "+name);
}
//调用函数
sayHello();

        需要注意的是,如果没有使用var声明变量,该变量将被自动作为全局变量声明,不推荐使用。

        比如这条语句:carname="Volvo";将声明一个全局变量 carname,即使它在函数内部声明。

变量的生命周期

        JavaScript 变量的生命期从它们被声明的时间开始。

        局部变量会在退出作用域之后会销毁。

        全局变量会在关闭网页或浏览器才会销毁。

局部变量和全局变量的区别

1、声明位置不同

        局部变量声明在函数内部。全局变量声明在函数外部

2、作用域不一样

        局部变量只在某一个局部范围有效,比如函数内部。全局变量在整个页面有效,网页上的所有脚本和函数都能访问它。

3、生命周期不一样

        局部变量会在函数运行以后被删除,全局变量会在页面关闭后被删除

块级作用域

        任何一对花括号({和})中的语句集都属于一个块,在这之中定义的所有变量在代码块外都是不可见的,我们称之为块级作用域。

        在es5之前没有块级作用域的的概念,只有函数作用域,现阶段可以认为JavaScript没有块级作用域。

作用域链

        只有函数可以制造作用域结构, 那么只要是代码,就至少有一个作用域, 即全局作用域。凡是代码中有函数,那么这个函数就构成另一个作用域。如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用域。

        将这样的所有的作用域列出来,可以有一个结构: 函数内指向函数外的链式结构。就称作作用域链。

// 案例1:
function f1() {
    function f2() {
    }
}

var num = 456;
function f3() {
    function f4() {    
    }
}

// 案例2
function f1() {
    var num = 123;
    function f2() {
        console.log(num); 
    }
    f2();
}
var num = 456;
f1();

匿名函数

        匿名函数,就是没有名字的函数。

        一般的有名函数:

function  myFun( a,b ){
    console.info( a+b );
}
myFun( 10 , 30 );  // 输出40

        匿名函数: 有关键词 function, 有小括号,有大括号,就是没有函数名。

function(a,b){
    console.info( a+b );
}

        想要执行它,怎么执行?没有名字,怎么调用?

        方式一:把它放进一个变量里,这个变量就相当于一个函数名了。没有名字的匿名函数,变成有“名”函数了,我们把这种方式称之为函数字面量

var  myFun = function( a,b ){
    console.info( a+b);
};

myFun( 10,30 );

        方式二:干脆不要名字,直接执行~!-- 这么做可以在内部形成局部变量和局部函数,防止全局污染。

        个人常用的直接执行匿名函数的方式!

        (匿名函数)();

        (匿名函数() );

//注意: 使用改写法一定要注意添加分号
(function(a,b){
	console.info( a+b );
})(10,30);

(function(a,b){
	console.info( a+b );
}(10,30));

        其他直接调用的方式显得比较怪异了。

// 在function前面加一元操作符号  
!function () { /* code */ } ();  
~function () { /* code */ } ();  
-function () { /* code */ } ();  
+function () { /* code */ } ();

// 在function前面添加 new 关键词
new function () { /* code */ }  
new function () { /* code */ } () // 如果需要传递参数,只需要加上括弧()

方式三:利用事件去调用。

var  btn = document.getElementById("btn");  // 找到页面某个标签

// 添加事件
btn.onclick = function(){
	console.info("你点了我!");
}

方法四:作为对象的方法调用

var  myObj = {
	name : "John",
	sayHello:function(){
		console.info("Hello,"+ this.name );
	}
};

myObj.sayHello();

方法五:作为另一个函数的参数。

函数也可以作为另一个函数的参数的。当然有名函数也可以做参数。

function myFun(fn){
    fn();
}

myFun(function(){
    console.info("这个匿名函数是个参数");
});

构造函数

        通过 new 函数名 来实例化对象的函数叫构造函数。任何的函数都可以作为构造函数存在。之所以有构造函数与普通函数之分,主要从功能上进行区别的,构造函数的主要 功能为 初始化对象,特点是和new 一起使用。new就是在创建对象,从无到有,构造函数就是在为初始化的对象添加属性和方法构造函数定义时首字母大写(规范)。

        对new理解:new 申请内存, 创建对象,当调用new时,后台会隐式执行new Object()创建对象。所以,通过new创建的字符串、数字是引用类型,而是非值类型。

1、常用的构造函数:

1. var arr = [];     为      var arr = new Array();       的语法糖。
2. var obj = {}     为      var obj = new Object();     的语法糖
3. var date = new Date();
4. ...

2、自定义构造函数:

// 1. 构造函数名首字母要大写
// 2. 构造函数的作用就是用来创建对象的,
// 3. 功能上: 普通函数是用来执行某个行为;构造函数是用来创建对象的
// 4. 语法上: 基本没有差别
// 5. 从函数体: 普通函数的函数体里面就是要执行的逻辑代码; 构造函数的函数体里面是用来初始化对象成员以及对象方法的;
function Person(id,name,age) {
    //this关键字: 表示的当前对象
    this.id = id;   //对象的属性
    this.name = name;
    this.age = age;
    this.eat = function () {  //对象方法
        alert(this.name+"的吃饭行为");
    }
}

//直接通过函数名() 调用的是普通函数

//使用构造函数,必须结合new来使用
//Person()就算是构造函数,它也属于函数的一种
var p1 = new Person(1001,"张三",12); //创建一个Person对象

var p2 = new Person(1002,"李四",12);

document.write("p1的name是:"+p1.name+"<br>");

p1.eat();

回调函数

        我们先来看看回调的英文定义:A callback is a function that is passed as an argument to another function and is executed after its parent function has completed。

        字面上的理解,回调函数就是一个参数,将这个函数作为参数传到另一个函数里面,当那个函数执行完之后,再执行传进去的这个函数。这个过程就叫做回调

        其实也很好理解对吧,回调,回调,就是回头调用的意思。主函数的事先干完,回头再调用传进来的那个函数。

        举一个别人举过的例子:约会结束后你送你女朋友回家,离别时,你肯定会说:“到家了给我发条信息,我很担心你。” 对不,然后你女朋友回家以后还真给你发了条信息。小伙子,你有戏了。其实这就是一个回调的过程。你留了个参数函数(要求女朋友给你发条信息)给你女朋友,然后你女朋友回家,回家的动作是主函数。她必须先回到家以后,主函数执行完了,再执行传进去的函数,然后你就收到一条信息了。

function modifyArray(arr, callback) {
 // 对 arr 做一些操作
 arr.push(100);
 // 执行传进来的 callback 函数
 callback();
}
var arr = [1, 2, 3, 4, 5];
modifyArray(arr, function() {
 console.log("array has been modified", arr);
});

        上面的代码中,我们先定义了主函数和回调函数,然后再去调用主函数,将回调函数传进去。

        定义主函数的时候,我们让代码先去执行callback()回调函数,但输出结果却是后输出回调函数的内容。这就说明了主函数不用等待回调函数执行完,可以接着执行自己的代码。所以一般回调函数都用在耗时操作上面。比如ajax请求,比如处理文件等。

预解析

        JavaScript代码的执行是由浏览器中的JavaScript解析器来执行的。JavaScript解析器执行JavaScript代码的时候,分为两个过程:预解析过程和代码执行过程

执行环境

        执行环境定义了变量和函数有权访问的其他数据,决定了他们各自的行为。每个执行环境都有与之对应的变量对象(variable object),此对象保存着环境中定义的所有变量和函数。我们无法通过代码来访问变量对象,但是解析器在处理数据时会在后台使用到它。

全局执行环境:

        全局执行环境是最外围的一个执行环境,根据ECMAScript实现所在的宿主环境不同,表示执行环境的对象也不同。在web浏览器中,我们可以认为它是window对象,因此所有的全局变量和函数都是作为window对象的属性和方法创建的。代码载入浏览器时,全局环境被创建,应用程序退出,如关闭网页或者浏览器时,全局执行环境被销毁。

函数执行环境:

        每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就被推入一个环境栈中,当函数执行完毕后,栈将其环境弹出,把控制权返回给之前的执行环境。

预解析过程

  1. 把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值。

  2. 把函数的声明提升到当前作用域的最前面,只会提升声明,不会提升调用。

  3. 先提升var,再提升function。

变量提升(hoisting)

        ES5 提升有变量提升和函数提升。

原则:

        1、所有声明都会被提升到作用域的最顶上;

        2、同一个变量声明只进行一次,并且因此其他声明都会被忽略;

        3、函数声明的优先级优于变量申明,且函数声明会连带定义一起被提升。

变量提升:

        把变量声明提升到函数的顶部,但是变量赋值不会提升。

console.log(a);
var a = 10;
//输出为undefined

//与上例等价
var a;
console.log(a);
a = 10;

        注意:变量未声明,直接使用,输出‘变量 is not defined。

函数提升:

        函数提升会将函数声明连带定义一起提升。

        在JavaScript中函数的创建方式有三种:函数声明、函数表达式(函数字面量)、函数构造法(动态的,匿名的)。

        只有函数声明创建的函数会执行函数提升,字面量定义的函数(实质为变量+匿名函数)会执行变量提升。

test1();
function test1(){
    console.log("可以被提升");
}
test2();
var test2 = function(){
    console.log("不可以被提升");
}
console.log(test2);
var test2 = function(){
   console.log("不可以被提升");
}

函数和变量同时提升:

console.log(test3);
function test3(){console.log('func');}
var test3 = 'Mary';

防止全局污染

全局污染问题:

        JavaScript 可以随意定义保存所有应用资源的全局变量。

        但全局变量可以削弱程序灵活性,增大了模块之间的耦合性。在多人协作时,如果定义过多的全局变量 有可能造成全局变量冲突,也就是全局变量污染问题。

解决全局污染

// 方式1: 定义全局变量命名空间,只创建一个全局变量,并定义该变量为当前应用容器,把其他全局变量追加在该命名空间下。

//my就是一个全局变量,我们可以把my这个全局变量当成一个命名空间,然后把其他的所有全局变量都放在它下面;
var my={};  //唯一的全局变量
my.name={
    big_name:"zhangsan",
    small_name:"lisi"
};
my.work={
    school_work:"study",
    family_work:"we are"
};

// alert(my.name.small_name);

// 方式2: 利用匿名函数将脚本包裹起来
(function(){
    var temp = {};
    //把两个变量赋值给temp
    //temp.school_work = "study";
    //temp.small_name = "李四";
    small_name = "李四";
    temp.getName = function(){
        return small_name;
    }

    //把tmep赋值给全局变量test
    window.test = temp;
}())
// alert(test.getName());
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是波哩个波

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值