27.前端基础-ES基础1

6.1 es6中箭头函数

参考答案:

  1. 基本语法

    ES6中允许使用箭头=>来定义箭头函数,具体语法,我们来看一个简单的例子:

    // 箭头函数
    let fun = (name) => {
        // 函数体
        return `Hello ${name} !`;
    };
    
    // 等同于
    let fun = function (name) {
        // 函数体
        return `Hello ${name} !`;
    };

    可以看出,定义箭头函在数语法上要比普通函数简洁得多。箭头函数省去了function关键字,采用箭头=>来定义函数。函数的参数放在=>前面的括号中,函数体跟在=>后的花括号中。

    关于箭头函数的参数:

    如果箭头函数没有参数,直接写一个空括号即可。

    如果箭头函数的参数只有一个,也可以省去包裹参数的括号。

    如果箭头函数有多个参数,将参数依次用逗号(,)分隔,包裹在括号中即可。

    // 没有参数
    let fun1 = () => {
        console.log(111);
    };
    
    // 只有一个参数,可以省去参数括号
    let fun2 = name => {
        console.log(`Hello ${name} !`)
    };
    
    // 有多个参数
    let fun3 = (val1, val2, val3) => {
        return [val1, val2, val3];
    };

    关于箭头函数的函数体:

    如果箭头函数的函数体只有一句代码,就是简单返回某个变量或者返回一个简单的JS表达式,可以省去函数体的大括号{ }。

    let f = val => val;
    // 等同于
    let f = function (val) { return val };
    
    let sum = (num1, num2) => num1 + num2;
    // 等同于
    let sum = function(num1, num2) {
      return num1 + num2;
    };

    如果箭头函数的函数体只有一句代码,就是返回一个对象,可以像下面这样写:

    // 用小括号包裹要返回的对象,不报错
    let getTempItem = id => ({ id: id, name: "Temp" });
    
    // 但绝不能这样写,会报错。
    // 因为对象的大括号会被解释为函数体的大括号
    let getTempItem = id => { id: id, name: "Temp" };

    如果箭头函数的函数体只有一条语句并且不需要返回值(最常见是调用一个函数),可以给这条语句前面加一个void关键字

    let fn = () => void doesNotReturn();

    箭头函数最常见的用处就是简化回调函数。

    // 例子一
    // 正常函数写法
    [1,2,3].map(function (x) {
      return x * x;
    });
    
    // 箭头函数写法
    [1,2,3].map(x => x * x);
    
    // 例子二
    // 正常函数写法
    var result = [2, 5, 1, 4, 3].sort(function (a, b) {
      return a - b;
    });
    
    // 箭头函数写法
    var result = [2, 5, 1, 4, 3].sort((a, b) => a - b);
  2. 箭头函数与普通函数的区别

    2.1 语法更加简洁、清晰

    从上面的基本语法示例中可以看出,箭头函数的定义要比普通函数定义简洁、清晰得多,很快捷。

    2.2 箭头函数不会创建自己的this

    箭头函数没有自己的this,它会捕获自己在定义时(注意,是定义时,不是调用时)所处的外层执行环境的this,并继承这个this值。所以,箭头函数中this的指向在它被定义的时候就已经确定了,之后永远不会改变。

    var id = 'Global';
    
    function fun1() {
        // setTimeout中使用普通函数
        setTimeout(function(){
            console.log(this.id);
        }, 2000);
    }
    
    function fun2() {
        // setTimeout中使用箭头函数
        setTimeout(() => {
            console.log(this.id);
        }, 2000)
    }
    
    fun1.call({id: 'Obj'});     // 'Global'
    
    fun2.call({id: 'Obj'});     // 'Obj'

    上面这个例子,函数fun1中的setTimeout中使用普通函数,2秒后函数执行时,这时函数其实是在全局作用域执行的,所以this指向Window对象,this.id就指向全局变量id,所以输出'Global'。 但是函数fun2中的setTimeout中使用的是箭头函数,这个箭头函数的this在定义时就确定了,它继承了它外层fun2的执行环境中的this,而fun2调用时thiscall方法改变到了对象{id: 'Obj'}中,所以输出'Obj'

    var id = 'GLOBAL';
    var obj = {
      id: 'OBJ',
      a: function(){
        console.log(this.id);
      },
      b: () => {
        console.log(this.id);
      }
    };
    
    obj.a();    // 'OBJ'
    obj.b();    // 'GLOBAL'

    上面这个例子,对象obj的方法a使用普通函数定义的,普通函数作为对象的方法调用时,this指向它所属的对象。所以,this.id就是obj.id,所以输出'OBJ'。 但是方法b是使用箭头函数定义的,箭头函数中的this实际是继承的它定义时所处的全局执行环境中的this,所以指向Window对象,所以输出'GLOBAL'。(这里要注意,定义对象的大括号{}是无法形成一个单独的执行环境的,它依旧是处于全局执行环境中!!

  3. 箭头函数继承而来的this指向永远不变(重要!!深入理解!!)

    上面的例子,就完全可以说明箭头函数继承而来的this指向永远不变。对象obj的方法b是使用箭头函数定义的,这个函数中的this永远指向它定义时所处的全局执行环境中的this,即便这个函数是作为对象obj的方法调用,this依旧指向Window对象。

  4. .call()/.apply()/.bind()无法改变箭头函数中this的指向

    .call()/.apply()/.bind()方法可以用来动态修改函数执行时this的指向,但由于箭头函数的this定义时就已经确定且永远不会改变。所以使用这些方法永远也改变不了箭头函数this的指向,虽然这么做代码不会报错。

    var id = 'Global';
    // 箭头函数定义在全局作用域
    let fun1 = () => {
        console.log(this.id)
    };
    
    fun1();     // 'Global'
    // this的指向不会改变,永远指向Window对象
    fun1.call({id: 'Obj'});     // 'Global'
    fun1.apply({id: 'Obj'});    // 'Global'
    fun1.bind({id: 'Obj'})();   // 'Global'
  5. 箭头函数不能作为构造函数使用

    我们先了解一下构造函数的new都做了些什么?简单来说,分为四步: ① JS内部首先会先生成一个对象; ② 再把函数中的this指向该对象; ③ 然后执行构造函数中的语句; ④ 最终返回该对象实例。

    但是因为箭头函数没有自己的this,它的this其实是继承了外层执行环境中的this,且this指向永远不会随在哪里调用、被谁调用而改变,所以箭头函数不能作为构造函数使用,或者说构造函数不能定义成箭头函数,否则用new调用时会报错

    let Fun = (name, age) => {
        this.name = name;
        this.age = age;
    };
    
    // 报错
    let p = new Fun('cao', 24);
  6. 箭头函数没有自己的arguments

    箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是外层局部(函数)执行环境中的值。

    // 例子一
    let fun = (val) => {
        console.log(val);   // 111
        // 下面一行会报错
        // Uncaught ReferenceError: arguments is not defined
        // 因为外层全局环境没有arguments对象
        console.log(arguments); 
    };
    fun(111);
    
    // 例子二
    function outer(val1, val2) {
        let argOut = arguments;
        console.log(argOut);    // ①
        let fun = () => {
            let argIn = arguments;
            console.log(argIn);     // ②
            console.log(argOut === argIn);  // ③
        };
        fun();
    }
    outer(111, 222);

    上面例子二,①②③处的输出结果如下:

    很明显,普通函数outer内部的箭头函数fun中的arguments对象,其实是沿作用域链向上访问的外层outer函数的arguments对象。

    可以在箭头函数中使用rest参数代替arguments对象,来访问箭头函数的参数列表!!

  7. 箭头函数没有原型prototype

    let sayHi = () => {
        console.log('Hello World !')
    };
    console.log(sayHi.prototype); // undefined
  8. 箭头函数不能用作Generator函数,不能使用yeild关键字

6.2 ES6新特性

参考答案:

  1. 变量和作用域

    1.1 let 、const、 块级作用域和变量声明

    let声明的变量只在所在块中生效

    let声明的变量可以解决var与for循环结合使用产生的无法取得最新变量值的问题(以往都需要通过闭包来解决这个问题);

    let声明的变量不存在变量提升(从undefined->ReferenceError,其实也是一种暂时性死区)、会造成变量暂时性死区(在声明let变量之前都不能用它)、也不允许重复声明;

    const声明的变量行为与let类似,只是多了两点更强的约束:1.声明时必须赋值;2.声明的变量内存地址不可变,需要注意的是:对于用const声明基本类型,值就保存在内存地址之中,意味着变量不可重新赋值;对于用const声明的对象,对象内容还是可以更改的,只是不能改变其指向。(冻结对象应该用Object.freeze())

    1.2 解构赋值(按照一定的结构解析出来进行赋值)

    解构赋值的使用场景:变量快捷赋值、提取数据、函数参数定义和默认值、遍历某结构

  2. 原生对象的方法扩展

    2.1 String

    加强了对unicode的支持、支持字符串遍历(后面有讲到实际上是部署了iterator接口)、repeat()等方法的支持、模板字符串

    2.2 RegExp

    构造函数第一个参数是正则表达式,指定第二个参数不再报错、u修饰符、y修饰符、s修饰符

    2.3 Number

    二进制和八进制新写法、新方法parseInt()、Number.EPSILON极小常量、安全整数、Math新方法

    2.4 Function

    函数参数默认值、rest参数、函数内部严格模式、函数的name属性、箭头函数

    2.5 Array

    扩展运算符...

    2.6 Object 和 Symbol

    ​ (1) Object对象

    ​ 支持简写:同名属性K-V可以省略一个、函数声明可以省略function;支持属性名表达式、函数名表达 式。(注意:以上2个——表达式和简写不能同时使用)。

    ​ 对象的方法的name属性返回方法名,但有几个例外情况要小心。新增了Object方法

    ​ Object.is()——用于解决==和===的部分兼容问题

    ​ Object.assign()——将src的所有可枚举对象属性复制到dest对象上(浅复制)

    ​ Object.setPrototypeOf()、Object.getPrototypeOf() (Object.__proto属性)

    ​ Object.entries()、Object.keys()、Object.values()

    ​ ES6中5种遍历对象属性的方法

    for-in——自身和继承的可枚举属性(除Symbol)

    Object.keys()——自身非继承的可枚举属性(除Symbol)

    Object.getOwnPropertyNames()——自身所有属性键名(包括不可枚举、除Symbol)

    Object.getOwnPropertySymbols()——自身的所有 Symbol 属性的键名

    Reflect.ownKeys()——自身的所有键名

    (2)Symbol类型

    ​ ES5以前,对象属性都只能是字符串,容易造成重命名导致的冲突。Symbol提供了一种机制,可以保存 属性名是独一无二的。Symbol类型的使用注意:1)创建是调用函数,而不是new关键字 2)Symbol类 型的属性不会被for-*、Object.keys()、Object.getPropertyNames()返回,可以用后面两种方法遍历。

  3. 数据结构Set和Map

    Set是一种类似数组的数据结构,区别在于其存储的成员都是不重复的,由此带来了它的一个应用就是:去重。Set通过new关键字实例化,入参可以是数组or类数组的对象。

    值得注意的是:在Set中,只能存储一个NaN,这说明在Set数据结构中,NaN等于NaN

    Set实例的方法:操作方法add()、delete()、has()和clear();遍历方法:keys()、values()、entries()和forEach();扩展运算符...、数组方法map()、filter()方法也可以用于Set结构。由此它可以很方便的实现数组的交、并、差集。

    WeakSet类似于Set,主要区别在于1.成员只能是对象类型;2.对象都是弱引用(如果其他对象都不再引用该对象,垃圾回收机制会自动回收该对象所占的内存,不可预测何时会发生,故WeakSet不可被遍历)

    JavaScript对象Object都是键值K-V对的集合,但K取值只能是字符串和Symbol,Map也是K-V的集合,然而其K可以取任意类型。如果需要键值对的集合,Map比Object更适合。Map通过new关键字实例化。

    Map实例的方法:set()、get()、has()、delete()和clear();遍历方法同Set。

    Map与其它数据结构的互相转换:Map <---> 数组| Map <---> 对象| Map <---> JSON。

    WeakMap类似于Map,主要区别在于:1.只接受对象作为键名;2.键名所指向的对象不计入垃圾回收机制

  4. 元编程相关Proxy和Reflect

    4.1 Proxy

    ​ 对目标对象加一层“拦截”(“代理”),外界对对象的访问、修改都必须先通过这层拦截层。因而它提供了 一个机制可以对外界的访问进行过滤和改写。

    ​ 用法:var proxy = new Proxy(p1,p2); p1是要被代理的目标对象,p2是配置对象。

    ​ 值得注意的是:Proxy不是对目标对象透明的代理——即使不做任何拦截的情况下无法保证代理对象与目 标对象行为的完全一致。(主要原因在于代理时,目标对象内部的this会指向代理对象)

    4.2 Reflect

    ​ 与Proxy一样是ES6为语言层面的用于操作对象提供的新API,目前它所拥有的对象方法与Proxy对象一一对 应,引入目的:1.将Object对象上一些属于语言内部的方法放在Reflect上(目前都可以放)2.修改Object对 象上某些方法的返回值,使得更加合理化(健壮)3.让Object对象的操作从命令式完全转化为函数式

  5. 异步编程Promise、Generator和Async

    在JavaScript的世界里,对于异步编程存在如下几种方案:1.回调函数;2.事件触发监听;3.发布订阅者模式;4.Promise。首先介绍Promise,然后介绍ES6提供的生成器函数,async函数。

    Promise来源于社区,代表一个对象,它代表异步操作未来的一个结果(承诺)。它总共有三个状态,pending\fulfilled\rejected。另外,它的状态翻转路径只有两个:pending->fulfilled or pending->rejected,一旦状态翻转,就不可变了。它支持链式调用,支持错误传递,支持以同步代码的方式写异步操作。

    Promise是一个对象,创建此对象实例的方法如下(可以理解resolve和reject是已返回的承诺对象未来回调函数的占位)

    Generator函数是ES6提供的异步编程解决方案。对于Generator函数,可以将它理解为一个状态机,封装了多个内部状态;此外它还是一个遍历器生成函数,这个函数可以遍历出状态机的所有状态。

    函数特征:关键字function与函数名之间有*,函数体内部yeild关键字。

    生成器函数与普通函数的区别:函数调用后不执行,而是返回一个指针对象(遍历器对象)。调用对象的next()方法,执行一段yield逻辑。故函数的分段执行的,yield是暂停执行的标志,next()可以恢复执行

    yield与return的区别:yield有记忆功能,return没有;一个函数可以多次执行yeild,但只会return一次

    async函数是Generator函数的语法糖,它进行了改进:1.自带执行器;2.返回值是Promise;

    三家对比:使用Promise的异步代码存在大量自有API的调用,操作本身的语义夹杂其中,不是很清晰;Generator函数实现的异步代码语义比Promise清晰,但需要一个执行器;async函数的写法最简洁、符合语义,不需要执行器

  6. 语言层面类、模块的支持

    6.1 class

    从 ES6 开始,JavaScript 提供了 class 关键字来定义类,尽管,这样的方案仍然是基于原型运行时系统的模拟,大部分功能ES5可以实现。

    构造函数的prototype属性在 ES6 的“类”上面继续存在。事实上,类中所有方法都定义在类的prototype属性上面(因而也是不可枚举的)。

    constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方***被默认添加。(默认构造函数);constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。

    注意区别:类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。

    类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。

    实例属性除了定义在constructor()方法里面的this上面,也可以定义在类的最顶层。

    私有属性和方法如何实现?1.命名上加以区别 2.将私有方法移出模块,利用公有方法调用;3.Symbol属性上(都不完美)

    6.2 module

    在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定。

    编译时加载VS运行时加载——对象VS代码

    模块命令:export和import;一个文件即为一个模块,除非导入否则外部无法读取模块属性;

    export支持:变量、函数和类

    export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,下一节的import命令也是如此。

    输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面,改写接口。由于import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。

    使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。

    模块之间也可以继承。

  7. JS中对象分类、及其它原生对象

  8. Iterator

    ES6之前在JS中只有Array和对象可以表示“集合”这种数据结构,ES6中增加了:Set和Map。由此,四种之间互相组合又可以定义新的数据结构。这些新定义的数据结构如何访问呢?遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。遍历器对象本质上是一个指针对象。

    只要为某个数据结构部署 了Iterator接口,则可以称此数据结构是可遍历的。iterator属性部署在Symbol上。如下对象默认部署了Iterator结口:Array Set Map String等。部署iterator结构的要点:1)在Symbol.iterator上部署;2)必须包含next()函数。默认调用iterator接口的场景:解构赋值、...扩展运算符、yeild* 。for-of循环内部调用的即是调用数据机构内部的Symbol.iterator方法。

    for-in和for-of循环

    for-in用于遍历对象属性,对象自身和继承的可枚举属性(不可遍历Symbol属性)

    for-of循环是一种遍历所有数据机构的统一方法。实现原理是数据结构上部署的Symbol.iterator属性。

全部评论

相关推荐

吃不饱的肱二头肌很想退休:tnnd 我以为选妹子呢,亏我兴高采烈的冲进来😠
投递快手等公司10个岗位
点赞 评论 收藏
分享
找不到工作死了算了:没事的,雨英,hr肯主动告知结果已经超越大部分hr了
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务