FE_函数(Function)的基本理解

1 函数(Function)的基本理解

函数就是在程序设计中,将一段代码封装起来,完成一个特定的功能,并给这段代码起一个名称,程序通过名称就可以执行这段代码。函数也是一个对象,也具有普通对象的功能,函数中可以封装一些代码,在需要的时候可以去调用函数来执行这些代码,使用typeof检查一个函数时会返回function。

1.1 函数的声明

// 函数的声明
function func(name, age, sex) {
    
    
    console.log(name, age, sex);
}

// 函数表达式
var func_ = function (name, age, sex) {
    
    
    console.log(name, age, sex);
}

JavaScript定义函数有两种方式:一种方式是显示定义函数;一种方式是匿名定义函数。显示定义函数通过函数声明来定义,定义语法如下:

  1. 显示定义函数
function functionname(parameters){
    
    函数代码}

其中,function是定义函数的关键字,functionname是函数名称,parameters是函数传入的参数,可以传入多个参数,每个参数之间用英文逗号分隔。JS函数不需要声明返回类型,当函数需要返回值时,直接使用return语句返回即可。

  1. 匿名函数
    通过一个表达式来定义,函数没有名称,定义的函数会赋值给一个变量,该变量指向函数的内存地址,当访问变量时,函数即被调用。
varfun = function(parameters){
    
    函数代码;}

其中,varfun是变量,用于存储函数的内存地址,function是定义函数的关键字,parameters是传入函数的参数,可以传入多个参数,每个参数之间用英文逗号分隔。

1.2 函数的调用

JS有多种方式来调用函数,有直接调用,表达式内调用,在事件响应中调用,通过链接调用。不管哪种方式调用函数,调用时都要写入函数名称,若是匿名函数,要写入变量名称。

  1. 直接调用就是函数调用语句占单独一行,直接调用比较适合没有返回值的函数。
<script type="text/javascript">
    function add() {
    
    
        num1 = parseInt(document.getElementById("op1").value);
        num2 = parseInt(document.getElementById("op2").value);
        alert(sum(num1, num2));
    }

    function sum(a, b) {
    
    
        return a + b;
    }
    add();
</script>

语句add()就是直接调用,该语句调用函数add()。

  1. 函数可以在表达式内调用,函数的返回值参与表达式的计算。在表达式内调用函数,一般适用于有返回值的函数。
function sum(a,b){
    
    return a+b;}

sum函数计算a和b两数的和,并使用return语句返回计算结果。调用sum函数就需要在表达式内调用。

    value = sum(10,90)
    value = sum(10,90) * 2
  1. 立即执行函数-函数定义完,立即被调用,立即执行函数往往只会执行一次
(function (name, age) {
    
    
    console.log(name, age)
})('zhaoshuai-la', 27);
  1. 调用函数时JS解析器不会检查实参的类型和个数,可以传递任意数据类型的值:
    如果实参的数量大于形参,多余实参将不会赋值;
    如果实参的数量小于形参,则没有对应实参的形参将会赋值undefined;

  2. 返回值,就是函数执行的结果:
    语法:return 值;
    该值就会成为函数的返回值,可以通过一个变量来接收返回值,
    return后边的代码都不会执行,一旦执行到return语句时,函数将会立刻退出。
    return后可以跟任意类型的值,可以是基本数据类型,也可以是一个对象。
    如果return后不跟值,或者是不写return则函数默认返回undefined。
    break、continue和return
    break-退出循环
    continue-跳过当次循环
    return-退出函数

  3. 参数,函数的实参也可以是任意的数据类型。

2 js函数中的 arguments

简单来说:

  1. arguments是一个类数组元素,它用来封装函数执行过程中的实参

  2. 所以即使不定义形参,也可以通过arguments来使用实参

  3. arguments中有一个属性callee表示当前执行的函数对象

  4. arguments 对象是所有(非箭头)函数中都可用的局部变量。你可以使用arguments 对象在函数中引用函数的参数。此对象包含传递给函数的每个参数,第一个参数在索引0处。例如,如果一个函数传递了三个参数,你可以以如下方式引用他们:

arguments[0]
arguments[1]
arguments[2]

参数也可以被设置:

arguments[0] = 'value';

arguments 是一个对象,不是一个 Array 。它类似于Array ,但除了length属性和索引元素之外没有任何Array 属性。例如,它没有 pop 方法。但是它可以被转换为一个真正的Array :

// 由于arguments不是 Array,所以无法使用 Array 的方法,所以通过这种方法转换为数组
 
var args = [].slice.call(arguments);  // 方式一
var args = Array.prototype.slice.call(arguments); // 方式二
 
// 下面是 es6 提供的语法
let args = Array.from(arguments)   // 方式一
let args = [...arguments]; // 方式二

  1. arguments上的属性
    arguments.callee:指向当前执行的函数(在 严格模式 下,第5版 ECMAScript (ES5) 禁止使用 arguments.callee() )
    argunments.length:指向传递给当前函数的参数数量

  2. arguments与剩余参数、默认参数和解构赋值参数的结合使用
    在严格模式下,剩余参数、默认参数和解构赋值参数的存在不会改变 arguments对象的行为,但是在非严格模式下就有所不同了:

function func(a) {
    
     
  arguments[0] = 99;   // 更新了arguments[0] 同样更新了a
  console.log(a);
}
func(10); // 99
 
// 并且
function func(a) {
    
     
  a = 99;              // 更新了a 同样更新了arguments[0] 
  console.log(arguments[0]);
}
func(10); // 99

当非严格模式中的函数没有包含剩余参数、默认参数和解构赋值,那么arguments对象中的值会跟踪参数的值(反之亦然)。看下面的代码:

function func(a = 55) {
    
     
  arguments[0] = 99; // updating arguments[0] does not also update a
  console.log(a);
}
func(10); // 10
 
//
 
function func(a = 55) {
    
     
  a = 99; // updating a does not also update arguments[0]
  console.log(arguments[0]);
}
func(10); // 10
 
 
function func(a = 55) {
    
     
  console.log(arguments[0]);
}
func(); // undefined

3 如何在 JavaScript 中使用 apply,call,bind - 作用改变this的指向

在这里插入图片描述
要检查函数是否为一个 Function 对象,我们可以使用以下代码进行判断,该代码段返回 true。

(function () {
    
     }).constructor === Function ? console.log(true) : console.log(false);
  1. apply() 方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。
function.apply(this,[argumentsArray])
  • thisArg 可选的。在 func 函数运行时使用的 this值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。
  • argsArray 可选的。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。
  • apply实现继承
function SuperType() {
    
    
    this.color = ['red', 'blue', 'yellow']
}

function SubType() {
    
    
    SuperType.apply(this);
}

var instance = new SubType();
instance.color.push('green');
console.log('instance1.color:', instance.color);
// instance1.color: [ 'red', 'blue', 'yellow', 'green' ]
  • 用 apply 将数组添加到另一个数组
var array = ['a', 'b'];
var elements = [1, 2, 3];
array.push(elements);
console.log(array);  // ['a', 'b', [1, 2, 3]]

以上代码中,当我们将一个数组 push 进另一个数组时,整个数组被视为一个元素直接 push 进去。但如果我们想要将数组 elemennts 中的元素分别 push 进数组 array 中呢?当然有很多方法可以这样做,在这里我们使用 apply()。

var array = ['a', 'b'];
var elements = [1, 2, 3];
[].push.apply(array, elements);
console.log(array); // ["a", "b", 1, 2, 3]

该例中,使用 apply 连接给定数组,参数为:数组 elements,this 指向变量 array,实现数组 elements 中的元素被 push 进 this 指向的对象(array)中。最终返回的结果为第二个数组中的每个元素被 push 进 this 指向的数组中。

  • 元素的最大值
var numbers = [53, 65, 25, 37, 78];
console.log(Math.max(numbers)); //NaN

JS 中 max 函数用于查找给定元素的最大值。但正如我们所见,如果给定值为数组,返回结果为 NaN。当然,JavaScript 中有很多方法可以解决,在这里我们使用 apply。

var numbers = [53, 65, 25, 37, 78];
console.log(Math.max.apply(null, numbers)); //78

当我们使用 apply 调用 Math.max() 时,得到了期望结果。apply 将 numbers 中所有值作为单独的参数,然后再调用 max 进行处理,最终返回数组中的最大值。

值得注意的是,我们使用 null 代替了 this。由于提供的参数是数字数组,即使使用了 this,它也仍会指向同一个数组,最终得到相同的结果。因此,这种情况下我们可以省略 this,改用 null 代替。也就是说,apply 函数中的 this 参数是一个可选参数。

  1. call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
function.call(thisArg,arg1,arg2,...)
  • thisArg: 在 fun 函数运行时指定的 this 值。
if(thisArg == undefined|null) this = window,if(thisArg == number|boolean|string) this == new Number()|new Boolean()| new String()
  • arg1, arg2, …:指定的参数列表。
  • 使用 call 方法调用父构造函数实现继承
function Father(name, age) {
    
    
    this.name = name;
    this.age = age;
    this.book = ['数据结构', '计算机网络', '算法']
    console.log('1.执行到Father---名字:' + this.name + '; 年龄:' + this.age + ' 书籍:' + this.book);

}

function Son(name, age) {
    
    
    Father.call(this, name, age);
    this.subject = '软件专业';
    console.log('2.执行到Son---专业:' + this.subject);
}

var son = new Son('gxm', '20');
son.book.push('javaScript');
console.log('3.改变Son.book:' + son.book);

/**
 * 1.执行到Father---名字:gxm; 年龄:20 书籍:数据结构,计算机网络,算法
 2.执行到Son---专业:软件专业
 3.改变Son.book:数据结构,计算机网络,算法,javaScript
 */

在该例子中,子构造函数Sun()通过调用父构造函数Father()的 call 方法来实现继承,此时Sun()函数中拥有了Father()函数中的属性。

  • 使用 call 方法调用函数并且指定上下文的 this
function play() {
    
    
    var reply = [this.player, '打', this.playName, '打了', this.playTimes].join(' ');
    console.log(reply);
}

var obj = {
    
    
    player: 'gxm', playName: '羽毛球', playTimes: '1小时'
};

play.call(obj); //gxm 打 羽毛球 打了 1小时

如果直接运行play()函数,结果就只会是打 打了。但指定了上下文就不一样了,play.call(obj)就是将play()函数的上下文从全局的window指定到obj对象上了。所以运行出来的结果过成为gxm 打 羽毛球 打了 1小时。

function foo() {
    
    
    return () => {
    
    
        return () => {
    
    
            return () => {
    
    
                console.log('id:', this.id);
            };
        };
    };
}

var f = foo.call({
    
    id: 1});

var t1 = f.call({
    
    id: 2})()(); // id: 1
var t2 = f().call({
    
    id: 3})(); // id: 1
var t3 = f()().call({
    
    id: 4}); // id: 1

上面代码之中,只有一个this,就是函数foo的this,所以t1、t2、t3都输出同样的结果。因为所有的内层函数都是箭头函数,都没有自己的this,它们的this其实都是最外层foo函数的this。

  • 使用 call 方法调用函数并且不指定第一个参数(argument)节

在下面的例子中,我们调用了 display 方法,但并没有传递它的第一个参数。如果没有传递第一个参数,this 的值将会被绑定为全局对象。

var sData = 'Wisen';

function display() {
    
    
  console.log('sData value is %s ', this.sData);
}

display.call();  // sData value is Wisen

注意:在严格模式下,this 的值将会是 undefined。

  1. bind() 方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入bind() 方法的第一个参数作为 this, 第二个以及以后的参数,加上绑定函数运行时本身的参数,按照顺序作为原函数的参数来调用原函数。
function.bind(this,arg1,arg2,arg3,...)

bind 方法与 call 方法类似,主要区别在于 bind 返回一个新函数,而 call 不返回。根据 ECMAScript 5 规范,bind 方法返回的函数是一种特殊类型的函数对象,称为绑定函数( BF )。BF 中包含原始函数对象,调用 BF 时会执行该函数。

var x = 9;
var module = {
    
    
    x: 81,
    getX: function () {
    
    
        return this.x;
    }
};
console.log(module.getX()); // 81

var retrieveX = module.getX;
console.log(retrieveX()); // 9

var boundGetX = retrieveX.bind(module);
console.log(boundGetX()); // 81

在上面的代码中,我们定义了一个变量 x 和一个对象 module,该对象中还定义了一个属性 x 以及一个返回 x 值的函数。

当调用函数 getX 时,它返回的是对象内定义的 x 的值,而不是全局作用域中的 x。

另一个变量在全局作用域中声明,并调用 module对象中的 getX 函数。但由于该变量处于全局作用域下,因此 getX 中的 this 指向全局作用域下的 x,返回 9。

最后又定义了另一个变量 boundGetX,该变量调用函数 retrieveX,与之前不同的是,这次将函数 retrieveX与对象 module 绑定,返回的是对象内 x 的值。这是由于 bind 将函数中的 this 指向对象中的 x 值而不是全局 x,因此输出 81。

4 this是函数的上下文对象

  1. 任何函数本质上都是通过某个对象来调用的,如果没有直接指定就是window
  2. 所有函数内部都有一个变量this
  3. 它的值是调用函数的当前对象
<script>
    function Person(color) {
    
    
        console.log(this)
        this.color = color;
        this.getColor = function () {
    
    
            console.log(this)
            return this.color;
        };
        this.setColor = function (color) {
    
    
            console.log(this)
            this.color = color;
        };
    }

    Person("red"); //this是谁? window

    const p = new Person("yello"); //this是谁? p

    p.getColor(); //this是谁? p

    const obj = {
    
    };
    //调用call会改变this指向-->让我的p函数成为`obj`的临时方法进行调用
    p.setColor.call(obj, "black"); //this是谁? obj

    const test = p.setColor;
    test(); //this是谁? window  -->因为直接调用了

    function fun1() {
    
    
        function fun2() {
    
    
            console.log(this);
        }

        fun2(); //this是谁? window
    }

    fun1();//调用fun1
</script>


5 箭头函数中的this指向问题

  1. 引出问题1:
radius = 200
let circle = {
    
    
    radius: 10,
    outer: function () {
    
    
        console.log(this) // ===> { radius: 10, outer: [Function: outer] }
        let inner = function () {
    
    
            console.log(this) // ===> window
            console.log(2 * this.radius) // 400
        }
        inner()
    }
}
circle.outer()
radius = 200
let circle = {
    
    
    radius: 10,
    outer: function () {
    
    
        console.log(this) // ===> { radius: 10, outer: [Function: outer] }
        let self = this
        let inner = function () {
    
    
            console.log(self) // ===> { radius: 10, outer: [Function: outer] }
            console.log(2 * self.radius)
        }
        inner() // window 调用 this 为 window
    }
}
circle.outer()
  1. 引出问题2:
let circle = {
    
    
    radius: 10,
    outer: function () {
    
    
        let inner = function () {
    
    
            console.log(2 * this.radius) // 20
        }
        inner = inner.bind(this);
        inner()
    }
}
circle.outer()
  1. 引出问题3:
let circle = {
    
    
    radius: 10,
    outer: function () {
    
    
        let inner = () => {
    
    
            console.log(this) // ===> { radius: 10, outer: [Function: outer] }
            console.log(2 * this.radius) // 20
        }
        inner()
    }
}
circle.outer()

5.1 箭头函数本身不具有this,箭头函数的this指向函数创建时所在作用域空间的this

  1. 箭头函数本身不具有this,箭头函数的this指向函数创建时所在作用域空间的this
<script>
    var b = 'hello';
    let obj = {
    
    
        b: 'HELLO',
        foo: () => {
    
    
            console.log(this) // window
            console.log(this.b); // hello 这个是属性,在定义obj时就创建了这个箭头函数,obj对象是在window作用域创建的
        }
    }
    obj.foo()
</script>
  1. 箭头函数中的this是不能被call apply去改变
<script>
    class Foo {
    
    
        print = () => {
    
    
            console.log(this)  // 生成foo实例时创建, 此时的 this 指向的是 foo 实例
            console.log(this.x) // 1
        };

        constructor() {
    
    
            this.x = 1
        }
    }

    let foo = new Foo();
    foo.print()
    foo.print.call({
    
    x: 2}) // 箭头函数中的this是不能被call apply去改变
</script>

在这里插入图片描述

  1. 箭头函数中的this = 箭头函数创建时所在作用域空间的this
<script>

    function printThis() {
    
     // printThis调用时所在作用域空间的this
        console.log(this) // [1]
        let print = () => console.log(this) // [1]
        print()
    }

    printThis.call([1]) 

    // 箭头函数中的this = 箭头函数创建时所在作用域空间的this
</script>

6 作用域 一个变量的作用范围

全局作用域:

  1. 直接在script标签中编写的代码都运行在全局作用域中
  2. 全局作用域在打开页面时创建,在页面关闭时销毁
  3. 全局作用域中有一个全局对象window,window对象由浏览器提供,可以在页面中直接使用,它代表的是整个的浏览器的窗口。
  4. 在全局作用域中创建的变量都会作为window对象的属性保存
  5. 在全局作用域中创建的变量和函数可以在页面的任意位置访问
  6. 在函数作用域中也可以访问到全局作用域的变量
  7. 尽量不要在全局中创建变量

函数作用域:

  1. 函数作用域是函数执行时创建的作用域,每次调用函数都会创建一个新的函数作用域
  2. 函数作用域在函数执行时创建,在函数执行结束时销毁
  3. 在函数作用域中创建的变量,不能在全局中访问
  4. 当在函数作用域中使用一个变量时,它会先在自身作用域中寻找,
    如果找到了则直接使用,如果没有找到则到上一级作用域中寻找,
    如果找到了则使用,找不到则继续向上找 ,

7 变量的声明提前

  1. 在全局作用域中,使用var关键字声明的变量会在所有的代码执行之前被声明,但是不会赋值,所以我们可以在变量声明前使用变量。但是不使用var关键字声明的变量不会被声明提前。
  2. 在函数作用域中,也具有该特性,使用var关键字声明的变量会在函数所有的代码执行前被声明.
  3. 如果没有使用var关键字声明变量,则变量会变成全局变量
   <script>
      function fun() {
    
    
          a = 100
      }
      fun()
      console.log(a) // 100
   </script>

8 函数的声明提前

  1. 在全局作用域中,使用函数声明创建的函数 function fun(){}, 会在所有的代码执行之前被创建,也就是我们可以在函数声明前去调用函数,
  2. 但是使用函数表达式 var fun = function(){} 创建的函数没有该特性
  3. 在函数作用域中,使用函数声明创建的函数,会在所有的函数中的代码执行之前就被创建好了。

9 构造函数是专门用来创建对象的函数

  1. 一个构造函数我们也可以称为一个类
  2. 通过一个构造函数创建的对象,我们称该对象是这个构造函数的实例
  3. 通过同一个构造函数创建的对象,我们称为类对象
  4. 构造函数就是一个普通的函数,只是他的调用方式不同,如果直接调用,它就是一个普通函数,如果使用new来调用,则它就是一个构造函数
<script>
    function Person(name, age, gender) {
    
    
        console.log('this - ', this)
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.sayHi = () => {
    
    
            console.log(this)
            console.log('hello-zhaoshuai-lc')
        }
    }

    var person = new Person('zhaoshuai-lc', 22, 'male');
    person.sayHi() // person
</script>

构造函数的执行流程:

  1. 创建一个新的对象
  2. 将新的对象作为函数的上下文对象(this)
  3. 执行函数中的代码
  4. 将新建的对象返回

猜你喜欢

转载自blog.csdn.net/zs18753479279/article/details/130680180
fe