函数

  • 函数是一段被封装的代码,可以反复被调用。
  • 在JS中,函数也可以是一个值、一个对像、还可以是一个表达式,因此函数可以赋值、可以运算、可以拥有属性和方法
  • JavaScript拥有函数式编程的很多特性和风格,灵活使用函数,可以编写出功能大、代码简洁、设计优雅的程序。
//自我介绍    
/*  var yourGirlFri = {};
    yourGirlFri.name = "张三";
    yourGirlFri.sex = "女";
    yourGirlFri.age = "40";
    yourGirlFri.price = "388";

    var yourGirlFri = {};
    yourGirlFri.name = "李四";
    yourGirlFri.sex = "女";
    yourGirlFri.age = "18";
    yourGirlFri.price = "188";

    var yourGirlFri = {};
    yourGirlFri.name = "王大麻子";
    yourGirlFri.sex = "女";
    yourGirlFri.age = "12";
    yourGirlFri.price = "1200";
*/

//一段相同的代码重复执行  我们可以书写一个函数把代码包含起来  使用的时候 直接调用函数即可
function yourGirlFri(name,sex,age,height) {
    var obj = {};
    obj.name = name;
    obj.sex = sex;
    obj.age = age;
    obj.height = height;
    return obj;
}

var girl1 = yourGirlFri("lily","男","19","180")
var girl2 = yourGirlFri("王八八","女","100","190")
var girl3 = yourGirlFri("王久久","女","90","110")

console.log(girl1)
console.log(girl2)
console.log(girl3)

定义函数

声明函数

  • 在 Javascript中可以使用function语句声明函数。

    function funName ([args]){

    ​ statements

    }

    • funName是函数名,与变量名一样都必须是JavaScript合法的标识符。
    • 在函数名之后是一个由小括号包含的参数列表,参数之间以逗号分隔。参数是可选的,没有数量限制。
    • 作为标识符,参数仅在函数体内被访问,参数是函数作用域的私有成员。调用函数时,通过为函数传递值,然后使用参数获取外部传入的值,并在函数体内干预函数的运行
    • 在小括号之后是一个大括号,大括号内包含的语句就是函数体结构的主要内容。在函数体中,大括号号是必不可少的,缺少大括号, JavaScript将会抛出语法错误,
  • var语句和function语句都是声明语句,它们声明的变量和函数都在JavaScript预编译时被解析,也被称为变量提升和函数提升。

//直接声明函数,可以直接提升
fn1(1,2,3);
function fn1(a,b,c) {
    console.log(a,b,c)
}


//函数只有定义在了其他函数中,才是局部的函数,否则都是全局的,在哪都能调用
//ES5中,不允许 函数书写在非函数的代码块中
fn2();//oh
console.log(fn2);
var a = 100;
if (a > 10){
    function fn2() {
        alert("oh");
    }
}
fn2();//oh


//函数也可以不书写名字,这种函数被称作为匿名函数
function () {
    alert("heng")
}

//匿名函数的执行方法 1   自调用(分为直接扩住函数体调用、在函数前添加 !~ + - 一元运算符 然后 添加小括号调用)
(function (){
    alert("heng");
})();

!function (){
    alert("heng");
}()

//匿名函数的执行方法 2  赋值调用  (也被称作为函数表达式)  这种方法只能提升fn3  不能提升函数体

var fn3 = function (){
    alert("heng");
}
fn3()

构造函数

  • 使用Function(p1,p2,p3,p4,..pn,body)构造函数可以快速生成函数
  • Functiono的参数类型都是字符串,p1-pn表示所创建函数的参数名称列表,body表示所创建函数的函数结构体语句,在body语句之间以分号分隔。
  • 使用Function()构造函数不是很常用,因为一个函数体通常会包含很多代码,如果将这些代研以一行字符串的形式进行传递,代码的可读性会很差。
var fn1 = new Function("console.log(111)");
fn1();

//以下两种传参方式都可以使用
var fn2 = new Function("a","b","c","console.log(a+b+c)");
var fn2 = new Function("a,b,c","console.log(a+b+c)");
fn2(1,2,3);

函数直接量

  • 函数直接量也称为匿名函数,即函数没有函数名,仅包含函数关键字,参数和函数体,具体用法如下 function (){}
  • 匿名函数就是一个表达式,即函数表达式,而不是函数结构的语句,可以把匿名函数作为值赋值给变量或者对象等等
  • 当把函数结构作为一个值赋值给变量之后,变量就可以作为函数被调用,此时变量就指向那个匿名函数
  • 匿名函数可以自己调用,比如加上小括号然后整体调用,或者在最前边添加!-+~等等一元操作符
// 把函数当做一个值
function fn1() {
    console.log("fn1");
}
var fn2 = fn1;
var fn3 = function (){
    console.log("匿名")
}
fn2();
fn3();

//函数也可以是一个对象
function fn1() {
    alert("fn1");
}
fn1.name1 = "lily";
fn1.sex = "nv";
// fn1();
alert(fn1.name1);
alert(fn1.sex);

//函数可以作为对象的某个方法
var obj = {
    name :"lily",
    sex:"nan",
    say:function () {
        alert("aaaaaaaaa");
    },
    do:fn1
}
console.log(obj.name);
console.log(obj.say());//undefined
obj.do();

调用函数

JavasScipt供4种函数调用模式,函数调用,方法调用,使用call或apply动态调用(后边讲解),使用new间接调用(后边讲解)

函数调用

在默认状态下,函数是不会被执行的,使用小括号可以激活并执行函数。在小括号中可以包含零个或多个参数,参数之间通过逗号进行分隔(可以返回函数并调用)

函数的返回值

  • 函数提供两个接口与外界的交互,其中参数作为入口,接受外界的信息。返回值作为出口,把运算结果反馈给外界。
  • 在函数体内,使用return语句可以设置函数的返回值。一旦执行返回语句,将停止函数的运行,并运算和返回返回后面的表达式的值
  • 如果函数不包含return语句,则执行完函数体内每条语句后,返回undefiend值。
  • JavaScript是一种弱类型语言,所以函数对接收和输出的值都没有类型限制,JavaSeript也不会自动检测输入和输出值的类型
  • 可以返回一个计算值 也可以返回多个值(使用对象或者数组)
 function fn1(a,b){
     var sum = a + b;
     return sum;
     alert("计算完毕");
 }
fn1(1,2);//如果不用变量接受,或者是不使用,那么这个返回值是看不到的
console.log(fn1(1,2)+4);//7


function fn1() {
    var a = 1;
    return function () {
        alert(a);
    }
}
fn1()();
var b = fn1();
console.log(b);
a();


function teacherMessage(name,age,sex) {
    alert("我的名字是"+name);
    alert("我的年龄是"+age);
    alert("我的性别是"+sex);
}

var teacher1 = {
    name:"lily",
    age:"18",
    sex:"nv"
}
var teacher2 = {
    name:"lily1",
    age:"128",
    sex:"nan"
}
function teacherMessage(obj) {
    alert("我的名字是"+obj.name);
    alert("我的年龄是"+obj.age);
    alert("我的性别是"+obj.sex);
}
teacherMessage(teacher1);


function fn1() {
    var a = 1;
    return;//返回的是undefined
    alert(2);
}

console.log(fn1());

方法调用

  • 当一个函数被设置为对象的属性值时,称之为方法,使用点语法可以调用一个方法
var obj = {
    name :"lily",
    sex:"nan",
    say:function () {
        alert("aaaaaaaaa");
    },
    do:fn1
}
console.log(obj.name);
console.log(obj.say());//undefined
obj.do();

函数参数

参数是函数对外练习的唯一入口,用户只能通过参数来控制函数的运行。

01.形参和实参

  • 形参:在定义函数时,声明的参数变量仅在函数内部可见
  • 实参:在调用函数时,实际传入的值
  • 一般情况下,函数的实参和形参的数量应该相同,但是JS并没有这样的要求。可以不相同
  • 如果函数的实参数量少于形参数量,那么多出来的形参会默认会undefined
  • 如果函数实参数量多余形参数量,那么多出来的实参就不能通过形参访问。函数忽略掉多余的实参。
  • 可以设定形参默认值或者判断给值
function f(a,b) {

}
f(1,2);

function f2(a,b,c) {
    console.log(a,b,c);//1 2 undefined
    console.log(typeof c);//undefined
}
f2(1,2);

function f3(a,b,c) {
    console.log(a,b,c)
}
f3(1,2,3,4,5,6,7)

//书写一个函数 计算 三个值的和
function add(a,b,c) {
    return a+b+c;
}

console.log(add(1, 2));//NaN


//直接给函数形参默认值
function add(a,b,c=0) {
    return a+b+c;
}
console.log(add(1, 2));

//判断参数是否存在,否则给出默认值
function add(a,b,c) {
    c = typeof c === "undefined" ? 0 : c;
    typeof c === "undefined" ? c = 0 : c;
    return a+b+c;
}
console.log(add(1, 2));//NaN

获取参数个数

  • 使用arguments对象的length属性可以获取函数的实参个数。
  • argument对象只能在函数内可见,因此arguments.legth也只能在函数体内使用。
  • 使用函数对象的length属性可以获取函数的形参个数,该属性为只读属性,在函数体内,体外都可以使用
function add(a,b,c) {
    alert(add.length)//函数形参的个数
    alert(argument.length)//获取实参的个数
    return a+b+c;
}
alert(add.length)//函数形参的个数
console.log(add(1, 2));

使用arguments对象

  • arguments对象表示函数的实参集合,仅能够在函数体内可见,并可以直接访间。
  • 参数对象是一个伪类数组,不能够继承Array的原型方法。可以使用数组下标的形式访问每个实参,如参数[0]表示第一个实参
  • 通过修改length属性值,可以改变函数的实参个数。
function fn1(a,b,c) {
    console.log(arguments.length);//获取实参的个数
}
fn1(1,2,3,4)

//输入一组数字,求平均值的函数
function f() {
    //先获取到所有的实参 使用arguments
    var sum = 0;
    for (var i = 0; i < arguments.length; i++) {
        sum += arguments[i];
    }
    return sum / arguments.length;
}
console.log(f(12, 34, 54, 32, 100, 98, 45, 34, 12));


// 直接使用函数对象的length属性,就可以获取到函数的形参个数
function f3(a,b,c) {
    console.log(f3.length);//3
}
f3(1,2,3,4,5,6);


// 检测函数的形参和实参是否一致,如果不一致,则抛出错误
function f4(a,b,c,d) {
    if (arguments.length != f4.length) {
        throw new Error("参数不一致");
    }else{
        alert("go")
    }
}
f4(1,2,3,4,5)

使用callee

callee是arguments对象的属性,它引用当前argument对象所在的函数,使用该属性可以在函数体内调用自身。

// 检测函数的形参和实参是否一致,如果不一致,则抛出错误
function f4(a,b,c,d) {
    if (arguments.length != arguments.callee.length) {
        throw new Error("参数不一致");
    }else{
        alert("go")
    }
}
f4(1,2,3,4,5)

var a = 0;
(function (){
    a++;
    alert(1);
    if (a > 1){
        return;
    }
    arguments.callee()
})();

函数的练习

当点击不同的按钮的时候 让页面加载相应的颜色

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>函数的练习</title>
</head>
<body>
<button id="red">红色</button>
<button id="green">绿色</button>
<button id="blue">蓝色</button>
<button id="pink">粉色</button>

<script>
    /*
    * 当点击不同的按钮的时候 让页面加载相应的颜色
    * 改变屏幕的颜色 就是控制body的背景颜色
     */
    var oBody=document.getElementsByTagName("body")[0];
    var oRed = document.getElementById("red");
    var oGreen = document.getElementById("green");
    var oBlue = document.getElementById("blue");
    var oPink = document.getElementById("pink");

    function changeColor(col){
        oBody.style.backgroundColor = col;
    }
    oRed.onclick=function () {
        changeColor("red");
    }
    oGreen.onclick=function () {
        changeColor("green");
    }
    oBlue.onclick=function () {
        changeColor("blue");
    }
    oPink.onclick=function () {
        changeColor("pink");
    }

</script>
</body>
</html>

函数作用域

变量作用域

变量作用域(scope)是指变量在程序中可以访问的有效范围。也称为变量的可见性。分为全局变量和局部变量

  • 全局变量:变量在整个页面中都是可见的,可以被自由的访问
  • 局部变量,变量仅能在声明的函数内部可见,函数外是不允许访问的。
  • var声明的作用与是按照函数划分的
var a = 1;
function f() {
    console.log(a);
}
f();

function f2 (){
    var a = 2;
    function f3() {
        console.log(a);
    }
    f3();
}
// f3();//f3是局部函数 外部使用不了的
f2();
console.log(a);

执行上下文

  • JS引擎并不是一行行的解析和执行代码,而是一段段的去分析和执行,当执行一段代码时,先开始预处理,比如声明提升和函数提升
  • 在执行某段js代码的时候,会进行一个准备工作,这个准备工作用专业的说法 叫“执行上下文”,其实执行上下文也是在内存中开辟的一个空间
  • js可执行的代码分为3种类型, 全局代码 、 函数代码 、eval代码(忽略)
  • 每执行一段代码,都会创建相对应的执行上下文,在脚本中可能存在多个执行上下文
  • 因为有太多的执行上下文, JS创建了一个执行上下文栈(stack) 用来管理执行上下文
  • 当js开始解析程序的时候,最先遇到的全局代码,此时向执行上下文栈中 压入一个全局执行上下文,全局的一定是在整体运行结束以后才被清空
  • 当执行一个函数的时候 会创建一个函数的执行上下文,并压入到执行上下文栈中,只要函数执行完成,会将函数从栈里弹出
function fun3() {
    console.log("fun3");
}
function fun2() {
    fun3();
}
function fun1() {
    fun2();
}
fun1();

/*
      //用伪代码来实现上边代码执行上下文的流程
      //1.js引擎创建了一个执行上下文栈
      var Stack = [];
      //2.向栈中押入 全局执行上下文
      Stack = [globalContext]
      //3.在fun1中创建执行上下文  并压入栈中
      Stack.push(<fun1>Context)
      //4.在fun2中创建执行上下文  并压入栈中
      Stack.push(<fun2>Context)
      //5.在fun3中创建执行上下文  并压入栈中
      Stack.push(<fun3>Context)
      //6.fun3执行完毕
      Stack.pop();
      //7.fun2执行完毕
      Stack.pop();
      //8.fun1执行完毕
      Stack.pop();
      //9整体代码执行结束  全局执行上下文出去
*/

var scope = "hello";
function checkscope() {
    var scope = "world";
    function f() {
        return scope;
    }
    return f();
}
checkscope();
/*
    var Stack = [globalContext];
    Stack.push(<checkscope>Context);
    Stack.push(<f>Context);
    Stack.pop()
    Stack.pop()
*/

变量对象

  • 每个执行上下文 都有三个重要属性:1.变量对象(VO) 2.作用域链 3.this

  • 变量对象是 ECMAScript规范术语。在一个执行上下文中,变量对象才被激活,只有激活的变量对象,其各种属性才能被访问

  • 变量对象是与执行上下文相关的数据作用域,储存了在上下文中定义的变量和函数声明

全局上下文的变量对象

  • window是预定义对象,作为JS全局函数和全局属性的占位符(全局的变量和函数就是window对象属性和方法)
  • 全局执行上下文的变量对象其实就是全局对象window

函数上下文的变量对象

  • 进入执行上下文 不会立马执行代码,只进行分析。此时首先第一步,变量对象包括了函数所有的形参和实参
  • 检查所有声明的函数,由名称和对应值 组成一个变量对象的属性 被创建。如果变量对象已经有相同名字的属性,则完全替换
  • 检查所有的声明的变量,创建键值对儿
  • 变成变量对象的属性,如果变量名和已经声明的形参或函数相同,则变量声明不会干扰已经存在的这类属性
function foo(a) {
    var b = 2;
    function c() {}
    var d = function () {};
    b = 3;
}
foo(1, 2);

//模拟以上代码的变量对象:
var AO = {
    //arguments是保存所有的的实参
    arguments:{
        0:1,
        1:2,
        length:2
    },
    a:1,
    c:function c(){},
    b:undefined,
    d:undefined
}

//练习
function fn1(a) {
    console.log(a); //1
    var a = 2;
    console.log(a); //2
}
fn1(1);

function fn2() {
    console.log(foo); //函数
    function foo() {}
    var foo = 1;
}
fn2();

作用域链

  • 当代码在一个环境中执行时,会创建变量对象的一个作用域链( scope chain),作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。
  • 作用域链的前端,始终都是当前执行的代码所在环境的变量对象。
  • 全局执行环境的变量对象始终都是作用域链中的最后一个对象。
  • 标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级地向后回溯,直至找到标识符为止(如果找不到标识符,通常会导致错误发生)

this和调用对象

什么是this

  • javascript函数的作用域是静态的,但是函数的调用却是动态的,由于函数可以在不同的运行环境内执行,因此 JavaScript在函数体内定义了this关键字,用来获取当前的运行环境。
  • this是一个指针型变量,它动态引用当前的运行环境,具体来说,就是调用函数的对象。
  • this永远指向其所在函数的调用者,如果没有所有者则指向全局对象window

this的指向方式

  1. 默认绑定 :常用的函数调用类型:独立函数调用

    可以把这个规则看作是无法应用其他规则的时候 默认的规则,基本指向的是window

    function foo() {
        console.log(this);
    }
    foo(); //window
    
    var obj = {
        do: function () {
            foo(); //foo是直接使用不带任何修饰的函数引用进行调用,因此只能使用默认绑定 规则
        },
    };
    obj.do();
  2. 隐式绑定

    当函数引用有上下文对象的时候(obj),隐式绑定规则会把函数中的this绑定到这个上下文对象上

    function foo() {
        console.log(this.a);
    }
    var obj = {
        a: 2,
        foo: foo, //
    };
    obj.foo(); // //当foo调用的时候,它的落脚点确实是指向的obj对象,当函数引用有上下文对象的时候(obj),隐式绑定规则会把函数中的this绑定到这个上下文对象上
  3. 隐式绑定可能会出现隐式丢失的问题 :被隐式绑定的函数,会丢失了绑定对象

    function foo() {
        console.log(this.a);
    }
    var obj = {
        a: 2,
        foo: foo,
    };
    var fn1 = obj.foo;
    var a = "hello";
    fn1(); //hello 虽然fn1是obj.foo的一个引用,但是实际上它的引用是foo函数本身,因此fn1其实是一个不带任何修饰的函数调用,属于默认绑定
    
    //
    function foo() {
        console.log(this.a);
    }
    function doFoo(fn) {
        fn(); //传参也是隐式赋值,所以传递函数也是隐式赋值,赋值的是foo函数本身
    }
    var obj = {
        a: 2,
        foo: foo,
    };
    doFoo(obj.foo);
  4. 显式绑定

    function foo() {
        console.log(this.a);
    }
    var obj = {
        a: 2,
    };
    foo.call(obj); //2 //通过call 调用foo时,把foo的this强制的绑定给了obj上
  5. new绑定

    构造函数只是一些使用new操作符被调用的函数,使用new调用函数的时候,会构造一新的对象,这个时候 就把新的对象绑定给了函数的this上

    function Foo(a) {
        this.a = a;
    }
    var bar = new Foo(2);
    console.log(bar.a); //2

怎么判断this指向

  1. 函数是否在new中调用,如果是的话,this绑定的是新创建的对象
  2. 函数是否通过call、apply(显示绑定)调用,如果是,则this绑定的是执行的对象
  3. 函数是否在某个上下文对象中调用(隐式绑定),如果有,则this绑定在这个上下文对象上
  4. 如果以上都不是 则默认绑定 执行window
//obj.f在上面代码中,obj.f表示在obj对象上调用f函数,则调用对象为obj,此时this就指向obj,this.x就等于obj.x,即返回结果为2
//若把obj.f赋值给变量f1,然后在全局上下文中调用们函数,则f函数体的运行环境在全局上下文中执行,此时this就指向 window, this.x就等于 window.x,即返回结果为1
var x = 1;
var obj = {
    f:function(){
        console.log(this.x)
    },
    x:2
}
obj.f();
var f1 = obj.f;
f1();

其他练习

var x = 1;
var obj = {
    f:function(){
        console.log(this.x);
    }
    x:2,
}
  
(obj.f = obj.f)();//正确理解“运行环境”obj.f=obj.f是赋值表达式,把obj.f赋值给obj.f,obj.f是一个地址,把地址赋值给obj.f属性,表达式的运行环境发生在全局上下文中,所以此时函数f内的this就指向了全局上下文的调用对象 window
(false||obj.f)();//逻辑表达式,左侧操作数为false,则运算右侧操作数,返回obj.f的值,即引用地址。由于这个逻辑表达式运算发生在全局作用域内,此时的f函数内this就指向了全局对象
(obj.f,obj.f);//逗号运算表达式,逗号左侧和右侧的obj.f都是一个地址,都被运算一次,最后返回第2个操作数的值,即返回引用地址。由于这个操作发生在全局作用域内,所以f函数内this也指向了全局对象
(obj.f)//his指向obj对象,因为小括号不是一个运算符,它仅是一个逻辑分隔符,不执行运算,不会产生运行环境,当使用小括号调用函数时,此时生成的运行环境就是obj

作用域练习

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script>
    //demo1
    var v = "hello";
    (function(){
        alert(v);
        var v = "world";
    })();
    alert(v);

    //demo2
    (function(){
        alert(a+b+c);
        var a = "1";
        var f = function(){};
        var b = "2";
        var c = "3";
    })();

    //demo3
    (function(){
        f2();
        f1();
        var f1 = function(){
            alert(2)
        };
        function f2(){
            alert(1);
        }
    })();

    //demo4
    var a = 1;
    function outer(){
        var b = 2;
        function inner(){
            var c = 4;
            alert(a);
        }
        inner();
    }
    outer();

    //demo5
    var a = 1;
    function check(){
        a = 100;
        alert(a);
    }
    check();
    alert(a);

    //demo6
    var v = "hello";
    if(true){
        alert(v);
        var v = "world";
    }
    alert(v);

    //demo7
    function rain(){
        var x = 1;
        function man(){
            x = 100;
        }
        man();
        alert( x );
    }
    rain();

    //demo8
    var x = 1;
    function rain(){
        alert( x );
        var x = 'man';
        alert( x );
    }
    rain()


    //demo9
    function rain(){
        x = 100;
    }
    rain();
    alert( x );//100

    //demo10
    var t='a';
    function test2(){
        alert(t);
        var t='b';
        alert(t);
    }
    test2();

    //demo11
    var t='a';
    function test2(){
        alert(t);
        t='b';
        alert(t);
    }
    alert(t);
    test2();
    alert(t);

    //demo12
    var a="Hello";
    function test(){
        alert(a);
        a="World";
        alert(a);
    }
    test();

    //demo13
    var a =1;
    function test(){
        alert(a);
        a=4;
        alert(a)
        var a=2;
        alert(a);
    }
    test();
    alert(a);

    </script>
</body>
</html>

练习

  • 求1-n之间所有数的和

  • 求n-m之间所有数的和

  • 圆的面积

  • 求2个数中的最大值

  • 求3个数中的最大值

  • 判断一个数是否是素数(又叫质数,只能被1和自身整数的数)

  • 求任意个数的最大值

  • 求任意个数的和

posted @ 2023-01-12 21:29  z_bky  阅读(15)  评论(0)    收藏  举报