今天看啥  ›  专栏  ›  xszi

深入理解ES6(至第三章-函数)

xszi  · 掘金  ·  · 2018-12-15 18:15
阅读 91

深入理解ES6(至第三章-函数)

第一章 块级作用域绑定

var 声明初始化变量, 声明可以提升,但初始化不可以提升。

一、块级声明:

  1. let
  2. const
    • 默认使用,在某种程度上实现代码不可变,减少错误发生的几率
    • 如果常量是对象,则对象中的值可以修改
  3. 不能重复声明,声明不会提升

二、临时死区:

JS引擎在扫描代码发现变量声明时,要么将它们提升至作用域顶部(var声明),要么将声明放到TDZ中(letconst声明)。访问TDZ中的变量会触发运行时错误。只有执行过变量声明语句后,变量才会从TDZ中移出,然后可以正常访问。

第一种情况:

if(condition) {
    console.log(typeof value); //引用错误!
    let value = "blue"; //TDZ
}
复制代码

第二种情况:

console.log(typeof value); //'undefined'-->只有变量在TDZ中才会报错
if(condition) {
    let value = "blue";
}
复制代码

三、循坏中块作用域绑定

  • 立即调用(IIFE)

  • letconst之所以可以在运用在for-infor-of循环中,是因为每次迭代会创建一个新的绑定(const在for循环中会报错)。

四、全局块作用域绑定

var 可能会在无意中覆盖一个已有的全局属性 letconst,会在全局作用域下创建一个新的绑定,但该绑定不会添加为全局对象的属性。换句话说,使用letconst不能覆盖全局变量,而只能遮蔽它。如果不行为全局对象创建属性,使用letconst要安全得多。

注:如果希望子安全局对象下定义变量,仍然可以使用var。这种情况常见于在浏览器中跨frame或跨window访问代码

第二章 字符串和正则表达式

一、UTF-8码位

名词解释:

  • 码位: 每一个字符的“全球唯一的标识符,从0开始的数值”
  • 字符编码:表示某个字符的数值或码位即为该字符的字符编码。
  • 基本多文种平面(BMP,Basic Multilingual Plane)

    在UTF-16中,前2^16个码位均以16位的编码单元表示,这个范围被称作基本多文种平面

二、codePointAt()、 String.fromCodePoint() 和 normalize()

  • 两个方法对应于charCodeAt()fromCharCode()
  • normalize(): 规范的统一,适用于比较排序,国际化。

三、正则表达式 u 和 y 修饰符、正则表达式的复制、flag属性

  • u: 编码单元 ---> 字符模式

这个方法尽管有效,但是当统计长字符串中的码位数量时,运动效率很低。因此,你也可以使用字符串迭代器解决效率低的问题,总体而言,只要有可能就尝试着减小码位计算的开销。

检测u修饰符支持:

function hasRegExpU() {
    try {
        var pattern = new RegExp('.', 'u');
        return true;
    } catch (ex) {
        return false;
    }
}
复制代码
  • y: 第一次匹配不到就终止匹配

当执行操作时, y修饰符会把上次匹配后面一个字符的索引保存到lastIndexOf中;如果该操作匹配的结果为空,则lastIndexOf会被重置为0。g修饰符的行为类似。

1. 只有调用exec()和test()这些正则表达式对象的方法时才会涉及lastIndex属性;
2. 调用字符串的方法,例如match(),则不会触发粘滞行为。
复制代码
  • 正则表达式的复制
var re1 = /ab/i;
re2 = new RegExp(re1); //没有修饰符复制
re3 = new RegExp(re1, "g"); //有修饰符(ES6)
复制代码
  • flag属性 --- 获取正则表达式的修饰符

es5方法获取正则表达式的修饰符:

function getFlags(re) {
    var text = re.toString();
    return text.substring(text.lastIndexOf('/' + 1, text.length);
}
复制代码

模板字面量

多行字符串

基本的字符串格式化(字符串占位符)

HTML转义

  • 标签模板
function passthru(literals, ...substitutions) {
    //返回一个字符串
    let result = "";
    //根据substitutions的数量来确定循环的执行次数
    for(let i=0; i<substitutions.length; i++){
        result += literals;
        result += substitutions[i]
    }
    
    //合并最后一个literal
    result += literals[literals.length - 1];
    return result;
}

let count = 10;
price = 0.25;
message = passthru`${count} items cost $${(count * price).toFixed(2)}`;

console.log(message)
复制代码
  • String.raw
String.raw`assda\\naadasd`

//代码模拟(略)
复制代码

第三章 函数

一、默认参数值

ES5默认参数值

下面函数存在什么问题 ???

function makeRequest(url, timeout, callback) {
    
    timeout = timeout || 2000;
    callback = callback || function() {};
    
}
复制代码

假如timeout传入值0,这个值是合法的,但是也会被视为一个假值,并最终将timeout赋值为2000。在这种情况下,更安全的选择是通过typeof检查参数类型,如下:

function makeRequest(url, timeout, callback) {
    
    timeout = (typeof timeout !== 'undefined') ? timeout :2000;
    callback = (typeof callback !== 'undefined') ? callback : function() {};
    
}
复制代码

ES5默认参数值

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

//特别注意:此时 null 是一个合法值,所以不会使用 timeout 默认值,即 timeout = null
makeRequest('/foo', null, function(body){
    doSomething(body);
})
复制代码

二、默认参数值对arguments的影响**

  • ES5:

非严格模式:参数变化,arguments对象随之改变;

严格模式:无论参数如何变化,arguments对象不再随之改变;

  • ES6

非严格模式/严格模式:无论参数如何变化,arguments对象不再随之改变;

注: 在引用参数默认值的时候,只允许引用前面参数的值,即先定义的参数不能访问后定义的参数。这可以用默认参数的临时死区来解释。如下:

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

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

//解释原理:
//add(1, 1)
let first = 1;
let second =1;
//add(undefined, 1)
let first = second;
let second = 1; //处于临时死区
复制代码

三、不定参数的使用限制

  1. 每个函数最多只能声明一个不定参数,而且移动要放在所有参数的末尾。
  2. 不定参数不能用于对象字面量setter之中(因为对象字面量setter的参数有且只有一个,而在不定参数的定义中,参数的数量可以无限多

无论是否使用不定参数,arguments对象总是包含所有传入函数的参数。

四、展开运算符

let value = [25, 50, 75, 100];
//es5
console.log(Math.max.apply(Math, values); //100
//es6
console.log(Math.max(...values)); //100
复制代码

五、name 属性

两个有关函数名称的特例:

  1. 通过bind(1)函数创建的函数,其名称将带有“bound”前缀;
  2. 通过Function构造函数创建的函数,其名称将是“anonymous”.
var doSomething = function() {
    //空函数
}

console.log(doSomething.bind().name); //'bound doSomething'
console.log((new Function()).name); //'anonymous'
复制代码

切记: 函数name属性的值不一定引用同名变量,它只是协助调试用的额外信息,所以不能使用name属性的值来获取对于函数的引用。

六、明确函数的多重用途

JS函数有两个不同的内部方法:[[call]][[Construct]]

  • 当通过new关键字调用函数是,执行的是 [[Construct]] 函数,它负责创建一个通常被称为实例的新对象,然后再执行函数体,将this绑定到实例上(具有 [[Construct]] 方法的函数被统称为构造函数,箭头函数没有 [[Construct]] 方法 );
  • 如果不通过 new 关键字调用函数,则执行 [[call]] 函数,从而直接执行代码中的函数体;

七、元属性(Metaproperty)new.target

为了解决判断函数是否通过new关键字调用的问题,new.target横空出世 (instance of ---> new.target)

在函数外使用new.target是一个语法错误。

八、块级函数

  • ES5严格模式下,代码块中声明函数会报错;
  • ES6严格模式下, 可以在定义该函数的代码块中访问和调用它 (块级函数提升,let变量不提升);
  • ES6非严格模式下,函数不再提升至代码块的顶部,而是提升至外围函数或全局作用域的顶部。

九、箭头函数

箭头函数与传统的JS函数不同之处主要有一下一个方面:

  1. 没有thissuperargumentsnew.target绑定;
  2. 不能通过new关键字调用;
  3. 没有原型;
  4. 不可以改变this的绑定;
  5. 不支持arguments对象
  6. 不支持重复的命名参数

创建一个空函数

let doNothing = () => {};
复制代码

返回一个对象字面量

let getTempItem = id => ({ id: id, name: "Temp"});
复制代码

创建立即执行的函数

let person = ((name) => {
    
    return {
        getName: function() {
            return name;
        }
    }
    
})("xszi")

console.log(person.getName()); //xszi
复制代码

箭头函数没有this绑定

let PageHandler = {
    
    id: '123456',
    init: function() {
        document.addEventListener("click", function(event){
            this.doSomething(event.type); //抛出错误
        }, false)
    },
    
    doSomething: function(type) {
        console.log("handling " + type + "for" + this.id)
    }
    
}
复制代码

使用bind()方法将函数的this绑定到PageHandler,修正报错:

let PageHandler = {
    
    id: '123456',
    init: function() {
        document.addEventListener("click", (function(event){
            this.doSomething(event.type); //抛出错误
        }).bind(this), false)
    },
    
    doSomething: function(type) {
        console.log("handling " + type + "for" + this.id)
    }
    
}
复制代码

使用箭头函数修正:

let PageHandler = {
    
    id: '123456',
    init: function() {
        document.addEventListener("click", 
            event => this.doSomething(event.type), false);
    },
    
    doSomething: function(type) {
        console.log("handling " + type + "for" + this.id)
    }
    
}
复制代码
  • 箭头函数没有 prototype属性,它的设计初衷是 即用即弃, 不能用来定义新的类型。
  • 箭头函数的中this取决于该函数外部非箭头函数的this值,不能通过call(), apply()bind()方法来改变this的值。

箭头函数没有arguments绑定

始终访问外围函数的arguments对象

十、尾调用优化

  • ES5中,循环调用情况下,每一个未完成的栈帧都会保存在内存中,当调用栈变的过大时会造成程序问题。
  • ES6中尾调用优化,需要满足一下三个条件:
    • 尾调用不访问当前栈帧的变量(也就是说函数不是一个闭包);
    • 在函数内部,尾调用是最后一条语句;
    • 尾调用的结果作为函数值返回;

如何利用尾调用优化

function factorial(n) {
    if ( n<=1 ) {
        return 1;
    }
}else{
    
    //引擎无法自动优化,必须在返回后执行乘法操作 
    return n * factorial(n-1);
    //随调用栈尺寸的增大,存在栈溢出的风险
}
复制代码
function factorial(n, p = 1) {
    if ( n<=1 ) {
        return 1 * p;
    }
}else{
    let result = n * p;
    //引擎可自动优化
    return  factorial(n-1, result);
    //不创建新的栈帧,而是消除并重用当前栈帧
}
复制代码



原文地址:访问原文地址
快照地址: 访问文章快照