💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
## ES6 中类的定义 **** 参考答案: ~~~js // 1、类的基本定义 class Parent { constructor(name = "小白") { this.name = name; } } ~~~ ~~~js // 2、生成一个实例 let g_parent = new Parent(); console.log(g_parent); //{name: "小白"} let v_parent = new Parent("v"); // 'v'就是构造函数name属性 , 覆盖构造函数的name属性值 console.log(v_parent); // {name: "v"} ~~~ ~~~js // 3、继承 class Parent { //定义一个类 constructor(name = "小白") { this.name = name; } } class Child extends Parent {} console.log("继承", new Child()); // 继承 {name: "小白"} ~~~ ~~~js // 4、继承传递参数 class Parent { //定义一个类 constructor(name = "小白") { this.name = name; } } class Child extends Parent { constructor(name = "child") { // 子类重写name属性值 super(name); // 子类向父类修改 super一定放第一行 this.type = "preson"; } } console.log("继承", new Child("hello")); // 带参数覆盖默认值 继承{name: "hello", type: "preson"} ~~~ ~~~js // 5、ES6重新定义的ES5中的访问器属性 class Parent { //定义一个类 constructor(name = "小白") { this.name = name; } get longName() { // 属性 return "mk" + this.name; } set longName(value) { this.name = value; } } let v = new Parent(); console.log("getter", v.longName); // getter mk小白 v.longName = "hello"; console.log("setter", v.longName); // setter mkhello ~~~ ~~~js // 6、类的静态方法 class Parent { //定义一个类 constructor(name = "小白") { this.name = name; } static tell() { // 静态方法:通过类去调用,而不是实例 console.log("tell"); } } Parent.tell(); // tell ~~~ ~~~js // 7、类的静态属性: class Parent { //定义一个类 constructor(name = "小白") { this.name = name; } static tell() { // 静态方法:通过类去调用,而不是实例 console.log("tell"); // tell } } Parent.type = "test"; // 定义静态属性 console.log("静态属性", Parent.type); // 静态属性 test let v_parent = new Parent(); console.log(v_parent); // {name: "小白"}  没有tell方法和type属性 ~~~ 解析:[参考](https://es6.ruanyifeng.com/#docs/class) ## 谈谈你对 ES6 的理解 **** 参考答案:es6 是一个新的标准,它包含了许多新的语言特性和库,是 JS 最实质性的一次升级。 比如'箭头函数'、'字符串模板'、'generators(生成器)'、'async/await'、'解构赋值'、'class'等等,还有就是引入 module 模块的概念。 箭头函数可以让 this 指向固定化,这种特性很有利于封装回调函数 * (1)函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。 * (2)不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误。 * (3)不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 Rest 参数代替。 * (4)不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。 * async/await 是写异步代码的新方式,以前的方法有回调函数和 Promise。 * async/await 是基于 Promise 实现的,它不能用于普通的回调函数。async/await 与 Promise 一样,是非阻塞的。 * async/await 使得异步代码看起来像同步代码,这正是它的魔力所在。 解析:[参考](https://www.cnblogs.com/heweijain/p/7073553.html) ## 说说你对 promise 的了解**** 高级前端会考 自己实现 promise 参考答案:Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件监听——更合理和更强大。 所谓 Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。 Promise 对象有以下两个特点: 1. 对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。 2. 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。 解析:[参考](https://es6.ruanyifeng.com/#docs/promise) ## 解构赋值及其原理 **** 参考答案: 解构赋值:其实就是分解出一个对象的解构,分成两个步骤: 1. 变量的声明 2. 变量的赋值 原理:ES6 变量的解构赋值本质上是“模式匹配”, 只要等号两边的模式相同,左边的变量就会被赋予匹配的右边的值,如果匹配不成功变量的值就等于 undefined 解析: 一、 数组的解构赋值 ~~~js // 对于数组的解构赋值,其实就是获得数组的元素,而我们一般情况下获取数组元素的方法是通过下标获取,例如: let arr = [1, 2, 3]; let a = arr[0]; let b = arr[1]; let c = arr[2]; // 而数组的解构赋值给我们提供了极其方便的获取方式,如下: let [a, b, c] = [1, 2, 3]; console.log(a, b, c); //1,2,3 ~~~ 1. 模式匹配解构赋值 ~~~js let [foo, [ [bar], baz ]] = [1, [ [2], 3 ]]; console.log(foo, bar, baz); //1,2,3 ~~~ 2. 省略解构赋值 ~~~js let [, , a, , b] = [1, 2, 3, 4, 5]; console.log(a, b); //3,5 ~~~ 3. 含剩余参数的解构赋值 ~~~js let [a, ...reset] = [1, 2, 3, 4, 5]; console.log(a, reset); //1,[2,3,4,5] ~~~ 其转成 ES5 的原理如下: ~~~js var a = 1, reset = [2, 3, 4, 5]; console.log(a, reset); //1,[2,3,4,5] ~~~ 注意:如果剩余参数是对应的值为 undefined,则赋值为\[\],因为找不到对应值的时候,是通过 slice 截取的,如下: ~~~js let [a, ...reset] = [1]; console.log(a, reset); //1,[] ~~~ 其转成 ES5 的原理如下: ~~~js var _ref = [1], a = _ref[0], reset = _ref.slice(1); console.log(a, reset); //1,[] ~~~ 4. 非数组解构成数组(重点,难点) 一条原则:要解构成数组的前提:如果等号右边,不是数组(严格地说,不是可遍历的解构),则直接报错,例如: ~~~js let [foo] = 1; //报错 let [foo1] = false; //报错 let [foo2] = NaN; //报错 let [foo3] = undefined; //报错 let [foo4] = null; //报错 let [foo5] = {}; //报错 ~~~ 为什么?转成 ES5 看下原理就一清二楚了: ~~~js var _ = 1, foo = _[0]; //报错 var _false = false, foo1 = _false[0]; //报错 var _NaN = NaN, foo2 = _NaN[0]; //报错 var _undefined = undefined, foo3 = _undefined[0]; //报错 var _ref = null; foo4 = _ref[0]; //报错 var _ref2 = {}, foo5 = _ref2[0]; //报错 ~~~ 5. Set 的解构赋值 先执行 new Set()去重,然后对得到的结果进行解构 ~~~js let [a, b, c] = new Set([1, 2, 2, 3]); console.log(a, b, c); //1,2,3 ~~~ 6. 迭代器解构 ~~~js function* fibs() { let a = 0; let b = 1; while (true) { yield a; [a, b] = [b, a + b]; } } let [first, second, third, fourth, fifth, sixth] = fibs(); sixth; // 5 ~~~ ### [](https://github.com/yisainan/web-interview/blob/master/content/js/es6.md#%E6%80%BB%E7%BB%93-1%E5%8F%AA%E8%A6%81%E6%9F%90%E7%A7%8D%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%85%B7%E6%9C%89-iterator-%E6%8E%A5%E5%8F%A3%E9%83%BD%E5%8F%AF%E4%BB%A5%E9%87%87%E7%94%A8%E6%95%B0%E7%BB%84%E5%BD%A2%E5%BC%8F%E7%9A%84%E8%A7%A3%E6%9E%84%E8%B5%8B%E5%80%BC)总结 1:只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。 7. 解构赋值的默认值 当变量严格等于 undefined 的时候,会读取默认值,所谓的严格等于,就是“===” ~~~js -- -- -- -- -- let [a, b = 'default'] = [1]; console.log(a, b); //1,'default' -- -- -- -- -- let [c = 'default'] = [undefined]; console.log(c); //'default' -- -- -- -- -- function f() { console.log('aaa'); } let [x = f()] = [1]; console.log(x); //1 -- -- -- -- -- function f() { console.log('aaa'); //'aaa' } let [a, x = f()] = [1]; console.log(a, x); //1,undefined ~~~ ### [](https://github.com/yisainan/web-interview/blob/master/content/js/es6.md#%E6%80%BB%E7%BB%93-2%E5%A6%82%E6%9E%9C%E4%B8%8D%E4%BD%BF%E7%94%A8%E9%BB%98%E8%AE%A4%E5%80%BC%E5%88%99%E4%B8%8D%E4%BC%9A%E6%89%A7%E8%A1%8C%E9%BB%98%E8%AE%A4%E5%80%BC%E7%9A%84%E5%87%BD%E6%95%B0)总结 2:如果不使用默认值,则不会执行默认值的函数 二、对象的解构赋值 1. 解构赋值的举例: ~~~js let p1 = { name: "zhuangzhuang", age: 25 }; let { name, age } = p1; //注意变量必须为属性名 console.log(name, age); //"zhuangzhuang",25 ~~~ 其转成 es5 的原理则为: ~~~js var _p1 = p1, name = _p1.name, age = _p1.age; console.log(name, age); //"zhuangzhuang",25 ~~~ 2. 解构赋值的别名 如果使用别名,则不允许再使用原有的解构出来的属性名,看以下举例则会明白: ~~~js let p1 = { name: "zhuangzhuang", age: 25 }; let { name: aliasName, age: aliasAge } = p1; //注意变量必须为属性名 console.log(aliasName, aliasAge); //"zhuangzhuang",25 console.log(name, age); //Uncaught ReferenceError: age is not defined ~~~ 为何打印原有的属性名则会报错?让我们看看转成 es5 后的原理是如何实现的: ~~~js var _p1 = p1, aliasName = _p1.name, aliasAge = _p1.age; console.log(aliasName, aliasAge); //"zhuangzhuang",25 console.log(name, age); //所以打印name和age会报错——“Uncaught ReferenceError: age is not defined”,但是为何只报错age,不报错name呢? ~~~ 只报错 age,不报错 name,这说明其实 name 是存在的,那么根据 js 的解析顺序,当在当前作用域 name 无法找到时,会向上找,直到找到 window 下的 name, 而我们打印 window 可以发现,其下面确实有一个 name,值为“”,而其下面并没有属性叫做 age,所以在这里 name 不报错,只报 age 的错。类似 name 的属性还有很多,比如 length 等。 3. 解构赋值的默认值 有些情况下,我们解构出来的值并不存在,所以需要设定一个默认值,例如: ~~~js let obj = { name: "zhuangzhuang" }; let { name, age } = obj; console.log(name, age); //"zhuangzhuang",undefined ~~~ 我们可以看到当 age 这个属性并不存在于 obj 的时候,解构出来的值为 undefined,那么为了避免这种尴尬的情况,我们常常会设置该属性的默认值,如下: ~~~js let obj = { name: "zhuangzhuang" }; let { name, age = 18 } = obj; console.log(name, age); //"zhuangzhuang",18 ~~~ 当我们取出来的值不存在,即为 undefined 的时候,则会取默认值(假设存在默认值),ES6 的默认值是使用\*\*“变量=默认值”\*\*的方式。 注意:只有当为 undefined 的时候才会取默认值,null 等均不会取默认值 ~~~js let obj = { name: "zhuangzhuang", age: 27, gender: null, //假设未知使用null isFat: false }; let { name, age = 18, gender = "man", isFat = true, hobbies = "study" } = obj; console.log(name, age, gender, isFat, hobbies); //"zhuangzhuang",27,null,false,"study" ~~~ 4. 解构赋值的省略赋值 当我们并不是需要取出所有的值的时候,其实可以省略一些变量,这就是省略赋值,如下 ~~~js let arr = [1, 2, 3]; let [, , c] = arr; console.log(c); //3 ~~~ 注意:省略赋值并不存在与对象解构,因为对象解构,明确了需要的属性 ~~~js let obj = { name: "zhuangzhuang", age: 27, gender: "man" }; let { age } = obj; console.log(age); //27 ~~~ 5. 解构赋值的嵌套赋值(易错点,重点,难点) ~~~js let obj = {}, arr = []; ({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true }); console.log(obj, arr); //{prop:123},[true] ~~~ 注意当解构出来是 undefined 的时候,如果再给子对象的属性,则会报错,如下 ~~~js let { foo: { bar } } = { baz: "baz" }; //报错,原因很简单,看下原理即可,如下: //原理: let obj = { baz: "baz" }; let foo = obj.foo; //foo为undefined let bar = foo.bar; //undefined的bar,可定报错 ~~~ 6. {}是块还是对象? 当我们写解构赋值的时候,很容易犯一个错误——{}的作用是块还是对象混淆,举例如下: ~~~js //举例一: let { a } = { a: "a" }; console.log(a); //'a',这个很简单 //很多人觉得,以下这种写法也是可以的: let a; { a } = { a: "a" }; //直接报错,因为此时a已经声明过了,在语法解析的时候,会将这一行的{}看做块结构,而“块=对象”,显然是语法错误,所以正确的做法是不将大括号写在开头,如下: let a; ({ a } = { a: "a" }) ~~~ 7. 空解构 按照之前写的,解构赋值,左边则为解构出来的属性名,当然,在这里,我们也可以不写任何属性名称,也不会又任何的语法错误,即便这样没有任何意义,如下: ~~~js ({} = [true, false]); ({} = "abc"); ({} = []); ~~~ 8. 解构成对象的原则 如果解构成对象,右侧不是 null 或者 undefined 即可! 之前说过,要解构成数组,右侧必须是可迭代对象,但是如果解构成对象,右侧不是 null 活着 undefined 即可! 三、字符串的解构赋值 字符串也是可以解构赋值的 ~~~js const [a, b, c, d, e] = "hello"; console.log(a, b, c, d, e); //'h','e','l','l','o' ~~~ 转成 es5 的原理如下: ~~~js var _hello = "hello", a = _hello[0], b = _hello[1], c = _hello[2]; console.log(a, b, c); ~~~ 注意:字符串有一个属性 length,也可以被解构出来,但是要注意,解构属性一定是对象解构 ~~~js let { length } = "hello"; console.log(length); //5 ~~~ 4. 布尔值和数值的解构 布尔值和数值的解构,其实就是对其包装对象的解构,取的是包装对象的属性 ~~~js { toString: s } = 123; console.log(s); //s === Number.prototype.toString { toString: s } = true; console.log(s); //s === Boolean.prototype.toString ~~~ ### [](https://github.com/yisainan/web-interview/blob/master/content/js/es6.md#%E6%80%BB%E7%BB%93%E8%A7%A3%E6%9E%84%E8%B5%8B%E5%80%BC%E7%9A%84%E8%A7%84%E5%88%99%E6%98%AF)总结:解构赋值的规则是: > 1. 解构成对象,只要等号右边的值不是对象或数组,就先将其转为对象。由于 undefined 和 null 无法转为对象,所以对它们进行解构赋值,都会报错。 > 2. 解构成数组,等号右边必须为可迭代对象 [参考](https://blog.csdn.net/qq_17175013/article/details/81490923) ## var let 在 for 循环中的区别**** 参考答案: ~~~js //使用var声明,得到3个3 var a = []; for (var i = 0; i < 3; i++) { a[i] = function () { console.log(i); }; } a[0](); //3 a[1](); //3 a[2](); //3 //使用let声明,得到0,1,2 var a = []; for (let i = 0; i < 3; i++) { a[i] = function () { console.log(i); }; } a[0](); //0 a[1](); //1 a[2](); //2 ~~~ ~~~js for(var i=0;i<5;i++){ setTimeout(()=>{ console.log(i);//5个5 },100) } console.log(i);//5 console.log('=============') for(let j=0;j<5;j++){ setTimeout(()=>{ console.log(j);//0,1,2,3,4 },100) } console.log(j);//报错 j is not defined ~~~ var是全局作用域,有变量提升的作用,所以在for中定义一个变量,全局可以使用,循环中的每一次给变量i赋值都是给全局变量i赋值。 let是块级作用域,只能在代码块中起作用,在js中一个{}中的语句我们也称为叫一个代码块,每次循环会产生一个代码块,每个代码块中的都是一个新的变量i; 解析:[参考](https://www.cnblogs.com/fanfanZhao/p/12179508.html) ## 模板字符串 ***** 参考答案: * 就是这种形式${varible}, 在以往的时候我们在连接字符串和变量的时候需要使用这种方式'string' + varible + 'string'但是有了模版语言后我们可以使用string${varible}string 这种进行连接。基本用途有如下: 1、基本的字符串格式化,将表达式嵌入字符串中进行拼接,用${}来界定。 ~~~js //es5 var name = "lux"; console.log("hello" + name); //es6 const name = "lux"; console.log(`hello ${name}`); //hello lux ~~~ 2、在 ES5 时我们通过反斜杠()来做多行字符串或者字符串一行行拼接,ES6 反引号(``)直接搞定。 ~~~js //ES5 var template = "hello \ world"; console.log(template); //hello world //ES6 const template = `hello world`; console.log(template); //hello 空行 world ~~~ ## 箭头函数需要注意的地方**** 参考答案: ~~~ 箭头函数有几个使用注意点。 (1)函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。 (2)不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误。 (3)不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。 (4)不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。 ~~~ 上面四点中,第一点尤其值得注意。this 对象的指向是可变的,但是在箭头函数中,它是固定的。 ~~~js function foo() { setTimeout(() => { console.log("id:", this.id); }, 100); } var id = 21; foo.call({ id: 42 }); // id: 42 ~~~ 解析:[参考](https://www.jianshu.com/p/bc28e4f67ef9) ## 箭头函数和普通函数有什么区别**** 参考答案: * 函数体内的`this`对象,就是定义时所在的对象,而不是使用时所在的对象,用`call``apply``bind`也不能改变`this`指向 * 不可以当作构造函数,也就是说,不可以使用`new`命令,否则会抛出一个错误。 * 不可以使用`arguments`对象,该对象在函数体内不存在。如果要用,可以用`rest`参数代替。 * 不可以使用`yield`命令,因此箭头函数不能用作`Generator`函数。 * 箭头函数没有原型对象`prototype` ## Promise 构造函数是同步执行还是异步执行,那么 then 方法呢?**** 参考答案: ~~~js const promise = new Promise((resolve, reject) => { console.log(1) resolve() console.log(2) }) promise.then(() => { console.log(3) }) console.log(4) ~~~ 执行结果是:1243 promise构造函数是同步执行的,then方法是异步执行的