影子的知识库

影子的知识库

  • 知识库
  • GitHub

›ES6

JVM系列

  • JVM内存区域
  • 对象创建-布局-访问
  • 内存溢出实战
  • 内存区域回收
  • 四大引用
  • 垃圾回收算法
  • HotSpot回收算法细节

Java系列

  • java注解
  • springboot请求参数绑定
  • springboot请求参数校验框架
  • YAML语法
  • 动态代理
  • classpath和java命令
  • springboot-aop编程
  • springboot统一异常处理
  • springboot数据库和事务
  • springboot拦截器
  • springboot中的web配置
  • docker的简单开发
  • springboot自动配置
  • 数据库的隔离级别
  • springboot监控
  • java类加载
  • java-agent的相关内容
  • 类加载器详解
  • java的SecurityManager
  • maven学习

Node

    JS 基础

    • 语法基础和数据类型
    • 数据类型转换
    • 语句 表达式 运算符
    • 变量与对象
    • 函数
    • 数据处理
    • 常用 API
    • 重点知识

    ES6

    • 块级作用域
    • 字符串和正则表达式
    • 函数
    • 对象
    • Symbol
    • Set和Map
    • 迭代器和生成器
    • 类
    • 数组
    • Promise

    Node 基础

    • 模块系统
    • package.json
    • 内置对象
    • npm脚本的使用
    • Buffer
    • Stream
    • 事件循环机制
    • 示例代码

    stream系列

    • 流的缓冲
    • 可读流
    • 可写流
    • 双工流和转换流
    • 自定义流

后期计划

  • 学习计划
  • 专题研究计划
Edit

本文内容

默认参数

ES 6 之前想为函数添加默认参数,一般就是函数内部对参数进行检测,不存在时将其赋值为默认参数,但是这些检测代码会扰乱函数逻辑,而且想写出完全没有缺陷的检测代码还有点困难,所以 ES 6 提供了对默认参数的原生支持:

function makeRequest(url, timeout = 2000, callback = function () { }) {
    // 函数其余部分
}

这个示例中,第二个和第三个参数都是可选参数,都有一个默认值。

我们也可以在中间使用默认参数,后续继续使用非默认的参数

function makeRequest(url, timeout = 2000, callback) {
    // 函数其余部分
}

只有中间的参数有默认值,这种情况下,只有第二个参数没有传值或者主动传入 undefined,才会使用参数的默认值。(传入 null 不会使用默认值)

默认参数对 arguments 的影响

ES 6 中,函数参数与 arguments 会进行分离,而在 ES 5 非严格模式下,函数参数与 arguments 是同步更新的,也就是说

function f(first, second) {
    console.log(first === arguments[0]); // true
    console.log(second === arguments[1]); // true
    first = 'a';
    second = 'b';
    console.log(first === arguments[0]); // true
    console.log(second === arguments[1]); // true
}
f(1, 2);

严格模式下

function f(first, second) {
    'use strict';
    console.log(first === arguments[0]); // true
    console.log(second === arguments[1]); // true
    first = 'a';
    second = 'b';
    console.log(first === arguments[0]); // false
    console.log(second === arguments[1]); // false
}
f(1, 2);

在 ES 6 中

function f(first, second = 3) {
    console.log(first === arguments[0]); // true
    console.log(second === arguments[1]); // false
    first = 'a';
    second = 'b';
    console.log(first === arguments[0]); // false
    console.log(second === arguments[1]); // false
}
f(1);

可以这么理解:

  • 默认参数使用类似于 ES 5 的严格模式来执行
  • 当我们使用默认值时,相当于:原值是 undefined,被修改成了那个默认值
  • 同时由于 arguments 没有进行同步更改,因此 second !== arguments[1]
  • 后面的 2 个 false 则是与 ES 5 的严格模式一致

默认参数表达式

let value = 5;
function getValue() {
    return value++;
}
function f(a, b = getValue()) {
    console.log(a);
    console.log(b);
}
f(2); // 2 5
f(2); // 2 6
f(2); // 2 7

显然,可以在默认参数的值那里嵌入一个表达式,表达式的值是调用时计算的

function f(a, b = a) {
    console.log(a);
    console.log(b);
}
f(2); // 2 2
f(3); // 3 3
f(4); // 4 4

这种方式,第二个参数引用第一个参数的值是允许的,这里有个限制:只能后定义的参数引用先定义的参数,先定义的参数引用后定义的参数将会报错,例如

function f(a = b, b) {
    console.log(a);
    console.log(b);
}
f(undefined,2); // 抛出错误

这个是由于默认参数的临时死区导致的

默认参数的临时死区

查看如下示例

function getValue(value) {
    return value + 5;
}

function add(first, second = getValue(first)) {
    return first + second;
}

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

实际上 first 和 second 的定义相当于如下

// add(1,1) 时的参数等价定义
let first = 1;
let second = 1;

// add(1) 时的参数等价定义
let first = 1;
let second = getValue(first);

现在,我们重写 add 方法

function add(first = second, second) {
    return first + second;
}

console.log(add(1, 1)); // 2
console.log(add(undefined, 1)); // 抛出错误

这个方式里 first 和 second 的定义相当于如下

// add(1,1) 时的参数等价定义
let first = 1;
let second = 1;

// add(undefined,1) 时的参数等价定义
let first = second;
let second = 1;

当调用 add(undefined,1) 时,参数定义 first 时,second 尚处于临时死区,所以会抛出错误

变长参数

实际上,JavaScript 函数中,都有一个 arguments 参数,可以用来表示函数接收到的所有参数,从而实现变长参数的特性。但是 ES 6 提供了更方便的特性来直接支持这一点,使用方式和 Java 的变长参数类似。

在函数的命名参数前添加三个点 ... 就表示这是一个变长参数,这个参数在函数体内是一个数组,包含着自它之后传入的所有参数。例如:

function add(first, ...others) {
    console.log(first + others.reduce((pre, next) => {
        return pre + next;
    }));
}

add(1, 2, 3, 4); // 10

我们使用 others 接收了 2 3 4,这三个参数组成了一个数组,也就是说在函数内部 others = [2,3,4]

函数的 length 属性只计算命名参数的数量,也就是说,add(first, ...others) 函数中,只有 first 才计入 length 数量,而 others 不计算在 length 中

使用限制:

  • 每个函数最多只有一个变长参数(很合理,基本我见过有这种特性的语言都是这样的)

  • 变长参数一定要放在参数末尾(也很合理和直观)

  • 定长参数不能用于对象的 setter 方法中,例如

    let object = {
        set name(...value) {
            // 执行一些逻辑
        }
    };
    

    将会报错。因为对象字面量的 setter 的参数有且只能有一个,只能执行 object.name = xxx,只能传入一个参数,所以不允许使用变长参数来定义 setter

  • 无论是否使用变长参数,arguments 总是表示函数接收到的所有参数

展开运算符

展开运算符可以让我们把一个数组展平,当做多个参数传入到函数中,例如

console.log(Math.max(1, 2, 3, 4)); // 4
console.log(Math.max([1, 2, 3, 4])); // NaN

第一行正确输出,第二行输出 NaN。因为第二行传入的是一个数组,Math.max 将其当成一个参数接收了,发现不是一个数字,于是输出 NaN。

而展开运算符则可以处理这种情况,例如

const array = [1, 2, 3, 4];
console.log(Math.max(...array)); // 4

我们在参数前面加上 ... 就可以将数组展平,Math.max(...array) 等价于执行了 Math.max(1,2,3,4),Math.max(...array, 5) 等价于执行了 Math.max(1,2,3,4,5)

函数名称

ES 6 为所有函数都添加了 name 属性。例如

function doSomething() {
    // 空函数
}

let doAnotherThing = function () {
    // 空函数
};
console.log(doSomething.name); // doSomething
console.log(doAnotherThing.name); // doAnotherThing

明确函数的多重用途

在 ES 5 以及之前,函数可以直接调用,也可以通过 new 来调用。通过 new 调用的方式是首先生成一个空对象,然后将函数的 this 指向这个空对象,最后将新对象的__proto__ 属性指向函数对象的 prototype。

函数的两个内部方法

每个函数通常有两个内部方法:[[Call]] 和 [[Construct]],当通过 new 调用的时候,指向的是内部的 [[Construct]],普通调用执行的是内部的 [[Call]]。具有 [[Construct]] 的函数被称为构造函数。

不是所有的函数都有 [[Construct]],例如箭头函数就没有 [[Construct]],因此不能通过 new 来调用箭头函数

元属性 new.target

ES 6 中,函数内添加了一个元属性,new.target,当调用函数的 [[constructor]] 方法时,new.target 被赋值为 new 操作符的目标的构造函数,例如:

function Person() {
    console.log(new.target === this.constructor);
}
new Person();

上述结果将会是 true,表示 new.target 是新对象的构造器

同时有 new.target = this.constructor = Person = Person.prototype.constructor

我们通过这个元属性可以知道,函数是不是被通过 new 来调用的

块级函数

ES 6 之前,在块级作用域内部声明函数是错误的语法。ES 6 之后添加了块级函数(严格模式下)

"use strict";
if (true) {
    console.log(typeof dosomething); // function

    function dosomething() {

    }

    dosomething();
}
console.log(typeof dosomething); // undefined

代码块内,函数的声明提升,所以第一次打印了 function,代码块外,块级作用域结束,因此 dosomething 不存在,打印 undefined

非严格模式下,ES 6 可以在代码块内声明函数,块级函数的声明不是被提升到代码块顶部,而是外围函数或者全局作用域的顶部

if (true) {
    console.log(typeof dosomething); // function

    function dosomething() {

    }

    dosomething();
}
console.log(typeof dosomething); // function

箭头函数

箭头函数有以下特性:

  • 没有 this、super、arguments、new.target 属性。箭头函数中的这些值都由外围最近一层的非箭头函数决定
  • 不能通过 new 关键字调用。箭头函数没有 [[constructor]] 方法,不能被用作构造函数
  • 没有原型。箭头函数不存在 prototype 属性
  • 不可以改变 this 绑定。函数内部的 this 值不可以改变,在函数体内始终保持一致
  • 不支持 arguments 对象。所以必须要通过命名参数和变长参数进行访问
  • 不支持重复的命名参数。(非严格模式下普通函数可以有重复的参数)

语法

let reflect = value => value;

// 实际上相当于
let reflect = function (value) {
    return value;
}

当箭头函数只有一个参数,可以省略参数的括号,右侧的表达式求值后被当做返回值返回

let sum = (num1, num2) => num1 + num2;

// 实际上相当于
let sum = function (num1, num2) {
    return num1 + num2;
}

传入多个参数时,参数周围需要有括号

let sum = () => 3;

// 实际上相当于
let sum = function () {
    return 3;
}

没有参数时,也需要带有括号

也就是说,只有在有且仅有一个参数时,参数可以不带括号

let sum = (num1, num2) => {
    return num1 + num2;
};

// 实际上相当于
let sum = function (num1, num2) {
    return num1 + num2;
};

这种方式类似于传统的函数体

let donothing = () => {
};

// 实际上相当于
let donothing = function () {

};

定义一个空的箭头函数

let getObj = () => ({id: 1, name: 'czp'});

// 实际上相当于
let getObj = function () {
    return {id: 1, name: 'czp'}
};

当我们想返回一个对象字面量的时候,要在外面添加一层小括号,这是防止与函数体混淆,不加括号会报错

箭头函数的this

箭头函数的 this 可以当成闭包来理解

let obj = {
    init: function () {
        console.log(this);
        return () => console.log(this);
    }
};
let func = obj.init();
func();

输出如下

(py3.5) czp@:~/workspace/knowledge-base/demos/node_start$ node hello.js
{ init: [Function: init] }
{ init: [Function: init] }

分析:

  • 箭头函数内的 this 类似于一个闭包,捕获了外围函数的 this
  • obj.init() 调用后,init() 内的 this 指向 obj
  • 因此箭头函数中的 this 指向 obj

这种行为十分类似于闭包,所以按照闭包的思路来理解挺合适的。

与闭包中的变量捕获不同的是,箭头函数内的 this 是不可以被更改的,即使通过 call、apply、bind 方法来修改 this 值,也不会生效

箭头函数的arguments

这个跟箭头函数的 this 行为几乎一致,也是通过闭包的变量捕获的机制来完成的,就不再多讲解了

尾递归优化

ES 6 添加了尾递归优化的功能,基本用不上。

最好别写递归,尾递归优化最主要的限制是递归调用必须在结尾,而且直接作为值返回。

Last updated on 11/8/2020
← 字符串和正则表达式对象 →
  • 默认参数
    • 默认参数对 arguments 的影响
    • 默认参数表达式
    • 默认参数的临时死区
  • 变长参数
  • 展开运算符
  • 函数名称
  • 明确函数的多重用途
    • 函数的两个内部方法
    • 元属性 new.target
  • 块级函数
  • 箭头函数
    • 语法
    • 箭头函数的this
    • 箭头函数的arguments
    • 尾递归优化
影子的知识库
Docs
Getting Started (or other categories)Guides (or other categories)API Reference (or other categories)
Community
User ShowcaseStack OverflowProject ChatTwitter
More
BlogGitHub
Copyright © 2020 Cen ZhiPeng