1、函数基础

1.1 函数的基本概念

  • 函数是一段JavaScript代码,只被定义一次,但是可以被调用或者执行许多次。函数是一种对象,可以设置属性,或调用方法。

  • 函数中的参数分为实参和形参。其中,形参在函数体中类似局部变量,函数调用会为形参提供实参的值。函数使用实参的值来计算返回值,成为该函数调用表达式的值。除了实参外,函数每次调用都会有一个this的值。

  • 如果函数挂载在对象的属性上则该函数被称为对象的方法,当通过该对象调用函数时,该对象就是此时的上下文,也就是该函数的this。

  • 用于初始化一个新创建的对象的函数称为构造函数

  • 函数可以嵌套在其它函数上进行定义,从而可以访问它们被定义时所处的作用域中的任何变量。此时,函数就构成了一个闭包。

1.2 函数的定义方法

1.2.1 函数定义构成

  • 函数定义是由函数名称标识符、一对圆括号和一对花括号构成,如:

    function sum(a, b) {
    return a + b
    }

    其中,sum是该函数的函数名,圆括号中存放啊a,b两个参数,大括号中存放JavaScript语句,构成了函数体。

1.2.2 函数定义方式

函数定义方式共有一下四种。
1、函数声明语法,如:

function mul(a, b) {
return a * b
}

2、函数表达式,如:

let mul = function (a, b) {
return a * b
}

3、Function构造函数,如:

// 使用function构造函数
let mul = new Function('a', 'b', 'return a * b')
  • 这个构造函数接收任意多个字符串参数,最后一个参数始终会被当成函数体,而之前的参数都是新函数的参数。
    不推荐使用这种语法来定义函数,因为这段代码会被解释两次:第一次是将它当作常规ECMAScript 代码,第二次是解释传给构造函数的字符串。这显然会影响性能。

4、箭头函数(ES6),如:

// 使用箭头函数
let mul = (a, b) => a * b

1.2.3 函数命名方式

函数命名和变量命名差别不大,建议使用驼峰命名法。即混合使用大小写字母来构成变量和函数的名字。驼峰命名法一般以小写字母开头,后面的独立单词首字母大写,用以区分每个独立的单词。

1.2.4 函数参数

  • 特征:
    ECMAScript 函数既不关心传入的参数个数,也不关心这些参数的数据类型。在定义函数时接收多个参数并不一定在调用时就必须要传入多个参数。

  • 原因:
    因为 ECMAScript 函数的参数在内部表现为一个数组。函数被调用时总会接收一个数组,但函数并不关心这个数组中包含什么。传进函数的每个参数值都被包含在arguments 对象(类数组)中。

  • 有关arguments对象:

    • arguments对象是一个类数组对象(但不是 Array 的实例),因此可以使用中括号语法访问其中的元素。如:arguments[0],arguments[1]
    • 要确定接收到的参数个数,可以访问 arguments.length 属性,如:

      function likes(name, fav1, fav2) {
      console.log(`${name}喜欢${fav1}、${fav2}`);
      let output = `${name}喜欢`
      let argCount = arguments.length
      for (let i = 0; i < argCount.length; i++) {
      output += `${arguments[i]}、`
      }
      output += `${arguments[argCount - 1]}。`
      console.log(output);
      }
      likes("小明", "读书", "篮球")

      运行结果:
      小明喜欢读书、篮球
      小明喜欢篮球。

  • 参数的默认值
    比较下方两段代码:

    ES5写法:

    function f1(name, age) {
    name = name ? name : "User";
    age = age ? age : 0;
    console.log(name, age);
    }
    f1();
    f1("Tom");

    运行结果是:
    User 0
    Tom 0

    ES6写法:

    function f2(name = "User", age = 0) {
    console.log(name, age);
    }
    f2();
    f2("Tom");

    运行结果是:
    User 0
    Tom 0

    由此可见,第二段代码更加的简化,增加了可读性。

  • 扩展参数
    先看一段代码:

    function sum() {
    let r = 0
    for (let i = 0; i < arguments.length; i++) {
    r += arguments[i]
    }
    return r
    }
    let nums = [1, 2, 3, 4, 5, 6]

    对于上面的代码,如何将nums数组的值传入函数sum里面?

    在es5里面,提供了apply方法,第一个参数默认是null,如:

    // 使用apply
    console.log(sum.apply(null, nums)) // 21

    在es6里面,提供了扩展运算符,如:

    // 扩展符...
    console.log(sum(...nums)); // 21
  • 剩余参数
    剩余参数语法允许我们将一个不定数量的参数表示为一个数组。
    语法:function(a,b,...arr){}
    如果函数的最后一个命名参数以...为前缀,则它将成为一个由剩余参数组成的真数组,其中从0(包括)到arr.length(不包括)的元素由传递给函数的实际参数提供。

    举个例子:已知三门科目的成绩计算总分

    es5 写法:

    function sum1(name) {
    let r = 0
    for (let i = 1; i < arguments.length; i++) {
    r += arguments[i]
    }
    console.log(`${name}总分是:${r}`);
    }
    sum1("Tom", 80, 80, 80)

    es6 写法:

    function sum2(name, ...scores) {
    let r = scores.reduce((x, y) => x + y, 0)
    console.log(`${name}总分是:${r}`);
    }
    sum2("Tom", 80, 80, 80)

    在上面的例子中,arr将收集该函数的第三个参数(因为第一个参数被映射到a)和所有后续参数。

  • 剩余参数和 arguments对象的区别:

    • 剩余参数只包含那些没有对应形参的实参,而 arguments 对象包含了传给函数的所有实参。
    • arguments对象不是一个真正的数组,而剩余参数是真正的 Array实例,也就是说你能够在它上面直接使用所有的数组方法,比如 sort,map,forEach或pop。
    • arguments对象还有一些附加的属性 (如callee属性)。
  • 常见问题:
    1、箭头函数不支持arguments对象。
    2、箭头函数支持剩余函数。
    3、arguments对象包含了剩余参数值。剩余参数没有接收到实参值时,为空数组
    4、剩余参数必须位于参数列表的末尾。

    举个例子:

    function sum1() {
    return Array.from(arguments).reduce((x, y) => x + y, 0)
    }
    console.log(sum1(1, 2, 3)); let sum2 = () => {
    return Array.from(arguments).reduce((x, y) => x + y, 0)
    }
    console.log(sum2(1, 2, 3));

    其中,第一个声明的函数输出结果是:6,
    第二个箭头函数输出错误: Uncaught ReferenceError: arguments is not defined。这说明了箭头函数不支持arguments对象。

    再看个例子:

    let sum3 = (...nums) => {
    return nums.reduce((x, y) => x + y, 0)
    }
    console.log(sum3(1, 2, 3)); //6

    这个说明es6的箭头函数支持剩余函数。

    再看下面的例子:

    function f1(x, y, ...nums) {
    console.log(arguments);
    console.log(nums);
    }
    f1(1, 2, 3, 4, 5, 6);
    f1(1, 2);

    运行结果:
    第一次调用后:
    这里的1,2对应f1的x,y参数,剩下的3, 4, 5, 6对应...nums,所以依次输出:
    [Arguments] { '0': 1, '1': 2, '2': 3, '3': 4, '4': 5, '5': 6 };
    [ 3, 4, 5, 6 ]数组

    第二次调用后:
    这里的1,2对应f1的x,y参数,...nums为空,所以依次输出:
    [Arguments] { '0': 1, '1': 2 };
    []空数组

1.2.5 函数调用

构成函数主体的JS代码在定义之时并不会执行,只有调用该函数时,它们才会执行。调用函数共有四种方式,分别是:作为函数、作为方法、作为构造函数以及通过它们的call()和apply()方法间接调用。
1、作为函数调用:

  • 使用调用表达式可以进行普通的函数调用也可进行方法调用。如:
    sum( 1, 2, 3+6, getNum( ) )

  • 对于普通的函数调用,函数的返回值成为调用表达式的值。如果该函数返回是因为解释器到达结尾,返回值就是undefined。
    如果函数返回是因为解释器执行到一条return语句,返回值就是return之后的表达式的值;如果return语句没有值,则返回undefined。

  • 非严格的ES5中,函数调用上下文(this的值)是全局对象。在严格模式下,调用上下文则是undefine

2、作为方法调用

  • 一般情况下,与普通函数的使用是调用上下文。
  • 属性访问表达式由两部分组成:一个对象(o)和属性名 (m)。在像这样的方法调用表达式里,对象o成为调用上下文,函数体可以使用关键字this引用该对象。
    举个例子:

    let obj = {
    a: 10,
    b: 20,
    add: function () {
    this.sum = this.a + this.b;
    },
    };
    obj.add();
    console.log(obj.sum);

    输出结果:30

  • 调用上下文。关键字this没有作用域的限制,嵌套的函数不会从调用它的函数中继承this。举个例子:

    let obj = {
    x: 10,
    fn: function () {
    this.x++;
    function ff() {
    console.log(this.x);
    }
    ff();
    },
    };
    obj.fn();

    输出:undefined
    若嵌套的函数要获取调用函数中的this,可以把它的this保存下来,然后在嵌套的函数里面调用。改进后的代码如下:

    let obj = {
    x: 10,
    fn: function () {
    this.x++;
    let self = this;
    function ff() {
    console.log(self.x);
    }
    ff();
    },
    };
    obj.fn();

    输出:11

3、作为构造函数调用

  • 构造函数调用指的是:函数或方法调用之前带有的关键字new的语句。
  • 构造函数调用会创建一个新的空对象,这个对象继承自构造函数的prototype属性。构造函数会初始化这个新创建的对象,并将这个对象用做其调用上下文,因此构造函数可以使用this关键字来引用这个新创建的对象
  • 构造函数通常不使用return关键字,它们通常初始化新对象,当构造函数的函数体执行完毕时,它会显式返回该对象。
    举个例子:
function Car(name) {
this.name = name;
return null;
}
console.log(Car("Benz"));
console.log(new Car("Benz"));

输出结果:
null
Car { name: 'Benz' }

4、间接调用方法

  • 使用函数对象的call( )和apply( )方法可以间接调用函数。其中第一个参数指定调用上下文(函数内部的this),第二个参数给函数传递参数。
    举个例子:
let obj1 = {
x: 100,
y: 100,
show: function (n = 1, m = 1) {
return `(${this.x * n},${this.y * m})`
},
concat: function () {
let r = [this.x, this.y]
for (let a of arguments) r.push(a)
return r
}
}
let obj2 = { x: 111, y: 222 }
let r1 = obj1.show.call(obj2)
let r2 = obj1.show.call(obj2, 10, 100)
console.log(r1, r2);
let r3 = obj1.concat.call(obj2, 11, 22, 33)
let r4 = obj1.concat.apply(obj2, [2, 3, 4, 5])
console.log(r3, r4);

输出结果:
(111,222) (1110,22200)
[ 111, 222, 11, 22, 33 ] [ 111, 222, 2, 3, 4, 5 ]

5、作为回调函数调用

  • 回调函数是被作为实参传入另一函数,并在该外部函数内被调用,用以来完成某些任务的函数。
    举个例子:
let arr = [1, 2, 12, 267, 21, 23, 78, 43, 10]
console.log("从大到小排序:" + arr.sort((a, b) => a - b));
console.log("从小到大排序:" + arr.sort((a, b) => b - a)); let data = {
x: 10,
y: 20,
show: function (how) {
how(this.x, this.y)
}
};
data.show(function (a, b) {
console.log(`(${a},${b})`);
})
data.show((a, b) => {
console.log(`(${a},${b})`);
})

输出结果:
从大到小排序:1,2,10,12,21,23,43,78,267
从小到大排序:267,78,43,23,21,12,10,2,1
(10,20)
(10,20)

1.2.6 函数对象

说道函数对象,就不得不介绍一下它的一些常用的属性。
1、length属性

  • 在函数体里,arguments.length表示传入函数的实参的个数。
  • 函数本身的length属性是只读的,它代表函数声明的实际参数的数量。
    举个例子:
function fn(a, b) {
console.log(arguments.length);
console.log(fn.length);
console.log(arguments.callee.length); // 当前函数对象执行次数
}
fn(1, 2, 3, 4, 5)

运行结果:
5
2
2

2、prototype属性

  • 每个函数都包含一个prototype属性,这个属性是指向一个对象的引用,这个对象称做“原型对象”。每一个函数都包含不同的原型对象。当将函数用做构造函数的时候,新创建的对象会从原型对象上继承属性。

3、自定义属性

  • 函数是一种特殊的对象,可以拥有属性
    举个例子:
function fn(a, b) {
if (fn.count) fn.count++;
else fn.count = 1
return a + b
}
fn(1, 2)
fn(2, 3)
fn(3, 4)
fn(4, 5)
fn.count = 10;
// 记录函数调用了多少次(不可行,可直接赋值修改)
console.log(fn.count);
console.log(arguments.callee.length);

运行结果:
10
5

函数对象除了需要掌握的属性外,还有一些方法同等重要。如:

  • call( )和apply( )方法
    通过调用方法的形式来间接调用函数。
    call()和apply()的第一个实参是要调用函数的主体对象,它是调用上下文,在函数体内通过this来获得对它的引用。
  • bind( )方法
    将函数绑定至某个对象,且可以绑定参数值。
    当在函数f()上调用bind()方法并传入一个对象o作为参数,这个方法将返回一个新的函数。
    (以函数调用的方式)调用新的函数将会把原始的函数f()当做o的方法来调用。
    传入新函数的任何实参都将传入原始函数。
    通过bind()为函数绑定调用上下文后,返回的函数不能通过call和apply修改调用上下文对象

bind( )方法例子:

let obj = {
x: 10,
show: function (y) {
let r = "";
for (let i = 0; i < y; i++) {
r += this.x + " ";
}
console.log(r);
},
};
obj.show(4);
let ss = obj.show.bind({ x: 1000 });
ss(3);
ss.call({ x: 2000 }, 3);

运行结果:
10 10 10 10
1000 1000 1000
1000 1000 1000

bind( )应用举例,将对象某方法的调用上下文固定为当前对象,如:

let obj = { z: 1000 };
function sum(x, y) {
let r = x + y;
if (Object.getPrototypeOf(this) != window) {
console.log(this); r += this.z;
}
return r;
}
obj.sum = sum;
console.log(obj.sum(20, 30));
console.log(obj.sum.call({ z: 1 }, 2, 3)); obj.asum = sum.bind(obj);
console.log(obj.asum(100, 200));
console.log(obj.asum.apply({ z: 1 }, [50, 60]));

运行结果:
{z: 1000, sum: ƒ}
1050
{z: 1}
6
{z: 1000, sum: ƒ, asum: ƒ}
1300
{z: 1000, sum: ƒ, asum: ƒ}
1110

JS学习笔记 (五) 函数进阶的更多相关文章

  1. JS学习笔记 (四) 数组进阶

    1.基本知识 1.数组是值的有序集合.每个值叫做一个元素,而每个元素在数组中的位置称为索引,以数字表示,以0开始. 2.数组是无类型的.数组元素可以是任意类型,并且同一个数组中的不同元素也可能有不同的 ...

  2. JS学习笔记3_函数表达式

    1.函数表达式与函数声明的区别 函数声明有“提升”(hoisting)的特性,而函数表达式没有.也就是说,函数声明会在加载代码时被预先加载到context中,而函数表达式只有在执行表达式语句时才会被加 ...

  3. js学习笔记19----getElementsByClassName函数封装

    js里面的getElementsByClassName()方法可通过某个class名获取到元素,在标准浏览器下可使用,在非标准浏览器下不可用.为了能够让这个方法兼容所有的浏览器,可以封装成如下函数: ...

  4. Node.js学习笔记(五) --- 使用Node.js搭建Web服务器

    1. Node.js 创建的第一个应用 1.引入http模块 var http = require("http"); 2. 创建服务器接下来我们使用 http.createServ ...

  5. Python学习笔记(五)函数和代码复用

    函数能提高应用的模块性,和代码的重复利用率.在很多高级语言中,都可以使用函数实现多种功能.在之前的学习中,相信你已经知道Python提供了许多内建函数,比如print().同样,你也可以自己创建函数, ...

  6. JS学习笔记 (三) 对象进阶

    1.JS对象 1.1 JS对象特征 1.JS对象是基本数据数据类型之一,是一种复合值,可以看成若干属性的集合. 属性是名值对的形式(key:value) 属性名是字符串,因此可以把对象看成是字符串到值 ...

  7. Go语言学习笔记(五) [函数]

    日期:2014年7月29日   1.函数定义:func (p type) funcname(q int) (r,s int) {return 0,0 }     func: 保留字,用于定义一个函数 ...

  8. 10-Node.js学习笔记-异步函数

    异步函数 异步函数是异步编程语法的终极解决方案,它可以让我们将异步代码写成同步的形式,让代码不再有回调函数嵌套,是代码变得清晰明了 const fn = async()=>{} async fu ...

  9. python学习笔记(五)---函数与类

    函数 def为定义函数的一个标志 demo1: def greet_user(username): print("Hello, " + username.title() + &qu ...

随机推荐

  1. Flutter 检测报错 Unable to locate Android SDK.

    安装好 Flutter SDK 之后,官方建议使用flutter doctor检查 Flutter SDK 的相关配置信息. 如果 Android Studio 安装 Android SDK 的时候选 ...

  2. [RootersCTF2019]I_<3_Flask-1|SSTI注入

    1.打开之后很明显的提示flask框架,但是未提供参数,在源代码中发现了一个git地址,打开之后也是没有啥用,结果如下: 2.这里我们首先需要爆破出来参数进行信息的传递,就需要使用Arjun这一款工具 ...

  3. P7727 风暴之眼 Eye of the Storm (树形 DP)

    谨 以 此 文 表 达 笔 者 个 人 观 点 , 如 有 冒 犯 官 解 , 可 在 评 论 区 诉 说 _{^{_{谨以此文表达笔者个人观点,如有冒犯官解,可在评论区诉说}}} 谨以此文表达笔者个 ...

  4. Spring的俩大核心概念:IOC、AOP

    1.Spring 有两个核心部分: IOC 和 Aop (1)IOC:控制反转,把创建对象过程交给 Spring 进行管理   (2)Aop:面向切面,不修改源代码进行功能增强 2.Spring 特点 ...

  5. 页面POST请求302重定向解决方案

    一.在后端Middleware中判断如果是post请求,返回json格式 二.前端全局js中捕获ajax请求进行处理

  6. Tomcat入门学习笔记

    Tomcat服务器 Tomcat使用 Tomcat下载 官网地址:Apache Tomcat - Apache Tomcat 8 Software Downloads 下载Windows 64位版To ...

  7. 第一百篇:JS异步

    好家伙,打工人要打工,博客会更新的没有以前频繁了   芜湖,一百篇了,这篇写一个比较难的异步(其实并不难理解,主要是为promise铺垫)   老样子,先补点基础: 1.进程 来吧,新华字典    大 ...

  8. windows清理必看

    清理缓存 代码如下 介绍此文件夹都是缓存文件全选删除即可 ctrl+A全选shift+del强制删除(不会添加到回收站) %temp% 找到C盘右击属性选择想要删除的文件进行清理即可 清理完点击清理系 ...

  9. STL再回顾(非常见知识点)

    目录 为人熟知的pair类型 再谈STL 迭代器的使用 常用的STL容器 顺序容器 vector(向量) 构造方式 拥有的常用的成员函数(java人称方法) string 构造方式 成员函数 dequ ...

  10. winfrom,窗体抖动功能

    #region 方法一 Point first = this.Location; for (int i = 0; i < 50; i++) { Application.DoEvents(); R ...