# JavaScript 函数

函数的概念

什么是函数

  • 在 JS 里面,可能会定义非常多的相同代码或者功能相似的代码,这些代码可能需要大量重复使用。虽然 for 循环语句也能实现一些简单的重复操作,但是比较具有局限性,此时我们就可以使用 JS 中的函数。

  • 函数:就是封装了一段可被重复调用执行的代码块。通过此代码块可以实现大量代码的重复使用。

    • 不使用函数的弊端:
      • 冗余代码太多
      • 需求变更, 需要修改很多的代码
    • 使用函数的好处:
      • 冗余代码变少了
      • 需求变更, 需要修改的代码变少了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    // 不使用函数:
// 0. 要求计算两个变量的和
let num1 = 10;
let num2 = 20;
let res = num1 + num2;
console.log(res);

let value1 = 30;
let value2 = 20;
let res2 = value1 +value2;
console.log(res2);

// 1. 求 1~100的累加和
var sum = 0;
for (var i = 1; i <= 100; i++) {
sum += i;
}
console.log(sum);

// 2. 求 10~50的累加和
var sum = 0;
for (var i = 10; i <= 50; i++) {
sum += i;
}
console.log(sum);
1
2
3
4
5
6
7
8
9
10
11
12
// 使用函数
// 3. 函数就是封装了一段可以被重复执行调用的代码块 目的: 就是让大量代码重复使用
function getSum(num1, num2) {
var sum = 0;
for (var i = num1; i <= num2; i++) {
sum += i;
}
console.log(sum);
}
getSum(1, 100); // 5050
getSum(10, 50); // 1230
getSum(1, 1000);// 500500

函数的使用

声明函数

  • 函数定义步骤
    • 书写函数的固定格式
    • 给函数起一个有意义的名称
      • 为了提升代码的阅读性
      • 函数名称也是标识符的一种, 所以也需要遵守标识符的命名规则和规范
    • 确定函数的形参列表
      • 看看使用函数的时候是否需要传入一些辅助的数据
    • 将需要封装的代码书写到 { } 中
    • 确定函数的返回值
      • 可以通过 return 数据; 的格式, 将函数中的计算结果返回给函数的调用者
1
2
3
4
// 声明函数
function 函数名() {
//函数体代码
}
  • function 是声明函数的关键字,必须小写
  • 由于函数一般是为了实现某个功能才定义的,所以通常我们将函数名命名为动词,比如 getSum

匿名函数

1
2
3
4
5
6
7
8
// JavaScript中的函数和数组一样, 都是引用数据类型(对象类型)
// 既然是一种数据类型, 所以也可以保存到一个变量中
// 将一个函数保存到一个变量中
// 将来可以通过变量名称找到函数并执行函数
let say = function () {
console.log("hello world");
}
say();

调用函数

1
2
// 调用函数
函数名(); // 通过调用函数名来执行函数体代码
  • 调用的时候千万不要忘记添加小括号
  • 口诀:函数不调用,自己不执行。
  • 注意:声明函数本身并不会执行代码,只有调用函数时才会执行函数体代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 函数使用分为两步: 声明函数 和 调用函数
// 1. 声明函数
// function 函数名() {
// // 函数体
// }
function sayHi() {
console.log('hi~~');
}
// (1) function 声明函数的关键字 全部小写
// (2) 函数是做某件事情,函数名一般是动词 sayHi
// (3) 函数不调用自己不执行

// 2. 调用函数
// 函数名();
sayHi();
// 调用函数的时候千万不要忘记加小括号

函数的封装

  • 函数的封装是把一个或者多个功能通过函数的方式封装起来,对外只提供一个简单的函数接口

  • 简单理解:封装类似于将电脑配件整合组装到机箱中

案例-求1-100累加和

1
2
3
4
5
6
7
8
9
10
11
12
13
// 利用函数计算1-100之间的累加和 
// 1. 声明函数
function getSum() {
var sum = 0;
for (var i = 1; i <= 100; i++) {
sum += i;
}
console.log(sum);
}

// 2. 调用函数
getSum();
getSum();

函数的使用小结

  • 01 函数是做什么的(作用)?
  • 02 声明函数用什么关键词?
  • 03 如何调用函数?
  • 04 封装是什么意思?

函数的参数

形参和实参

  • 在声明函数时,可以在函数名称后面的小括号中添加一些参数,这些参数被称为形参,而在调用该函数时,同样也需要传递相应的参数,这些参数被称为实参。

image-20220128205809979

  • 参数的作用 : 在函数内部某些值不能固定,我们可以通过参数在调用函数时传递不同的值进去。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 1. 函数可以重复相同的代码
function cook() {
console.log('酸辣土豆丝');
}
cook();
cook();

// 2. 我们可以利用函数的参数实现函数重复不同的代码
// function 函数名(形参1,形参2,...) { // 在声明函数的小括号里面的是 形参 (形式上的参数)
// 函数体
// }
// 函数名(实参1,实参2...); // 在函数调用的小括号里面是实参(实际的参数)

// 3. 形参和实参的执行过程
function cook(aru) { // 形参是接受实参的 aru = '酸辣土豆丝' 形参类似于一个变量
console.log(aru);
}
cook('酸辣土豆丝');
cook('大肘子');

// 4. 函数的参数可以有,也可以没有,看实际是否需要传参,形参个数不限
// 一个函数可以有形参也可以没有形参(零个或多个)
// 什么是形参? 定义函数时函数括号中的变量我们就称之为形参
// 没有形参的函数
function say() {
console.log("hello world");
}
say();

// 有形参的函数
function say(name) {
console.log("hello " + name);
}
say("lnj");

案例-求任意两个数的和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 1. 利用函数求任意两个数的和
function getSum(num1, num2) {
console.log(num1 + num2);
}
getSum(1, 3);
getSum(3, 8);

// 2. 利用函数求任意两个数之间的和
function getSums(start, end) {
var sum = 0;
for (var i = start; i <= end; i++) {
sum += i;
}
console.log(sum);
}
getSums(1, 100); // 5050
getSums(1, 10); // 55

// 3. 注意点
// (1) 多个参数之间用逗号隔开
// (2) 形参可以看做是不用声明的变量

函数参数的传递过程

1
2
3
4
5
6
7
// 声明函数
function getSum(num1, num2) {
console.log(num1 + num2);
}
// 调用函数
getSum(1, 3); // 4
getSum(6, 5); // 11
  • 调用的时候实参是传递给形参的
  • 形参简单理解为:不用声明的变量
  • 实参和形参的多个参数之间用逗号(,)分隔

形参与实参个数不匹配

image-20220128210203900

1
2
3
4
5
6
7
function sum(num1, num2) {
console.log(num1 + num2);
}
sum(100, 200); // 300 形参和实参个数相等,输出正确结果
sum(100, 400, 500, 700); // 500 实参个数多于形参,只取到形参的个数
sum(200); // NaN 实参个数少于形参,多的形参定义为undefined,结果为NaN
//注意:在JavaScript中,形参的默认值是undefined。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 函数形参实参个数匹配
function getSum(num1, num2) {
console.log(num1 + num2);
}

// 1. 如果实参的个数和形参的个数一致 则正常输出结果
getSum(1, 2);

// 2. 如果实参的个数多于形参的个数 ,会按照形参的个数去接收参数
getSum(1, 2, 3);

// 3. 如果实参的个数小于形参的个数, 多于的形参定义为 undefined 最终的结果就是 NaN
// 形参可以看做是不用声明的变量 ,num2 是一个变量但是没有接收值,结果就是undefined
getSum(1); // NaN
// 建议 我们尽量让实参的个数和形参相匹配

函数形参的默认值

  • 在 ES6 之前可以通过逻辑运算符来给形参指定默认值
1
2
3
4
5
6
7
8
9
function getSum(a, b) {
// 格式: 条件A || 条件B
// 如果条件A成立, 那么就返回条件A
// 如果条件A不成立, 无论条件B是否成立, 都会返回条件B
a = a || "指趣学院";
b = b || "知播渔教育";
console.log(a, b); // 123 'abc'
}
getSum(123, "abc"); // 123 'abc' //若传递了实参,则实参会覆盖默认值,使用实参
  • 从 ES6 开始, 可以直接在形参后面通过 = 指定默认值
1
2
3
4
5
function getSum(a = "指趣学院", b = "知播渔教育") {
console.log(a, b);
}
getSum(); // 指趣学院 李南江
getSum(123, "abc"); // 123 'abc' //若传递了实参,则实参会覆盖默认值,使用实参
  • ES6 开始的默认值还可以从其它的函数中获取
1
2
3
4
5
6
7
8
function getSum(a = "指趣学院", b = getDefault()) { // 形参 b 的默认值就是 getDefault 函数调用后的返回值
console.log(a, b);
}
getSum(); // 指趣学院 李南江
getSum(123, "abc"); // 123 'abc' //若传递了实参,则实参会覆盖默认值,使用实参
function getDefault() {
return "李南江"
}

函数作为参数传递

1
2
3
4
5
6
7
8
9
// 将函数作为其他函数的参数        
let say = function () {
console.log("hello world");
}

function test(fn) { // let fn = say;
fn();
}
test(say); // hello world

函数参数小结

  • 函数可以带参数也可以不带参数

  • 声明函数的时候,函数名括号里面的是形参,形参的默认值为 undefined

  • 调用函数的时候,函数名括号里面的是实参

  • 多个参数中间用逗号分隔

  • 形参的个数可以和实参个数不匹配,但是结果不可预计,我们尽量要匹配

函数的返回值

函数返回值

  • 有的时候,我们会希望函数将值返回给调用者,此时通过使用 return 语句就可以实现。

  • return 语句的语法格式如下:

1
2
3
4
5
6
7
// 声明函数
function 函数名(){
//函数体...
return 需要返回的值;
}
// 调用函数
函数名(); // 此时调用函数就可以得到函数体内return 后面的值
  • 在使用 return 语句时,return 语句后面的代码会停止执行,并返回指定的值
  • 如果函数没有 return ,返回的值是 undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
// 1.函数是做某件事或者实现某种功能
function cook(aru) {
console.log(aru);
}
cook('大肘子');

// 2. 函数的返回值格式
function 函数名() {
return 需要返回的结果;
}
函数名();
// (1) 函数只是实现某种功能,最终的结果需要返回给函数的调用者函数名() 通过return 实现的
// (2) 只要函数遇到 return , 就把后面的结果返回给函数的调用者, 函数名() = return后面的结果

return 语句

  • 有的时候,我们会希望函数将值返回给调用者,此时通过使用 return 语句就可以实现。

  • 例如,声明了一个 sum() 函数,该函数的返回值为 666,其代码如下:

1
2
3
4
5
6
7
// 声明函数
function sum(){
...
return 666
}
// 调用函数
sum(); // 此时 sum 的值就等于666,因为 return 语句会把自身后面的值返回给调用者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 3. 代码验证
function getResult() {
return 666;
}
getResult();
console.log(getResult()); // 666

function cook(aru) {
return aru;
}
console.log(cook('大肘子')); // 大肘子

// 4. 求任意两个数的和
function getSum(num1, num2) {
return num1 + num2;
}
console.log(getSum(1, 2)); // 3

案例-求两个数的最大值

1
2
3
4
5
6
7
8
9
10
11
// 利用函数 求两个数的最大值
function getMax(num1, num2) {
// if (num1 > num2) {
// return num1;
// } else {
// return num2;
// }
return num1 > num2 ? num1 : num2;
}
console.log(getMax(1, 3)); // 3
console.log(getMax(11, 3)); // 11

案例-求数组中的最大值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 利用函数求数组 [5,2,99,101,67,77] 中的最大数值。
function getArrMax(arr) { // arr 接受一个数组 arr = [5,2,99,101,67,77]
var max = arr[0];
for (var i = 1; i <= arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
}
// getArrMax([5, 2, 99, 101, 67, 77]); // 实参是一个数组送过去
// 在我们实际开发里面,我们经常用一个变量来接受 函数的返回结果 使用更简单
// var re = getArrMax([5, 2, 99, 101, 67, 77]);
var res = getArrMax([3, 77, 44, 99, 143]);
console.log(res);

return终止函数

  • return 语句之后的代码不被执行。
1
2
3
4
5
6
7
function add(num1,num2){
//函数体
return num1 + num2; // 注意:return 后的代码不执行
alert('我不会被执行,因为前面有 return');
}
var resNum = add(21,6); // 调用函数,传入两个实参,并通过 resNum 接收函数返回值
alert(resNum); // 27
1
2
3
4
5
6
// 1. return 会终止函数
function getSum(num1, num2) {
return num1 + num2; // return 后面的代码不会被执行
alert('我是不会被执行的哦!')
}
console.log(getSum(1, 2));

return只能返回一个值

  • return 只能返回一个值。如果用逗号隔开多个值,以最后一个为准。
1
2
3
4
5
// 2. return 只能返回一个值
function fn(num1, num2) {
return num1, num2; // 返回的结果是最后一个值
}
console.log(fn(1, 2));
1
2
3
4
5
6
7
8
9
10
11
12
13
// 2.一个函数可以有返回值也可以没有返回值
// 没有返回值的函数
function say() {
console.log("hello world");
}
say(); // 不写返回值,函数调用会默认返回 undefined

// 有返回值的函数
function getSum(a, b) {
return a + b;
}
let res = getSum(10 , 20);
console.log(res);

函数作为返回值

1
2
3
4
5
6
7
8
9
10
11
// 将函数作为其他函数的返回值
function test() {
// 注意点: 在其它编程语言中函数是不可以嵌套定义的,
// 但是在JavaScript中函数是可以嵌套定义的
let say = function () {
console.log("hello world");
}
return say;
}
let fn = test(); // let fn = say;
fn();

返回值案例

  • 案例:创建一个函数,实现两个数之间的加减乘除运算,并将结果返回
1
2
3
4
5
6
7
8
9
var a = parseFloat(prompt('请输入第一个数'));
var b = parseFloat(prompt('请输入第二个数'));

function count(a, b) {
var arr = [a + b, a - b, a * b, a / b];
return arr;
}
var result = count(a, b);
console.log(result);
1
2
3
4
5
6
// 3.  我们求任意两个数的加减乘除的结果
function getResult(num1, num2) {
return [num1 + num2, num1 - num2, num1 * num2, num1 / num2];
}
var re = getResult(1, 2); // 返回的是一个数组
console.log(re);

函数的默认返回值

  • 函数没有 return ,则默认返回 undefined.

  • 函数都是有返回值的

    • 如果有return, 则返回 return 后面的值
    • 如果没有return ,则返回 undefined
1
2
3
4
5
6
7
8
9
10
// 4. 我们的函数如果有return 则返回的是 return 后面的值,如果函数没有 return 则返回undefined
function fun1() {
return 666;
}
console.log(fun1()); // 返回 666

function fun2() {
console.log(888);
}
console.log(fun2()); // undefined ,函数返回的结果是 undefined

break/continue/return

  • break :结束当前的循环体(如 for、while)
  • continue :跳出本次循环,继续执行下次循环(如 for、while)
  • return :不仅可以退出循环,还能够返回 return 语句中的值,同时还可以结束当前的函数体内的代码
1
2
3
4
5
6
7
8
9
// 4.return的作用和break相似, 所以return后面不能编写任何语句(永远执行不到)
// break作用立即结束switch语句或者循环语句
// return作用立即结束当前所在函数
function say() {
console.log("hello world");
return;
console.log("return后面的代码");// 不会执行
}
say();

其他案例

  • 写一个函数,用户输入任意两个数字的任意算术运算(简单的计算器小功能),并能弹出运算后的结果。

  • 写一个函数,用户输入任意两个数字的最大值,并能出弹运算后的结果。

  • 写一个函数,用户输入任意三个不同数字的最大值,并能弹出运算后的结果。

  • 写一个函数,用户输入一个数判断是否是素数,并返弹出回值(又叫质数,只能被1和自身整数的数)

arguments的使用

console.log()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1. 因为console.log();也是通过()来调用的, 所以log也是一个函数
// 2. log函数的特点
// 可以接收1个或多个参数
// 3. 为什么log函数可以接收1个或多个参数
// 内部的实现原理就用到了arguments
// 4. arguments的作用
// 保存所有传递给函数的实参
function say() {
console.log("hello world");
}
window.say();

console.log(1);
console.log(1, 2);
console.log(1, 2, 3);

arguments

  • 当我们不确定有多少个参数传递的时候,可以用 arguments 来获取。在 JavaScript 中,arguments 实际上它是当前函数的一个内置对象。所有函数都内置了一个 arguments 对象,arguments 对象中存储了传递的所有实参

  • arguments 展示形式是一个伪数组,因此可以进行遍历。

  • 伪数组具有以下特点:

    • 具有 length 属性
  • 按索引方式储存数据

    • 不具有数组的 push , pop 等数组方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// arguments 的使用  只有函数才有 arguments 对象,而且是每个函数都内置好了这个arguments
function fn() {
// console.log(arguments); // 里面存储了所有传递过来的实参 arguments = [1,2,3]
// console.log(arguments.length);
// console.log(arguments[2]);
// 我们可以按照数组的方式遍历arguments
for (var i = 0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
fn(1, 2, 3);
fn(1, 2, 3, 4, 5);
// 伪数组 并不是真正意义上的数组
// 1. 具有数组的 length 属性
// 2. 按照索引的方式进行存储的
// 3. 它没有真正数组的一些方法 pop() push() 等等

案例-求任意个数的最大值

1
2
3
4
5
6
7
8
9
10
11
12
function maxValue() {
var max = arguments[0];
for (var i = 0; i < arguments.length; i++) {
if (max < arguments[i]) {
max = arguments[i];
}
}
return max;
}

console.log(maxValue(2, 4, 5, 9)); // 9
console.log(maxValue(12, 4, 9)); // 12
1
2
3
4
5
6
7
8
9
10
11
12
13
// 利用函数求任意个数的最大值
function getMax() { // arguments = [1,2,3]
var max = arguments[0];
for (var i = 1; i < arguments.length; i++) {
if (arguments[i] > max) {
max = arguments[i];
}
}
return max;
}
console.log(getMax(1, 2, 3)); // 3
console.log(getMax(1, 2, 3, 4, 5)); // 5
console.log(getMax(11, 2, 34, 444, 5, 100)); // 444

扩展运算符

  • 扩展运算符在等号左边, 将剩余的数据打包到一个新的数组中,只能写在最后
1
let [a, ...b] = [1, 3, 5]; // a = 1; b = [3, 5];
  • 扩展运算符在等号右边, 将数组中的数据解开
1
2
3
let arr1 = [1, 3, 5];
let arr2 = [2, 4, 6];
let arr = [...arr1, ...arr2]; //等价于: let arr = [1, 3, 5, 2, 4, 6];
  • 扩展运算符在函数的形参列表中的作用
    • 将传递给函数的所有实参打包到一个数组中
    • 和在等号左边一样, 也只能写在形参列表的最后,否则会报错
1
2
3
4
5
6
7
8
9
10
11
function getSum(...values) {
console.log(values); // [10, 20, 30, 40]
let sum = 0;
for (let i = 0; i < values.length; i++){
let num = values[i];
sum += num;
}
return sum;
}
let res = getSum(10, 20, 30, 40);
console.log(res); // 100
1
2
3
4
5
function getSum(a, ...values) {
console.log(a); // 10
console.log(values); // [20, 30]
}
getSum(10, 20 , 30);

函数案例

函数封装-翻转数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 利用函数翻转任意数组 reverse 翻转
function reverse(arr) {
var newArr = [];
for (var i = arr.length - 1; i >= 0; i--) {
newArr[newArr.length] = arr[i];
}
return newArr;
}

var arr1 = reverse([1, 3, 4, 6, 9]);
console.log(arr1); // [9, 6, 4, 3, 1]

var arr2 = reverse(['red', 'pink', 'blue']);
console.log(arr2); // ['blue', 'pink', 'red']

函数封装-对数组冒泡排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 利用函数冒泡排序 sort 排序
function sort(arr) {
for (var i = 0; i < arr.length - 1; i++) {
for (var j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
var temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
return arr;
}

var arr1 = sort([1, 4, 2, 9]);
console.log(arr1); // [1, 2, 4, 9]

var arr2 = sort([11, 7, 22, 999]);
console.log(arr2); // [7, 11, 22, 999]

判断闰年

  • 要求:输入一个年份,判断是否是闰年(闰年:能被4整除并且不能被100整数,或者能被400整除)
1
2
3
4
5
6
7
8
9
10
11
// 利用函数判断闰年
function isRunYear(year) {
// 如果是闰年我们返回 true 否则 返回 false
var flag = false;
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) {
flag = true;
}
return flag;
}
console.log(isRunYear(2000));
console.log(isRunYear(1999));

函数调用函数

函数互相调用

  • 函数可以调用另外一个函数.

  • 因为每个函数都是独立的代码块,用于完成特殊任务,因此经常会用到函数相互调用的情况。

1
2
3
4
5
6
7
8
9
10
function fn1() {
console.log(111);
fn2();
console.log('fn1');
}
function fn2() {
console.log(222);
console.log('fn2');
}
fn1();

案例-判断2月的天数

  • 如果是闰年,则 2 月份是 29 天, 如果是平年,则 2 月份是 28 天
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 定义判断是否为闰年的函数
function isRunYear(year) {
// 如果是闰年我们返回 true 否则 返回 false
var flag = false;
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) {
flag = true;
}
return flag;
}

// 用户输入年份,输出当前年份2月份的天数
function backDay() {
var year = prompt('请您输入年份:');
if (isRunYear(year)) { // 调用函数需要加小括号
alert('当前年份是闰年2月份有29天');
} else {
alert('当前年份是平年2月份有28天');
}
}
backDay();

函数的声明方式

命名函数

  • 利用函数关键字 function 自定义函数方式。
1
2
3
4
// 声明定义方式
function fn() {...}
// 调用
fn();
  • 因为有名字,所以也被称为命名函数
  • 调用函数的代码既可以放到声明函数的前面,也可以放在声明函数的后面
1
2
3
4
5
// 1. 利用函数关键字自定义函数(命名函数)
function fn() {
//函数体...
}
fn();

匿名函数

  • 利用函数表达式方式的写法如下:
1
2
3
4
// 这是函数表达式写法,匿名函数后面跟分号结束
var fn = function(){...};
// 调用的方式,函数调用必须写到函数体下面
fn();
  • 因为函数没有名字,所以也被称为匿名函数
  • 这个 fn 里面存储的是一个函数
  • 函数表达式方式原理跟声明变量方式是一致的
  • 函数调用的代码必须写到函数体后面,否则会报错,因为 undefined 不是函数,不能加括号调用执行
1
2
3
4
5
6
7
8
9
10
// 2. 函数表达式(匿名函数) 
// var 变量名 = function() {};
var fun = function(aru) {
console.log('我是函数表达式');
console.log(aru);
}
fun('pink老师');
// (1) fun是变量名 不是函数名
// (2) 函数表达式声明方式跟声明变量差不多,只不过变量里面存的是值 而 函数表达式里面存的是函数
// (3) 函数表达式也可以进行传递参数

箭头函数

  • 什么是箭头函数?

    • 箭头函数是 ES6 中新增的一种定义函数的格式
    • 目的: 就是为了简化定义函数的代码
  • 在 ES6 之前如何定义函数

1
2
3
4
5
6
7
function 函数名称(形参列表){
//需要封装的代码;
}

let 函数名称 = function(形参列表){
//需要封装的代码;
}
  • 从 ES6 开始如何定义函数
1
2
3
let 函数名称 = (形参列表) =>{
// 需要封装的代码;
}
1
2
3
4
5
6
7
8
// function say() {
// console.log("hello lnj");
// }

let say = () => {
console.log("hello lnj");
}
say();
  • 箭头函数的注意点
    • 在箭头函数中如果只有一个形参, 那么()可以省略
    • 在箭头函数中如果{}中只有一句代码, 那么{}也可以省略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    // ES6之前的函数定义
function say(name) {
console.log("hello " + name);
}
say("it666");

// ES6 的箭头函数
let say = (name) => {
console.log("hello " + name);
}
say("it666");

// 只有一个形参,可以省略小括号
let say = name => {
console.log("hello " + name);
}
say("it666");

// 函数体只有一句代码,可以省略大括号
let say = name => console.log("hello " + name);
say("it666");

递归函数

  • 什么是递归函数?
    • 递归函数就是在函数中自己调用自己, 我们就称之为递归函数
    • 递归函数在一定程度上可以实现循环的功能
1
2
3
4
5
6
7
8
9
10
11
12
    /*
需求: 要求用户输入密码, 判断输入密码是否正确(假设正确密码是123456)
如果正确, 输出"欢迎回来"
如果不正确, 要求用户重新输入
*/

// 使用do..whild..循环实现
let pwd = -1;
do{
pwd = prompt("请输入密码");
}while (pwd !== "123456");
alert("欢迎回来");
1
2
3
4
5
6
7
8
9
10
11
12
    // 使用递归函数实现 
function login() {
// 1.接收用户输入的密码
let pwd = prompt("请输入密码");
// 2.判断密码是否正确
if(pwd !== "123456"){
login();
}
// 3.输出欢迎回来
alert("欢迎回来");
}
login();
  • 递归函数的注意点
    • 每次调用递归函数都会开辟一块新的存储空间, 所以性能不是很好

立即执行函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<body>
<script>
// 1.立即执行函数: 不需要调用,立马能够自己执行的函数
function fn() {
console.log(1);
}
fn();

// 2. 写法 也可以传递参数进来
// 1.(function() {})()
(function(a, b) {
console.log(a + b);
var num = 10;
})(1, 2); // 第二个小括号可以看做是调用函数

// 2. (function(){}());
(function sum(a, b) {
console.log(a + b);
var num = 10; // 局部变量
}(2, 3));

// 3. 立即执行函数最大的作用就是独立创建了一个作用域, 里面所有的变量都是局部变量, 不会有命名冲突的情况
</script>
</body>

JavaScript作用域

作用域概述

  • 通常来说, —段程序代码中所用到的名字并不总是有效和可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。作用域的使用增强了程序的可靠性,减少了名字冲突。

作用域的分类

  • 在 JavaScript 中 { } 外面的作用域, 我们称之为全局作用域

  • 在 JavaScript 中函数后面 { } 中的的作用域, 我们称之为”局部作用域”

  • 在 ES6 中只要 { } 没有和函数结合在一起, 那么应该 “块级作用域”

  • 块级作用域和局部作用域区别

    • 在块级作用域中通过 var 定义的变量是全局变量
    • 在局部作用域中通过 var 定义的变量是局部变量
  • 无论是在块级作用域还是在局部作用域, 省略变量前面的 let 或者 var 就会变成一个全局变量

  • 在 JavaScript 中,根据作用域的不同,变量可以分为两种:

    • 全局变量
    • 局部变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
// 块级作用域
}

if(false){
// 块级作用域
}

while (false){
// 块级作用域
}

for(;;){
// 块级作用域
}

do{
// 块级作用域
}while (false);

switch () {
// 块级作用域
}

function say() {
// 局部作用域
}
1
2
3
4
5
{
// 块级作用域
var num = 123; // 全局变量
}
console.log(num); // 123
1
2
3
4
5
function test() {
var value = 666; // 局部变量
}
test();
console.log(value); // 报错
1
2
3
4
if(true){
var num = 666; // 全局变量
}
console.log(num); // 666
1
2
3
4
5
6
7
function test() {
// var num = 123; // 局部变量
// let num = 123; // 局部变量
num = 123; // 全局变量
}
test();
console.log(num);

变量

  • 在 JavaScript 中定义变量有两种方式
    • ES6 之前: var 变量名称;
    • ES6 开始: let 变量名称;
  • 两种定义变量方式的区别
1
2
3
4
5
6
7
8
9
// 2.1 是否能够定义同名变量
// 通过var定义变量,可以重复定义同名的变量,并且后定义的会覆盖先定义的
var num = 123;
var num = 456;
console.log(num); // 456

// 2.2如果通过let定义变量, "相同作用域内"不可以重复定义同名的变量
let num = 123;
let num = 456; // 报错
1
2
3
4
5
6
7
8
// 2.2是否能够先使用后定义
// 通过var定义变量, 可以先使用后定义(预解析)
console.log(num); // undefined
var num = 123;

// 通过let定义变量, 不可以先使用再定义(不会预解析)
console.log(num); // 报错
let num = 123;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 2.3是否能被{}限制作用域
// 无论是var还是let定义在{}外面都是全局变量
var num = 123;
let num = 123;

// 将var定义的变量放到一个单独的{}里面, 还是一个全局变量
{
var num = 123;
}
console.log(num); //123 不会报错

// 将let定义的变量放到一个单独的{}里面, 是一个局部变量
{
let num = 123;
}
console.log(num); //会报错

全局变量

  • 在全局作用域下声明的变量叫做全局变量(在函数外部定义的变量)
    • 全局变量在代码的任何位置都可以使用
  • 特殊情况下,在函数内不使用 var/let 关键字声明的变量也是全局变量(不建议使用)
1
2
3
4
5
6
// 1.JavaScript作用域 : 就是代码名字(变量)在某个范围内起作用和效果 目的是为了提高程序的可靠性更重要的是减少命名冲突
// 2. js的作用域(es6)之前 : 全局作用域 局部作用域
// 3. 全局作用域: 整个script标签 或者是一个单独的js文件
var num = 10;
var num = 30;
console.log(num);

局部变量

  • 在局部作用域下声明的变最叫做局部变量(在函数内部定义的变量)
    • 局部变量只能在该函数内部使用
    • 函数的形参实际上就是局部变量
1
2
3
4
5
6
7
// 4. 局部作用域(函数作用域) 在函数内部就是局部作用域 这个代码的名字只在函数内部起效果和作用
function fn() {
// 局部作用域
var num = 20;
console.log(num);
}
fn();

局部/全局变量的区别

  • 全局变量:在任何一个地方都可以使用,只有在浏览器关闭时才会被销毁,因此比较占内存
  • 局部变量:只在函数内部使用, 当其所在的代码块被执行时,会被初始化;当代码块运行结束后,就会被销毁, 因此更节省内存空间
1
2
3
4
5
6
7
8
9
// 2. 局部变量   在局部作用域下的变量   后者在函数内部的变量就是 局部变量
// 注意: 函数的形参也可以看做是局部变量
function fun(aru) {
var num1 = 10; // num1就是局部变量 只能在函数内部使用
num2 = 20; // 全局变量
}
fun();
console.log(num1); // 报错
console.log(num2); // 20
1
2
3
4
5
6
7
// ES5 中没有块级作用域  js的作用域: 全局作用域  局部作用域  
// 我们js 也是在 es6 的时候新增的块级作用域
// 块级作用域 {} if {} for {}
if (3 < 5) {
var num = 10;
}
console.log(num); // 10 没有块级作用域

作用域案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    // 1.找出下列哪些是全局变量,哪些是局部变量
// 1.1
var num1 = 123; // 全局变量

// 1.2
function test() {
var num2 = 456; // 局部变量
}

// 1.3
{
var num3 = 789; // 全局变量
}

// 1.4
function test() {
num4 = 666; // 全局变量
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    // 2.找出下列哪些是全局变量,哪些是局部变量
// 2.1
let num1 = 123; // 全局变量

// 2.2
function test() {
let num2 = 456; // 局部变量
}

// 2.3
{
let num3 = 789; // 局部变量
}

// 2.4
function test() {
num4 = 666; // 全局变量
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    // 3.下列代码运行是否会报错
// 3.1
var num = 123;
var num = 456; // 不会报错

// 3.2
{
var num = 123;
{
var num = 456; // 不会报错
}
}

// 3.3
console.log(num); // 不会报错
var num = 123;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    // 4.下列代码运行是否会报错
// 4.1
let num = 123;
let num = 456; // 会报错

// 4.2
{
let num = 123;
{
// 注意点: 在不同的作用域范围内, 是可以出现同名的变量的
let num = 456; // 不会报错
}
}

// 4.3
console.log(num); // 会报错
let num = 123;
1
2
3
4
5
6
7
8
9
    // 5.下列代码运行是否会报错
// 5.1
// 注意点: 只要出现了let, 在相同的作用域内, 就不能出现同名的变量
let num = 123;
var num = 456; // 会报错

// 5.2
var num = 123;
let num = 456; // 会报错

作用域链

  • 注意点: 初学者在研究”作用域链”的时候最好将 ES6 之前和 ES6 分开研究
  • 需要明确:
    • 1 ES6 之前定义变量通过 var
    • 2 ES6 之前没有块级作用域, 只有全局作用域和局部作用域
    • 3 ES6 之前函数大括号外的都是全局作用域
    • 4 ES6 之前函数大括号中的都是局部作用域
  • ES6 之前作用域链
    • 01 全局作用域我们又称之为 0 级作用域
    • 02 定义函数开启的作用域就是1级/2级/3级/…作用域
    • 03 JavaScript 会将这些作用域链接在一起形成一个链条, 这个链条就是作用域链
      • 0 —> 1 —-> 2 —-> 3 —-> 4
    • 04 除 0 级作用域以外, 当前作用域级别等于上一级+1
  • 变量在作用域链查找规则
    • 01 先在当前找, 找到就使用当前作用域找到的
    • 02 如果当前作用域中没有找到, 就去上一级作用域中查找
    • 03 以此类推直到 0 级为止, 如果 0 级作用域还没找到, 就报错
1
2
3
4
5
6
7
8
9
10
11
12
13
// 全局作用域 / 0级作用域
var num = 123;
function demo() {
// 1级作用域
var num = 456;
function test() {
// 2级作用域
var num = 789;
console.log(num); // 789
}
test();
}
demo();
1
2
3
4
5
6
7
8
9
10
11
12
// 全局作用域 / 0级作用域
let num = 123;
{
// 1级作用域
let num = 456;
function test() {
// 2级作用域
let num = 789;
console.log(num); // 789
}
test();
}
  • 只要是代码,就至少有一个作用域
  • 写在函数内部的局部作用域
  • 如果函数中还有函数,那么在这个作用域中就又可以诞生—个作用域
  • 根据在内部函数可以访问外部函数变量的这种机制, 用链式查找决定哪些数据能被内部函数访问, 就称作作用域链
1
2
3
4
5
6
7
8
9
10
// 作用域链  : 内部函数访问外部函数的变量,采取的是链式查找的方式来决定取那个值 这种结构我们称为作用域链   就近原则
var num = 10;
function fn() { // 外部函数
var num = 20;
function fun() { // 内部函数
console.log(num); // 20
}
fun();
}
fn();

image-20220128221614536

作用域链案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 案例1 : 结果是几?
function f1() {
var num = 123;

function f2() {
var num = 0;
console.log(num); // 0 站在目标出发,一层一层的往外查找
}
f2();
}
var num = 456;
f1();

// 案例2 :结果是几?
var a = 1;
function fn1() {
var a = 2;
var b = '22';
fn2();
function fn2() {
var a = 3;
fn3();
function fn3() {
var a = 4;
console.log(a); // 4
console.log(b); // 22
}
}
}
fn1();

JavaScript预解析

预解析

  • JavaScript 代码是由浏览器中的 JavaScript 解析器来执行的。JavaScript 解析器在运行 JavaScript 代码的时候分为两步,预解析和代码执行(先定义后执行)。
  • 什么是预解析
    • 浏览器在执行 JS 代码的时候会分成两部分操作:预解析以及逐行执行代码
    • 也就是说浏览器不会直接执行代码, 而是加工处理之后再执行,
    • 这个加工处理的过程, 我们就称之为预解析
  • 预解析规则
      1. 将变量声明和函数声明提升到当前作用域最前面
      1. 将剩余代码按照书写顺序依次放到后面
  • 注意点
    • 通过 let 定义的变量不会被提升(不会被预解析)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 1问  
console.log(num); // 报错

// 2问
console.log(num); // undefined 坑 1
var num = 10;
// 相当于执行了以下代码:
// var num;
// console.log(num);
// num = 10;

// 3问
function fn() {
console.log(11);
}
fn();

// 4问
fun(); // 报错 坑2
var fun = function () {
console.log(22);
}
// 函数表达式 调用必须写在函数表达式的下面
// 相当于执行了以下代码:
// var fun;
// fun();
// fun = function() {
// console.log(22);
// }

// 1. 我们js引擎运行js 分为两步: 预解析 代码执行
// (1). 预解析 js引擎会把js 里面所有的 var 还有 function 提升到当前作用域的最前面
// (2). 代码执行 按照代码书写的顺序从上往下执行

// 2. 预解析分为 变量预解析(变量提升) 和 函数预解析(函数提升)
// (1) 变量提升 就是把所有的变量声明提升到当前的作用域最前面 不提升赋值操作
// (2) 函数提升 就是把所有的函数声明提升到当前作用域的最前面 不调用函数

预解析案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 案例1
var num = 10;
fun();
function fun() {
console.log(num);
var num = 20;
}
// // 相当于执行了以下操作
// // var num;
// // function fun() {
// // var num;
// // console.log(num);
// // num = 20;
// // }
// // num = 10;
// // fun();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// // 案例2
var num = 10;
function fn() {
console.log(num);
var num = 20;
console.log(num);
}
fn();
// // 相当于以下代码
// // var num;
// // function fn() {
// // var num;
// // console.log(num);
// // num = 20;
// // console.log(num);
// // }
// // num = 10;
// // fn();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// // 案例3
var a = 18;
f1();
function f1() {
var b = 9;
console.log(a);
console.log(b);
var a = '123';
}
// 相当于以下代码
// var a;
// function f1() {
// var b;
// var a;
// b = 9;
// console.log(a);
// console.log(b);
// a = '123';
// }
// a = 18;
// f1();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 案例4
f1();
console.log(c);
console.log(b);
console.log(a);
function f1() {
var a = b = c = 9;
console.log(a);
console.log(b);
console.log(c);
}
// 以下代码
// function f1() {
// var a;
// a = b = c = 9;
// // 相当于 var a = 9; b = 9; c = 9; b 和 c 直接赋值 没有var 声明 当 全局变量看
// // 集体声明 var a = 9, b = 9, c = 9;
// console.log(a);
// console.log(b);
// console.log(c);
// }
// f1();
// console.log(c);
// console.log(b);
// console.log(a);

函数的 this 指向

this 的指向

  • 定义一个函数,我们采用三种不同的方式对它进行调用,它产生了三种不同的结果
1
2
3
4
5
6
// 定义函数
function foo(name) {
console.log(this)
}
// 1.方式一: 直接调用,此时 this 指向 window
foo() // window
1
2
3
4
5
6
7
8
// 定义函数
function foo(name) {
console.log(this)
}
// 2.方式二: 通过对象调用,此时 this 指向 obj 对象
var obj = { name: 'why' }
obj.aaa = foo
obj.aaa() // {name: 'why', aaa: ƒ}
1
2
3
4
5
6
// 定义函数
function foo(name) {
console.log('foo函数:', this)
}
// 3.方式三:通过 call / apply 调用
foo.call('abc') // String {'abc'} , 此时 this 指向 String {'abc'}
  • 函数在调用时, JavaScript 会 默认给 this 绑定一个值
  • this 的 绑定和定义的位置(编写的位置) 没有关系;
  • this 的 绑定和调用方式以及调用的位置有关系
  • this 是 在运行时被绑定

函数独立调用时的 this

  • 独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用;
  • 严格模式(“use strict”)下, 独立调用的函数中的 this 指向的是 undefined
1
2
3
4
5
6
// 定义函数
// 1.普通的函数被独立调用,this 指向 Window
function foo() {
console.log(this)
}
foo() // Window
1
2
3
4
5
6
7
8
9
// 2.函数定义在对象中, 但是是独立调用,this 指向 Window
var obj = {
name: 'why',
bar: function () {
console.log(this)
},
}
var baz = obj.bar
baz() // Window
1
2
3
4
5
6
7
8
9
10
11
// 3.高阶函数
var obj = {
name: 'why',
bar: function () {
console.log(this)
},
}
function test(fn) {
fn() // Window ,独立调用,this 指向 Window
}
test(obj.bar)

方法通过对象调用的 this

  • 也就是它的调用位置中,是通过 某个对象发起的函数调用 。
1
2
3
4
5
6
7
8
// 隐式绑定
function foo() {
console.log(this)
}
var obj = {
bar: foo,
}
obj.bar() // this 指向 obj 对象,为 {bar: ƒ}
1
2
3
4
5
6
7
8
9
10
11
12
function foo() {
console.log(this)
}
var obj1 = {
name: 'obj1',
foo: foo,
}
var obj2 = {
name: 'obj2',
obj1: obj1,
}
obj2.obj1.foo() // this 指向为 obj1 : {name: 'obj1', foo: ƒ}
1
2
3
4
5
6
7
8
9
function foo() {
console.log(this)
}
var obj1 = {
name: 'obj1',
foo: foo,
}
var bar = obj1.foo
bar() // this 指向为 window ,函数的独立调用

通过 new 绑定时的 this

  • JavaScript 中的函数可以当做一个类的构造函数来使用,也就是使用 new 关键字。

  • new 创建对象的过程:

    • 创建新的空对象

    • 将 this 指向这个空对象

    • 执行函数体中的代码

    • 没有显示返回非空对象时, 默认返回这个对象

1
2
3
4
5
function foo() {
this.name = 'why'
console.log(this)
}
new foo() // this 指向为 foo {name: 'why'} , (通过foo构造函数创建的实例对象)
  • new 创建对象的过程的另一种描述:
    • 创建一个全新的对象;
    • 这个新对象会被执行 prototype 连接;
    • 这个新对象会绑定到函数调用的 this 上( this 的绑定在这个步骤完成);
    • 如果函数没有返回其他对象,表达式会返回这个新对象;

call/apply/bind 显示绑定

call

  • 因为明确的绑定了 this 指向的对象,所以称之为 显式绑定 。
  • 显示绑定后, this 就会明确的指向绑定的对象
1
2
3
4
5
6
7
8
9
10
11
12
function foo(name, age, height) {
console.log(this)
console.log('打印参数:', name, age, height)
}

// 函数加()直接调用
foo('why', 18, 1.88) // this 指向为 window

// call , 会调用函数并修改函数中的 this 指向
// 第一个参数: 绑定this指向
// 后续参数列表: 后续的参数以多参数的形式传递, 会作为实参传递给 foo 函数
foo.call('call', 'james', 25, 2.05) // String {'call'}, this 执行为 String {'call'}

apply

  • 因为明确的绑定了 this 指向的对象,所以称之为 显式绑定 。
  • 显示绑定后, this 就会明确的指向绑定的对象
1
2
3
4
5
6
7
8
9
10
11
12
function foo(name, age, height) {
console.log(this)
console.log('打印参数:', name, age, height)
}

// 函数加()直接调用
foo('why', 18, 1.88) // this 指向为 window

// 02 apply ,会调用函数并修改函数中的 this 指向
// 第一个参数: 绑定this指向
// 第二个参数: 传入额外的实参, 以数组的形式,会作为实参传递给 foo 函数
foo.apply('apply', ['kobe', 30, 1.98]) // String {'apply'}, this 执行为 String {'apply'}

bind

  • 使用 bind 方法, bind() 方法创建一个新的 绑定函数( bound function BF )
  • 绑定函数是一个 exotic function object (怪异函数对象 ECMAScript 2015 中的术语)
  • 在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
1
2
3
4
5
6
7
8
9
10
function foo(name, age, height, address) {
console.log('foo:', this)
console.log('参数:', name, age, height, address)
}
var obj = { name: 'why' }

// 需求: 调用foo时, 总是绑定到obj对象身上(不希望obj对象身上有函数)
// 1. bind函数的基本使用
var bar = foo.bind(obj) // foo 函数调用时修改this的绑定,相当于返回了一个新的函数赋值给 bar
bar() // 此时 this 指向为 {name: 'why'} 未传参,形参为undefined
1
2
3
4
5
6
7
8
9
10
11
function foo(name, age, height, address) {
console.log('foo:', this)
console.log('参数:', name, age, height, address)
}
var obj = { name: 'why' }

// 需求: 调用foo时, 总是绑定到obj对象身上(不希望obj对象身上有函数)
// 2. bind函数的其他参数
var bar = foo.bind(obj, 'kobe', 18, 1.88)
// bar 函数的形参 "james" 赋值给了形参 address,相当于在 bind 方法的实参中追加该参数
bar('james') // 此时 this 指向为 {name: 'why'} / 参数: kobe 18 1.88 james

内置函数的调用绑定

  • 这些内置函数会要求我们传入 另外一个函数
  • 我们自己并不会显示的调用这些函数 ,而且 JavaScript 内部或者第三方库内部会帮助我们执行

定时器中的 this

1
2
3
4
// 1 定时器
setTimeout(function () {
console.log(this) // window,严格模式下也是window
}, 1000)

事件回调中的 this

1
2
3
4
5
6
7
8
9
// 2 按钮的点击监听
var btnEl = document.querySelector('button')
btnEl.onclick = function () {
console.log(this) // this 指向为 <button>按钮</button>
}

btnEl.addEventListener('click', function () {
console.log(this) // this 指向为 <button>按钮</button>,事件绑定给谁,this就指向谁
})

forEach 中的 this

1
2
3
4
5
// 3 forEach forEach的第二个参数为回调函数的this的指向
var names = ['abc', 'cba', 'nba']
names.forEach(function (item) {
console.log(this) // String {'aaaa'}
}, 'aaaa')

this 绑定的优先级

  • 01 默认规则的优先级最低
    • 毫无疑问,默认规则的优先级是最低的,因为存在其他规则时,就会通过其他规则的方式来绑定 this
  • 02 显示绑定优先级高于隐式绑定
  • 03 new 绑定优先级高于隐式绑定
  • 04 new 绑定优先级高于 bind
    • new 绑定和 call 、 apply 是不允许同时使用的,所以不存在谁的优先级更高
    • new 绑定可以和 bind 一起使用, new 绑定优先级更高

显式绑定高于隐式绑定

1
2
3
4
5
6
7
8
9
10
function foo() {
console.log(this)
}
var obj = { foo: foo }

// 比较优先级:
// 1 显式绑定绑定的优先级高于隐式绑定
// 1.1 测试一: apply/call高于隐式绑定
obj.foo.apply('abc') // this 指向为 String {'abc'},而不是obj对象
obj.foo.call('abc') // this 指向为 String {'abc'},而不是obj对象
1
2
3
4
5
6
7
8
9
10
11
12
function foo() {
console.log(this)
}

// 1.2 测试二: bind高于隐式绑定
var bar = foo.bind('aaa')
var obj = {
name: 'why',
baz: bar,
}

obj.baz() // this 指向为 String {'aaa'}

new 绑定高于隐式绑定

1
2
3
4
5
6
7
8
9
10
11
// 2.new绑定优先级高于隐式绑定
var obj = {
name: 'why',
foo: function () {
console.log(this)
console.log(this === obj) // false
},
}

// 如果是通过 obj.foo() 的方式调用,this就是指向obj对象
new obj.foo() // this 指向为 foo {} , 即指向的是 foo 方法创建的实例对象,空对象

new 高于显示绑定

1
2
3
4
5
6
7
8
9
// 3.new/显式
// 3.1. new 不可以和 apply/call一起使用
// 3.2. new 优先级高于 bind
function foo() {
console.log(this)
}

var bindFn = foo.bind('aaa')
new bindFn() // this 指向为 foo 函数缔造出来的 {},而不是指向 "aaa"

this 的特殊情况的绑定

  • 情况一:如果在显示绑定中,我们传入一个 null 或者 undefined ,那么这个显示绑定会被忽略,使用默认规则
1
2
3
4
5
6
7
8
// 特殊情况一: 显式绑定null/undefined, 那么使用的规则是默认绑定
function foo() {
console.log(this)
}

foo.apply('abc') // this 指向为 String {'abc'}
foo.apply(null) // this 指向为 Window , 严格模式("use strict")下this指向null
foo.apply(undefined) // this 指向为 Window , 严格模式("use strict")下this指向undefined
  • 情况二:创建一个函数的 间接引用 ,这种情况使用默认绑定规则。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 2.情况二: 间接函数引用
var obj1 = {
name: 'obj1',
foo: function () {
console.log(this)
},
}
var obj2 = {
name: 'obj2',
}

obj2.foo = obj1.foo
obj2.foo() // this 指向为 {name: 'obj2', foo: ƒ}
;(obj2.foo = obj1.foo)() // this 指向为 window , 相当于函数独立调用