这里写目录标题

        • 1.新增了块级作用域(let,const)
          • let 命令
            • 基本用法:
            • 代码块内有效:
            • **不能重复声明**
            • 不存在变量提升
          • const 命令
            • 基本用法:
          • 注意:
        • 2.提供了定义类的语法糖(class)
          • 类的由来
          • constructor 方法
          • 类的实例
          • 取值函数(getter)和存值函数(setter)
          • 属性表达式
          • Class 表达式
          • 注意点
            • **(1)严格模式**
            • **(2)不存在提升**
            • **(3)name 属性**
            • **(4)Generator 方法**
            • **(5)this 的指向**
          • 静态方法
          • 实例属性的新写法
          • 静态属性
          • 私有方法和私有属性
            • 现有的解决方案
          • 私有属性的提案
          • new.target 属性
        • 3.新增了一种基本数据类型(Symbol)
          • Symbol.hasInstance
          • Symbol.isConcatSpreadable
          • Symbol.match, Symbol.search, Symbol.replace, Sybmol.split
          • Symbol.toPrimitive
          • Symbol.toStringTag
          • Symbol.species
        • 4.增了变量的解构赋值
          • 解构模型
            • 数组的解构赋值
            • 对象的解构赋值
        • 5.函数参数允许设置默认值,引入了rest参数,新增了箭头函数。
            • 函数可以设置参数默认值
          • 1.箭头函数基本形式
          • 2. 箭头函数基本特点
            • (1). 箭头函数this为父作用域的this,不是调用时的this
            • 箭头函数使用的注意的地方:
        • 6.数组新增了一些API,如isArray / from / of 方法;数组实例新增了 entries(),keys() 和 values() 等方法。
          • 1、Array.from()方法
          • 2、使用Array.of()方法
          • 3、find()方法
          • 4、fill()方法
          • 5、遍历数组的方法:entries()、values()、keys()
        • 7.对象和数组新增了扩展运算符
            • 对象的扩展运算符:
            • 数组的扩展运算符
            • 扩展运算符可以与解构赋值结合起来,用于生成数组
            • 扩展运算符还可以将字符串转为真正的数组
            • 任何 Iterator 接口的对象(参阅 Iterator 一章),都可以用扩展运算符转为真正的数组
        • 8.ES6新增了模块化(import / export)
            • export的用法
            • 默认导出(default export)
            • 重命名export和import
        • 9.ES6新增了Set和Map数据结构。
          • 1.单一数组的去重
          • 2.多数组的合并去重
          • 操作
            • 1.add
            • 2.delete
            • 3.has
            • 4.clear
            • 5.size属性
            • 6.Array 转 Set
            • 7.Set 转 Array
            • 8.遍历(keys(),values(),entries())
          • Map
            • **特性**
            • 操作
            • 1.size
            • 2.set
            • 3.get
            • 4.has
            • 5.delete
            • 6.clear
            • 遍历(keys(),values(),entries(),forEach())
          • 和其它结构的互转
            • 1.Map 转 Array
            • 2.Array 转 Map
            • 3.Map 转 Object
            • 4.Object 转 Map
            • 5.Set 转 Map
            • 6.Map 转 Set
        • 10.ES6原生提供Proxy构造函数,用来生成Proxy实例
          • 概述
          • Proxy 实例的方法
            • get()
            • set()
            • apply()
            • has()
            • construct()
            • deleteProperty()
            • defineProperty()
          • Proxy.revocable()
          • this问题
          • 实例: Web 服务的客户端
        • 11.ES6新增了生成器(Generator)和遍历器(Iterator)
          • Iterator 迭代器
            • 1.Iterator的作用:
            • 2.原生具备iterator接口的数据(可用for of遍历)
            • 3.迭代器的工作原理
            • 4.手写一个迭代器
            • 5.注意点
          • Generator生成器
            • 1.概念
            • 2.特点
            • 3.next传递参数
            • 4.与 Iterator 接口的关系
            • 5.Generator的异步的应用

1.新增了块级作用域(let,const)

let 声明的变量只在 let 命令所在的代码块内有效。

const 声明一个只读的常量,一旦声明,常量的值就不能改变。

let 命令
基本用法:
{let a = 0;a   // 0
}
a   // 报错 ReferenceError: a is not defined
代码块内有效:

​ let 是在代码块内有效,var 是在全局范围内有效:

{let a = 0;var b = 1;
}
a  // ReferenceError: a is not defined
b  // 1
不能重复声明

let 只能声明一次 var 可以声明多次:

let a = 1;
let a = 2;
var b = 3;
var b = 4;
a  // Identifier 'a' has already been declared
b  // 4

for 循环计数器很适合用 let

for (var i = 0; i < 10; i++) {setTimeout(function(){console.log(i);})
}
// 输出十个 10
for (let j = 0; j < 10; j++) {setTimeout(function(){console.log(j);})
}
// 输出 0123456789

注:

​ 变量 i 是用 var 声明的,在全局范围内有效,所以全局中只有一个变量 i, 每次循环时,setTimeout 定时器里面的 i 指的是全局变量 i ,而循环里的十个 setTimeout 是在循环结束后才执行,所以此时的 i 都是 10。

​ 变量 j 是用 let 声明的,当前的 j 只在本轮循环中有效,每次循环的 j 其实都是一个新的变量,所以 setTimeout 定时器里面的 j 其实是不同的变量,即最后输出 12345。(若每次循环的变量 j 都是重新声明的,如何知道前一个循环的值?这是因为 JavaScript 引擎内部会记住前一个循环的值)。

不存在变量提升

let 不存在变量提升,var 会变量提升:

console.log(a);  //ReferenceError: a is not defined
let a = "apple";console.log(b);  //undefined
var b = "banana";

注:

​ 变量 b 用 var 声明存在变量提升,所以当脚本开始运行的时候,b 已经存在了,但是还没有赋值,所以会输出 undefined。

变量 a 用 let 声明不存在变量提升,在声明变量 a 之前,a 不存在,所以会报错。

const 命令

const 声明一个只读变量,声明之后不允许改变。意味着,一旦声明必须初始化,否则会报错。

基本用法:
const PI = "3.1415926";
PI  // 3.1415926const MY_AGE;  // SyntaxError: Missing initializer in const declaration  

暂时性死区问题:

var PI = "a";
if(true){console.log(PI);  // ReferenceError: PI is not definedconst PI = "3.1415926";
}

注:

​ ES6 明确规定,代码块内如果存在 let 或者 const,代码块会对这些命令声明的变量从块的开始就形成一个封闭作用域。代码块内,在声明变量 PI 之前使用它会报错。

注意:

​ const 如何做到变量在声明初始化之后不允许改变的?其实 const 其实保证的不是变量的值不变,而是保证变量指向的内存地址所保存的数据不允许改动。此时,你可能已经想到,简单类型和复合类型保存值的方式是不同的。是的,对于简单类型(数值 number、字符串 string 、布尔值 boolean),值就保存在变量指向的那个内存地址,因此 const 声明的简单类型变量等同于常量。而复杂类型(对象 object,数组 array,函数 function),变量指向的内存地址其实是保存了一个指向实际数据的指针,所以 const 只能保证指针是固定的,至于指针指向的数据结构变不变就无法控制了,所以使用 const 声明复杂类型对象时要慎重。

2.提供了定义类的语法糖(class)

类的由来

JavaScript 语言中,生成实例对象的传统方法是通过构造函数。

function Point(x, y) {this.x = x;this.y = y;
}Point.prototype.toString = function () {return '(' + this.x + ', ' + this.y + ')';
};var p = new Point(1, 2);

上面这种写法跟传统的面向对象语言(比如 C++ 和 Java)差异很大,很容易让新学习这门语言的程序员感到困惑。

​ ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。

​ 基本上,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 ES6 的class改写,就是下面这样。

class Point {constructor(x, y) {this.x = x;this.y = y;}toString() {return '(' + this.x + ', ' + this.y + ')';}
}

上面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。也就是说,ES5 的构造函数Point,对应 ES6 的Point类的构造方法。

​ Point类除了构造方法,还定义了一个toString方法。注意,定义“类”的方法的时候,前面不需要加上function`这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。

ES6 的类,完全可以看作构造函数的另一种写法。

class Point {// ...
}typeof Point // "function"
Point === Point.prototype.constructor // true

构造函数的prototype属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。

class Point {constructor() {// ...}toString() {// ...}toValue() {// ...}
}// 等同于Point.prototype = {constructor() {},toString() {},toValue() {},
};

在类的实例上面调用方法,其实就是调用原型上的方法。

class B {}
let b = new B();b.constructor === B.prototype.constructor // true
.

上面代码中,bB类的实例,它的constructor方法就是B类原型的constructor方法。

由于类的方法都定义在prototype对象上面,所以类的新方法可以添加在prototype对象上面。Object.assign方法可以很方便地一次向类添加多个方法。

class Point {constructor(){// ...}
}Object.assign(Point.prototype, {toString(){},toValue(){}
});

prototype对象的constructor属性,直接指向“类”的本身,这与 ES5 的行为是一致的。

Point.prototype.constructor === Point // true

另外,类的内部所有定义的方法,都是不可枚举的(non-enumerable)。

class Point {constructor(x, y) {// ...}toString() {// ...}
}Object.keys(Point.prototype)
// []
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]

上面代码中,toString方法是Point类内部定义的方法,它是不可枚举的。这一点与 ES5 的行为不一致。

var Point = function (x, y) {// ...
};Point.prototype.toString = function() {// ...
};Object.keys(Point.prototype)
// ["toString"]
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]

上面代码采用 ES5 的写法,toString方法就是可枚举的。

constructor 方法

constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。

class Point {
}// 等同于
class Point {constructor() {}
}

上面代码中,定义了一个空的类Point,JavaScript 引擎会自动为它添加一个空的constructor方法。

constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。

class Foo {constructor() {return Object.create(null);}
}new Foo() instanceof Foo
// false

上面代码中,constructor函数返回一个全新的对象,结果导致实例对象不是Foo类的实例。

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

class Foo {constructor() {return Object.create(null);}
}Foo()
// TypeError: Class constructor Foo cannot be invoked without 'new'
类的实例

生成类的实例的写法,与 ES5 完全一样,也是使用new命令。前面说过,如果忘记加上new,像函数那样调用Class,将会报错。

class Point {// ...
}// 报错
var point = Point(2, 3);// 正确
var point = new Point(2, 3);

与 ES5 一样,实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。

//定义类
class Point {constructor(x, y) {this.x = x;this.y = y;}toString() {return '(' + this.x + ', ' + this.y + ')';}}var point = new Point(2, 3);point.toString() // (2, 3)point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') // true

上面代码中,xy都是实例对象point自身的属性(因为定义在this变量上),所以hasOwnProperty方法返回true,而toString是原型对象的属性(因为定义在Point类上),所以hasOwnProperty方法返回false。这些都与 ES5 的行为保持一致。

与 ES5 一样,类的所有实例共享一个原型对象。

var p1 = new Point(2,3);
var p2 = new Point(3,2);p1.__proto__ === p2.__proto__
//true

上面代码中,p1p2都是Point的实例,它们的原型都是Point.prototype,所以__proto__属性是相等的。

这也意味着,可以通过实例的__proto__属性为“类”添加方法。

proto并不是语言本身的特性,这是各大厂商具体实现时添加的私有属性,虽然目前很多现代浏览器的 JS 引擎中都提供了这个私有属性,但依旧不建议在生产中使用该属性,避免对环境产生依赖。生产环境中,我们可以使用Object.getPrototypeOf` 方法来获取实例对象的原型,然后再来为原型添加方法/属性。

var p1 = new Point(2,3);
var p2 = new Point(3,2);p1.__proto__.printName = function () { return 'Oops' };p1.printName() // "Oops"
p2.printName() // "Oops"var p3 = new Point(4,2);
p3.printName() // "Oops"

​ 上面代码在p1的原型上添加了一个printName方法,由于p1的原型就是p2的原型,因此p2也可以调用这个方法。而且,此后新建的实例p3也可以调用这个方法。这意味着,使用实例的__proto__属性改写原型,必须相当谨慎,不推荐使用,因为这会改变“类”的原始定义,影响到所有实例。

取值函数(getter)和存值函数(setter)

​ 与 ES5 一样,在“类”的内部可以使用getset关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。

class MyClass {constructor() {// ...}get prop() {return 'getter';}set prop(value) {console.log('setter: '+value);}
}let inst = new MyClass();inst.prop = 123;
// setter: 123inst.prop
// 'getter'

上面代码中,prop属性有对应的存值函数和取值函数,因此赋值和读取行为都被自定义了。

存值函数和取值函数是设置在属性的 Descriptor 对象上的。

class CustomHTMLElement {constructor(element) {this.element = element;}get html() {return this.element.innerHTML;}set html(value) {this.element.innerHTML = value;}
}var descriptor = Object.getOwnPropertyDescriptor(CustomHTMLElement.prototype, "html"
);"get" in descriptor  // true
"set" in descriptor  // true

上面代码中,存值函数和取值函数是定义在html属性的描述对象上面,这与 ES5 完全一致。

属性表达式

类的属性名,可以采用表达式。

let methodName = 'getArea';class Square {constructor(length) {// ...}[methodName]() {// ...}
}

上面代码中,Square类的方法名getArea,是从表达式得到的。

Class 表达式

与函数一样,类也可以使用表达式的形式定义。

const MyClass = class Me {getClassName() {return Me.name;}
};

上面代码使用表达式定义了一个类。需要注意的是,这个类的名字是Me,但是Me只在 Class 的内部可用,指代当前类。在 Class 外部,这个类只能用MyClass引用。

let inst = new MyClass();
inst.getClassName() // Me
Me.name // ReferenceError: Me is not defined

上面代码表示,Me只在 Class 内部有定义。

如果类的内部没用到的话,可以省略Me,也就是可以写成下面的形式。

const MyClass = class { /* ... */ };

采用 Class 表达式,可以写出立即执行的 Class。

let person = new class {constructor(name) {this.name = name;}sayName() {console.log(this.name);}
}('张三');person.sayName(); // "张三"

上面代码中,person是一个立即执行的类的实例。

注意点
(1)严格模式

类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。考虑到未来所有的代码,其实都是运行在模块之中,所以 ES6 实际上把整个语言升级到了严格模式。

(2)不存在提升

类不存在变量提升(hoist),这一点与 ES5 完全不同。

new Foo(); // ReferenceError
class Foo {}

上面代码中,Foo类使用在前,定义在后,这样会报错,因为 ES6 不会把类的声明提升到代码头部。这种规定的原因与下文要提到的继承有关,必须保证子类在父类之后定义。

{let Foo = class {};class Bar extends Foo {}
}

上面的代码不会报错,因为Bar继承Foo的时候,Foo已经有定义了。但是,如果存在class的提升,上面代码就会报错,因为class会被提升到代码头部,而let命令是不提升的,所以导致Bar继承Foo的时候,Foo还没有定义。

(3)name 属性

由于本质上,ES6 的类只是 ES5 的构造函数的一层包装,所以函数的许多特性都被Class继承,包括name属性。

class Point {}
Point.name // "Point"

name属性总是返回紧跟在class关键字后面的类名。

(4)Generator 方法

如果某个方法之前加上星号(*),就表示该方法是一个 Generator 函数。

class Foo {constructor(...args) {this.args = args;}* [Symbol.iterator]() {for (let arg of this.args) {yield arg;}}
}for (let x of new Foo('hello', 'world')) {console.log(x);
}
// hello
// world

上面代码中,Foo类的Symbol.iterator方法前有一个星号,表示该方法是一个 Generator 函数。Symbol.iterator方法返回一个Foo类的默认遍历器,for...of循环会自动调用这个遍历器。

(5)this 的指向

类的方法内部如果含有this,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。

class Logger {printName(name = 'there') {this.print(`Hello ${name}`);}print(text) {console.log(text);}
}const logger = new Logger();
const { printName } = logger;
printName(); // TypeError: Cannot read property 'print' of undefined

上面代码中,printName方法中的this,默认指向Logger类的实例。但是,如果将这个方法提取出来单独使用,this会指向该方法运行时所在的环境(由于 class 内部是严格模式,所以 this 实际指向的是undefined),从而导致找不到print方法而报错。

一个比较简单的解决方法是,在构造方法中绑定this,这样就不会找不到print方法了。

class Logger {constructor() {this.printName = this.printName.bind(this);}// ...
}

另一种解决方法是使用箭头函数。

class Obj {constructor() {this.getThis = () => this;}
}const myObj = new Obj();
myObj.getThis() === myObj // true

​ 箭头函数内部的this总是指向定义时所在的对象。上面代码中,箭头函数位于构造函数内部,它的定义生效的时候,是在构造函数执行的时候。这时,箭头函数所在的运行环境,肯定是实例对象,所以this会总是指向实例对象。

还有一种解决方法是使用Proxy,获取方法的时候,自动绑定this

function selfish (target) {const cache = new WeakMap();const handler = {get (target, key) {const value = Reflect.get(target, key);if (typeof value !== 'function') {return value;}if (!cache.has(value)) {cache.set(value, value.bind(target));}return cache.get(value);}};const proxy = new Proxy(target, handler);return proxy;
}const logger = selfish(new Logger());
静态方法

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

class Foo {static classMethod() {return 'hello';}
}Foo.classMethod() // 'hello'var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function

上面代码中,Foo类的classMethod方法前有static关键字,表明该方法是一个静态方法,可以直接在Foo类上调用(Foo.classMethod()),而不是在Foo类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。

注意,如果静态方法包含this关键字,这个this指的是类,而不是实例。

class Foo {static bar() {this.baz();}static baz() {console.log('hello');}baz() {console.log('world');}
}Foo.bar() // hello

上面代码中,静态方法bar调用了this.baz,这里的this指的是Foo类,而不是Foo的实例,等同于调用Foo.baz。另外,从这个例子还可以看出,静态方法可以与非静态方法重名。

父类的静态方法,可以被子类继承。

class Foo {static classMethod() {return 'hello';}
}class Bar extends Foo {
}Bar.classMethod() // 'hello'

上面代码中,父类Foo有一个静态方法,子类Bar可以调用这个方法。

静态方法也是可以从super对象上调用的。

class Foo {static classMethod() {return 'hello';}
}class Bar extends Foo {static classMethod() {return super.classMethod() + ', too';}
}Bar.classMethod() // "hello, too"
实例属性的新写法

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

class IncreasingCounter {constructor() {this._count = 0;}get value() {console.log('Getting the current value!');return this._count;}increment() {this._count++;}
}

上面代码中,实例属性this._count定义在constructor()方法里面。另一种写法是,这个属性也可以定义在类的最顶层,其他都不变。

class IncreasingCounter {_count = 0;get value() {console.log('Getting the current value!');return this._count;}increment() {this._count++;}
}

上面代码中,实例属性_count与取值函数value()increment()方法,处于同一个层级。这时,不需要在实例属性前面加上this

这种新写法的好处是,所有实例对象自身的属性都定义在类的头部,看上去比较整齐,一眼就能看出这个类有哪些实例属性。

class foo {bar = 'hello';baz = 'world';constructor() {// ...}
}

上面的代码,一眼就能看出,foo类有两个实例属性,一目了然。另外,写起来也比较简洁。

静态属性

静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。

class Foo {
}Foo.prop = 1;
Foo.prop // 1

上面的写法为Foo类定义了一个静态属性prop

目前,只有这种写法可行,因为 ES6 明确规定,Class 内部只有静态方法,没有静态属性。现在有一个提案提供了类的静态属性,写法是在实例属性的前面,加上static关键字。

class MyClass {static myStaticProp = 42;constructor() {console.log(MyClass.myStaticProp); // 42}
}

这个新写法大大方便了静态属性的表达。

// 老写法
class Foo {// ...
}
Foo.prop = 1;// 新写法
class Foo {static prop = 1;
}

上面代码中,老写法的静态属性定义在类的外部。整个类生成以后,再生成静态属性。这样让人很容易忽略这个静态属性,也不符合相关代码应该放在一起的代码组织原则。另外,新写法是显式声明(declarative),而不是赋值处理,语义更好。

私有方法和私有属性
现有的解决方案

私有方法和私有属性,是只能在类的内部访问的方法和属性,外部不能访问。这是常见需求,有利于代码的封装,但 ES6 不提供,只能通过变通方法模拟实现。

一种做法是在命名上加以区别。

class Widget {// 公有方法foo (baz) {this._bar(baz);}// 私有方法_bar(baz) {return this.snaf = baz;}// ...
}

上面代码中,_bar方法前面的下划线,表示这是一个只限于内部使用的私有方法。但是,这种命名是不保险的,在类的外部,还是可以调用到这个方法。

另一种方法就是索性将私有方法移出模块,因为模块内部的所有方法都是对外可见的。

class Widget {foo (baz) {bar.call(this, baz);}// ...
}function bar(baz) {return this.snaf = baz;
}

上面代码中,foo是公开方法,内部调用了bar.call(this, baz)。这使得bar实际上成为了当前模块的私有方法。

还有一种方法是利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值。

const bar = Symbol('bar');
const snaf = Symbol('snaf');export default class myClass{// 公有方法foo(baz) {this[bar](baz);}// 私有方法[bar](baz) {return this[snaf] = baz;}// ...
};

上面代码中,barsnaf都是Symbol值,一般情况下无法获取到它们,因此达到了私有方法和私有属性的效果。但是也不是绝对不行,Reflect.ownKeys()依然可以拿到它们。

const inst = new myClass();Reflect.ownKeys(myClass.prototype)
// [ 'constructor', 'foo', Symbol(bar) ]

上面代码中,Symbol 值的属性名依然可以从类的外部拿到。

私有属性的提案

class加了私有属性。方法是在属性名之前,使用#表示。

class IncreasingCounter {#count = 0;get value() {console.log('Getting the current value!');return this.#count;}increment() {this.#count++;}
}

上面代码中,#count就是私有属性,只能在类的内部使用(this.#count)。如果在类的外部使用,就会报错。

const counter = new IncreasingCounter();
counter.#count // 报错
counter.#count = 42 // 报错

上面代码在类的外部,读取私有属性,就会报错。

class Point {#x;constructor(x = 0) {this.#x = +x;}get x() {return this.#x;}set x(value) {this.#x = +value;}
}

上面代码中,#x就是私有属性,在Point类之外是读取不到这个属性的。由于井号#是属性名的一部分,使用时必须带有#一起使用,所以#xx是两个不同的属性。

​ 之所以要引入一个新的前缀#表示私有属性,而没有采用private关键字,是因为 JavaScript 是一门动态语言,没有类型声明,使用独立的符号似乎是唯一的比较方便可靠的方法,能够准确地区分一种属性是否为私有属性。另外,Ruby 语言使用@表示私有属性,ES6 没有用这个符号而使用#,是因为@已经被留给了 Decorator。

这种写法不仅可以写私有属性,还可以用来写私有方法。

class Foo {#a;#b;constructor(a, b) {this.#a = a;this.#b = b;}#sum() {return #a + #b;}printSum() {console.log(this.#sum());}
}

上面代码中,#sum()就是一个私有方法。

另外,私有属性也可以设置 getter 和 setter 方法。

class Counter {#xValue = 0;constructor() {super();// ...}get #x() { return #xValue; }set #x(value) {this.#xValue = value;}
}

上面代码中,#x是一个私有属性,它的读写都通过get #x()set #x()来完成。

私有属性不限于从this引用,只要是在类的内部,实例也可以引用私有属性。

class Foo {#privateValue = 42;static getPrivateValue(foo) {return foo.#privateValue;}
}Foo.getPrivateValue(new Foo()); // 42

上面代码允许从实例foo上面引用私有属性。

私有属性和私有方法前面,也可以加上static关键字,表示这是一个静态的私有属性或私有方法。

class FakeMath {static PI = 22 / 7;static #totallyRandomNumber = 4;static #computeRandomNumber() {return FakeMath.#totallyRandomNumber;}static random() {console.log('I heard you like random numbers…')return FakeMath.#computeRandomNumber();}
}FakeMath.PI // 3.142857142857143
FakeMath.random()
// I heard you like random numbers…
// 4
FakeMath.#totallyRandomNumber // 报错
FakeMath.#computeRandomNumber() // 报错

上面代码中,#totallyRandomNumber是私有属性,#computeRandomNumber()是私有方法,只能在FakeMath这个类的内部调用,外部调用就会报错。

new.target 属性

​ new是从构造函数生成实例对象的命令。ES6 为new命令引入了一个new.target属性,该属性一般用在构造函数之中,返回new命令作用于的那个构造函数。如果构造函数不是通过new命令或Reflect.construct()调用的,new.target会返回undefined`,因此这个属性可以用来确定构造函数是怎么调用的。

function Person(name) {if (new.target !== undefined) {this.name = name;} else {throw new Error('必须使用 new 命令生成实例');}
}// 另一种写法
function Person(name) {if (new.target === Person) {this.name = name;} else {throw new Error('必须使用 new 命令生成实例');}
}var person = new Person('张三'); // 正确
var notAPerson = Person.call(person, '张三');  // 报错

上面代码确保构造函数只能通过new命令调用。

Class 内部调用new.target,返回当前 Class。

class Rectangle {constructor(length, width) {console.log(new.target === Rectangle);this.length = length;this.width = width;}
}var obj = new Rectangle(3, 4); // 输出 true

需要注意的是,子类继承父类时,new.target会返回子类。

class Rectangle {constructor(length, width) {console.log(new.target === Rectangle);// ...}
}class Square extends Rectangle {constructor(length) {super(length, width);}
}var obj = new Square(3); // 输出 false

上面代码中,new.target会返回子类。

利用这个特点,可以写出不能独立使用、必须继承后才能使用的类。

class Shape {constructor() {if (new.target === Shape) {throw new Error('本类不能实例化');}}
}class Rectangle extends Shape {constructor(length, width) {super();// ...}
}var x = new Shape();  // 报错
var y = new Rectangle(3, 4);  // 正确

上面代码中,Shape类不能被实例化,只能用于继承。

注意,在函数外部,使用new.target会报错。

3.新增了一种基本数据类型(Symbol)

Symbol类型没有字面量表现形式,要想获得一个Symbol类型的值, 需要调用全局函数Symbol()

let firstSymbol = Symbol();

调用的时候,也可以给函数传一个字符串参数,用来描述这个symbol

let secondSymbol = Symbol('second symbol');

参数也仅仅起描述作用,只有显示或隐式调用toString() ,比如console.log( secondSymbol)时,

console.log(secondSymbol) //Symbol(second symbol)

它才会显示出来,有利于debugger,知道操作的是哪一个symbol,仅此而已,没有太大的实际意义。

Symbol类型的值,最大的特点是唯一性,调用Symbol() 函数返回的值是唯一的。我们可以无数次地调用Symbol() 函数,但每一次调用返回的值都不一样。

let firstSymbol = Symbol();
let secondSymbol = Symbol();
console.log(firstSymbol == secondSymbol) // false

带上参数(相同的参数)调用也是如此

let firstSymbol = Symbol('firstSymbol');
let secondSymbol = Symbol('firstSymbol');
console.log(firstSymbol == secondSymbol) // false

这种唯一性带来了一个好处,就是当我们想给一个对象添加新属性的时候,使用Symbol()函数返回的值作为属性名,完全不用担心会覆盖掉以前的属性,所以Symbol最常用的一个地方就是作为对象的属性名使用,避免了属性名的冲突。

let firstSymbol = Symbol();let person = {[firstSymbol]: 'symbolName'
}let dog = {};
dog[firstSymbol] = "sybolName"

获取对象上的symbol 属性,Object.getOwnPropertySymbols(), 返回一个包含所有symbol 属性的数组,对象以前的方法,比如Object.keys() 不起作用

let firstSymbol = Symbol("firstSymbol");
let object = {[firstSymbol]: "12345"
};
let symbols = Object.getOwnPropertySymbols(object);
console.log(symbols.length); // 1
console.log(symbols[0]); // "Symbol(firstSymbol)"
console.log(object[symbols[0]]); // "12345"

但是有时候,你不想要这种唯一性, 可能所有的对象都共用一个symbol 属性, 这怎么办? 在一个文件js中,很好处理,所有的对象都使用这一个symbol 变量就可以了,但跨文件就不好处理了,尤其是提倡模块化的今天,每一个文件都是一个模块,都有自己的私有作用域。在这种情况下,就要使用共享symbol 了,创建symbol 变量的时候,使用Symbol.for() 方法,参数是一个字符串, 可以理解为共享标识key,

let uid = Symbol.for('uid');

当我们使用Symbol.for() 创建symbol 的时候,js 引擎会到全局symbol 注册中心去查找这个symbol, 查找的依据是唯一的标识key(在这里是‘’uid‘’),如果找到了,就直接返回找到的这个symbol,如果没有找到,就创建一个新的symbol并返回,同时使用唯一的标识key把这个symbol注册到全局注册中心。这样,以后再使用这个key 创建 symbol, 就会获取到同一个symbol, 这就共享了。key值和symbol值在全局注册中心作了一个简单的映射

let uid1 = Symbol.for('uid');
let uid2 = Symbol.for('uid');console.log(uid1 === uid2); // true

Symbol.keyFor() 方法,就是获取一个symbol 在全局注册中心中注册的唯一标识key。

let uid1 = Symbol.for('uid');
let symbolKey = Symbol.keyFor(uid1);
console.log(symbolKey)  // 'uid'

再简单一点,可以把Symbol值看作一个类字符串,和字符串的使用方式一致,字符串能用的地方,symbol 基本都能用。需要注意一点,Symbol函数调用的时候,前面不要加new.

Symbol 实际的意义,还在于扩展了js语言,就是暴露js的一些内部方法(Exposing Internal Operations), 内部方法就是我们调用JS的API 时,内部是怎么实现的。为此, JS定义了一些有名的symbol (well-known symbols), 这些symbols 都是增加到Symbol 对象上。

Symbol.hasInstance

instanceOf 方法,它右侧只能是一个函数,实际上就是调用函数的Symbol.hasInstance 方法。obj instanceOf Array,就是调用Array 函数的Symbol.hasInstance 方法,ArraySymbol.hasInstance。Symbol.hasInstance 方法 接受一个参数就是我们要检查的对象,然后返回true or false, 来表示检查的对象是不是函数的实例, true 就表示是,false 表示不是。写一个函数,声明一个函数Person

function Person(name) {this.name = name;
}

给它定义一个Symbol.hasInstance方法,Person[Symbol.hasInstance], 它是一个函数,接受一个参数,然后返回true or false。 函数是对象,能添加方法

Person[Symbol.hasInstance] = function(value) {return false;
}

现在使用instanceOf 方法验证一下

console.log(new Person('sam') instanceof Person);

返回true, 但是我们的定义的方法返回的是false,为什么呢?这是因为每一个函数都有一个默认的Symbol.hasInstance, 它位于函数的原型链Function.prototype 上, 我们在Person 函数上定义Symbol.hasInstance方法是相当于遮蔽原型链上的方法,但是在原型链上定义的Symbol.hasInstance 方法,它是不可配置,不可改写,不可迭代的。正是由于不可改写,像Person[Symbol.hasInstance] 普通赋值的方式无法真正实现赋值,静默失败。那怎么才能遮蔽原型链上Symbol.hasInstance 方法, 使用Object.defineProperty 方式进行赋值

Object.defineProperty(Person, Symbol.hasInstance, {value: function(value) {return false;}
})

这时候 console.log 就返回false了。其实有了Symbol.hasInstance, instanceOf 的右侧可以是任何对象,只要它定义了Symbol.hasInstance方

let uint8 = {[Symbol.hasInstance](x) {return Number.isInteger(x) && x >= 0 && x <= 255;}
};
128 instanceof uint8 // => true
Symbol.isConcatSpreadable

看字面意思是,concat 的时候是否能够spreadable. 这个Symbol 来自于数组的concat 方法,当一个数组去concat 另外一个数组的时候,它会把另外一个数组的元素一个一个都取出来,添加到第一个数组的后面,

let arr1 = [1, 2];
let arr2 = arr1.concat([3, 4]);
console.log(arr2) // [1, 2, 3, 4]

但是当一个数组去concat 一个字符串的时候,它直接把字符串作为一个整体放到了数组的后面

let arr1 = [1, 2];
let arr2 = arr1.concat("string");
console.log(arr2) // [1, 2, 'string']

有没有看到区别? 数组的话,concat 方法对数组进行了分割(spread), 而对于字符串,则没有, 为什么呢? js 就是这么规定的,数组能自动分割成一个一个元素,而其他类型不能。这也是Symbol.isConcatSpreadable 出现的原因, 它作为对象的属性进行使用,如果值是true 就表示,如果被concat 的话,这个对象可以像数组一样,被分割, false 则表示不能进行分割了。当然这个对象也有一定的要求,它有一个length 属性,且属性是数值类型, 也就是我们所说的类数组 对象。 被分割,就是表示这个对象的属性值可以一个一个取出来。

let arr1 = [1, 2];
let obj = {0: 3,1: 4,length: 2,[Symbol.isConcatSpreadable]: true
}
console.log(arr1.concat(obj)) // [1, 2, 3, 4]
Symbol.match, Symbol.search, Symbol.replace, Sybmol.split

字符串有四个方法,match, search, replace, split, 可以接受正则表达式,然后对匹配的字符进行操作,现在我们可以使用对象对正则表达式进行模拟,也就是说,字符串的这四个方法在调用时,可以接受一个对象。怎么用对象来模拟正则表达式呢,就是Symol 属性了。很显然,这四个属性必须是一个函数,要不然,他们没有办法对字符串进行操作,函数呢肯定有一个参数,就是调用方法的字符串,函数中只有获取到字符串,才能对这个字符串进行修改。

Symbol.match 属性就是模拟的 match 方法, 接受一个 参数,匹配成功返回匹配的数组,匹配失败返回null

Symbol.search属性模拟的就是search, 也是接受一个参数,查找到就返回索引,找不到,就返回-1

Symbol.replace 属性要接受两个参数,一个是操作的字符串,一个是替换的字符串, 返回替换后字符串

Symbol.split 属性接受一个参数, 返回一个分割后的数组

let obj = {[Symbol.match]: function(value) {console.log(value);return value.length === 10 ? [value.substring(0, 10)]: null;},[Symbol.replace]: function(value, replacement) {return value.length === 10 ? replacement + value.substring(2) : value;},[Symbol.search]: function(value) {return value.length === 10 ?  0 : -1},[Symbol.split]: function(value) {return value.length === 10 ? ["", ""] : [vlue]}
}

可以声明字符串来调用match, search, split 和replace 方法,它们接受的参数就是obj 对象,调用mactch 方法时,就是调用的obj 对象上的Symbol.match 方法, 相对应的, search 方法就是调用Symbol.search 方法,split, replace 对应地就是Symbol.split 和Symbol.replace

let message1 = "Hello world"; // 11 个字符
let message2 = "Hello John"; // 10 个字符
message1.match(obj); // null
message2.match(obj); ["hello json"]message1.replace(obj, 2) // replace 接受两个参数,第二个是replacment 

其实看一下obj 对象,它实际上就是模拟的正则表达式/^.{10}$/。 string.method(pattern, arg) 就变成了pattern[symbol](string, arg)

Symbol.toPrimitive

toPrimitive 就是转化成原始类型。在Js 中,引用类型object 是可以转化成原始类型的, 不是调用valueOf(), 就是调用toString() 方法,但具体返回什么值,我们就无法控制了,是js 内部帮我们做的。现在好了,有了Symbol.toPrimitive, 我们就可以规定当引用类型转化为基本类型的时候,它返回的是什么数据。Symbol.toPrimitive 它是一个函数,定义在对象的原型上,其次它要接受一个参数,hint, 有三个取值,“number“, “string“, “default“, “number” 就表示要转化成数字,我们就要返回数字,“string“ 表示转化成字符串,就要返回字符串,“default” 有点奇怪,返回字符串或数字都可以。不过,这里要注意是hint 不是我们自己传递过去的,而是js 内部根据某些操作符来判断是哪个hint , 传递到 Symbol.toPrimitive 函数中的。比如除法,肯定是数字才能相除,js 内部把hint 置为了"number", 你可能很好奇,什么时候触发hint 为"default" 呢? 有三种情况: ==操作符, +操作符, 给Date() 函数一个传参

Symbol.toPrimitive 定义到对象原型上的,所以我们要使用函数的构造方式调用的方法或使用ES6 中的类创建对象

function Temperature(degrees) {this.degrees = degrees;
}
Temperature.prototype[Symbol.toPrimitive] = function (hint) {switch (hint) {case "string":return this.degrees + "\u00b0";case "number":return this.degrees;case "default":return this.degrees + " degrees";}
}var freezing = new Temperature(32);
console.log(freezing + "!"); // + 操作符调用的是"default", "32 degrees!"
console.log(freezing / 2); // 16
console.log(String(frezing)); // "32°"
Symbol.toStringTag

当我们判断一个值是什么类型的时候,可能用到过Object.prototype.toString().call()

 Object.prototype.toString.call([]) // => "[object Array]"

但当使用这个方法来判断自定义对象的时候,就不用那么好用了

function Person() {this.name = "sam";
}console.log(Object.prototype.toString(new Person())); //'[object Object]'

返回’[object Object]’ , 可不可以返回Person 类型,这就是Sybmol.toStirngTag 的来源。在ES6中,Object.prototype.toString()会查找调用对象上的Sybmol.toStirngTag 方法,如果有,就使用这个方法的返回值

function Person() {this.name = "sam";
}Person.prototype[Symbol.toStringTag] = "Person";
console.log(Object.prototype.toString(new Person())); '[object Person]'

可以看到返回了’’[object Person]’, 但是现在你再调用一下toString() 方法, 它也是返回’[object Person] '有点不太好,不过我们可以重新定义toString() 方法

function Person() {this.name = "sam";
}Person.prototype[Symbol.toStringTag] = "Person";
Person.prototype.toString = function() {return this.name;
}
console.log(Object.prototype.toString(new Person())); '[object Person]'
console.log(new Person().toString) // sam
Symbol.species

ES6 提供了class和extends,可以很轻松地创建一个内置类型的子类,比如数组

class EZArray extends Array {get first() { return this[0]; }get last() { return this[this.length - 1]; }
}
let e = new EZArray(1, 2, 3);
let f = e.map(x => x * x);
e.last // => 3
f.last// => 9

数组的map(), concat() 等方法,也会返回一个数组,这就引发了一个问题,这个数组到底是子类型EZArray ,还是父类型Array?ES6规定返回的是子类型。它是怎么决定的呢?Array() 构造函数有一个Symbol.species方法,子类extends Array() 的时候,子类的构造函数也会继承这个方法,当调用map方法返回数组的时候,也会调用这个方法。Symbol.species 方法呢,是只读的,单纯地返回this. 所以e.map 的时候,调用的是子类身上的Symbol.species, 返回的是子类。如果不想使用默认方式,想让map 方法返回的是父类,就要定义Symbol.species 方法

class EZArray extends Array {
static get [Symbol.species]() { return Array; }
get first() { return this[0]; }
get last() { return this[this.length-1]; }
}
let e = new EZArray(1,2,3);
let f = e.map(x => x - 1);
e.last // => 3
f.last // => undefined

4.增了变量的解构赋值

​ 解构赋值是对赋值运算符的扩展。

​ 是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值。

​ 在代码书写上简洁且易读,语义更加清晰明了;也方便了复杂对象中数据字段获取。

解构模型

在解构中,有下面两部分参与:

​ 解构的源,解构赋值表达式的右边部分。

​ 解构的目标,解构赋值表达式的左边部分。

解构赋值主要分为对象的解构和数组的解构,在没有解构赋值的时候,我们赋值是这样的:

let arr = [0,1,2]
let a = arr[0]
let b = arr[1]
let c = arr[2]

解构赋值,简单理解就是等号的左边和右边相等。

数组的解构赋值
let arr = [0,1,2]
let [a,b,c] = arr
console.log(a) // 0
console.log(b) // 1
console.log(c) // 2

很多时候,数据并非一一对应的,并且我们希望得到一个默认值

let arr = [,1,2]
let [a='我是默认值',b,c] = arr
console.log(a) // '我是默认值'
console.log(b) // 1
console.log(c) // 2

从这个例子可以看出,在解构赋值的过程中,a=undefined时,会使用默认值
那么当a=null时呢?当a=null时,那么a就不会使用默认值,而是使用null

// 数组的拼接
let a = [0,1,2]
let b = [3,4,5]
let c = a.concat(b)
console.log(c) // [0,1,2,3,4,5]let d = [...a,...b]
console.log(d) // [0,1,2,3,4,5]
// 数组的克隆
// 假如我们简单地把一个数组赋值给另外一个变量
let a = [0,1,2,3]
let b = a
b.push(4)
console.log(a) // [0,1,2,3,4]
console.log(b) // [0,1,2,3,4]

这只是简单的把引用地址赋值给b,而不是重新开辟一个内存地址,所以
a和b共享了同一个内存地址,该内存地址的更改,会影响到所有引用该地址的变量
那么用下面的方法,把数组进行克隆一份,互不影响:

let a = [0,1,2,3]
let b = [...a]
b.push(4)
console.log(a) // [0,1,2,3]
console.log(b) // [0,1,2,3,4]
对象的解构赋值

对象的解构赋值和数组的解构赋值其实类似,但是数组的数组成员是有序的
而对象的属性则是无序的,所以对象的解构赋值简单理解是等号的左边和右边的结构相同

let {name,age} = {name:"swr",age:28}
console.log(name) // 'swr'
console.log(age) // 28

对象的解构赋值是根据key值进行匹配

// 这里可以看出,左侧的name和右侧的name,是互相匹配的key值
// 而左侧的name匹配完成后,再赋值给真正需要赋值的Name
let { name:Name,age } = { name:'swr',age:28 }
console.log(Name) // 'swr'
console.log(age) // 28

当变量已经被声明的情况:

let name,age
// 需要用圆括号,包裹起来
({name,age} = {name:"swr",age:28})
console.log(name) // 'swr'
console.log(age) // 28

变量能否也设置默认值?

let {name="swr",age} = {age:28}
console.log(name) // 'swr'
console.log(age) // 28
// 这里规则和数组的解构赋值一样,当name = undefined时,则会使用默认值
let [a] = [{name:"swr",age:28}]
console.log(a) // {name:"swr",age:28}let { length } = "hello swr"
console.log(length) // 9
function ajax({method,url,type='params'}){console.log(method) // 'get'console.log(url) // '/'console.log(type) // 'params'
}ajax({method:"get",url:"/"})

5.函数参数允许设置默认值,引入了rest参数,新增了箭头函数。

函数可以设置参数默认值

箭头函数是es6的一种函数的简写方法。

1.箭头函数基本形式
var f = v = > v;
//等同于
var f = function(v){return v;
}
var sum = (num1,num2) => num1+num2 ;
//等同于
var sum = function(num1,num2){return num1+num2
}
[1,2,3].map(function (x) {return x * x;
});
// 箭头函数写法
[1,2,3].map(x => x * x);//简洁了许多

从例子我们可以看出,省略了function,花括号‘{}’用‘=>’代替了。这种写法更简洁了。

2. 箭头函数基本特点
(1). 箭头函数this为父作用域的this,不是调用时的this

​ 箭头函数的this永远指向其父作用域,任何方法都改变不了,包括call,apply,bind。
普通函数的this指向调用它的那个对象。

​ 除了简洁之外,箭头函数体内的this的指向始终是指向定义它所在的对象,而不会指向调用它的对象,我们知道es5中的函数是谁执行它,它就指向谁。

var countdown ={'count':10,'str':'hello!!!',showstr(){var _this = this;var dom = document.getElementById('dom');dom.innerHTML= _this.todouble(this.count);setInterval(function(){var dom=document.getElementById('dom');dom.innerHTML=_this.todouble(_this.count);_this.count --;if(_this.count <0){dom.innerHTML=_this.str;}},1000) },todouble(t){var t = parseInt(t);if(t<10){return '0'+t;}else{return t;}}}countdown.showstr();

如上是一个倒计时完之后显示一个hello文本的效果,在setInterval里面,如果我们直接写this的话,这个this是指向window的。因此我们需要在setInterval函数之前先保存_this = this;

当我们使用es6的箭头函数的时候,就可以直接使用this了

//es6的写法。var countdown ={'count':10,'str':'hello!!!',showstr(){var dom = document.getElementById('dom');dom.innerHTML= this.todouble(this.count);setInterval(() => {dom.innerHTML= this.todouble(this.count);;this.count --;if(this.count <0){dom.innerHTML=this.str;return false;}},1000) },todouble(t){var t = parseInt(t);if(t<10){return '0'+t;}else{return t;}}}countdown.showstr();

上面同样的代码改成箭头函数之后我们在setInterval里面就可以直接使用this了。

箭头函数里面的this装换成es5后的代码如下:

// ES6
function fn() {setTimeout(() => {console.log('id:', this.id);}, 100);
}
// ES5
function fn() {var _this = this;setTimeout(function () {console.log('id:', _this.id);}, 100);
}

箭头函数里面根本没有自己的this,而是引用外层的this,由于箭头函数没有自己的this,所以也就不能用call()apply()bind()这些方法去改变this的指向。

箭头函数使用的注意的地方:

(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

let person = {name:'jike',init:function(){//为body添加一个点击事件,看看这个点击后的this属性有什么不同document.body.onclick = ()=>{alert(this.name);//?? this在浏览器默认是调用时的对象,可变的?                  }}
}
person.init();

​ init是function,以person.init调用,其内部this就是person本身,而onclick回调是箭头函数,
其内部的this,就是父作用域的this,就是person,能得到name。

let person = {name:'jike',init:()=>{//为body添加一个点击事件,看看这个点击后的this属性有什么不同document.body.onclick = ()=>{alert(this.name);//?? this在浏览器默认是调用时的对象,可变的?                  }}
}
person.init();

​ init为箭头函数,其内部的this为全局window,onclick的this也就是init函数的this,也是window,
得到的this.name就为undefined。

(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。因为箭头函数的this是由定义它的对象决定的,对象的构造函数是顶层的,它的外层,没有this可以传进去给箭头函数使用。

//构造函数如下:
function Person(p){this.name = p.name;
}
//如果用箭头函数作为构造函数,则如下
var Person = (p) => {this.name = p.name;
}

由于this必须是对象实例,而箭头函数是没有实例的,此处的this指向别处,不能产生person实例,自相矛盾。

(3)不可以使用arguments,caller,calle,该对象在函数体内不存在。

​ 箭头函数本身没有arguments,如果箭头函数在一个function内部,它会将外部函数的arguments拿过来使用。
箭头函数中要想接收不定参数,应该使用rest参数…解决。

let B = (b)=>{console.log(arguments);
}
B(2,92,32,32);   // Uncaught ReferenceError: arguments is not definedlet C = (...c) => {console.log(c);
}
C(3,82,32,11323);  // [3, 82, 32, 11323]

(4). 箭头函数通过call和apply调用,不会改变this指向,只会传入参数

let obj2 = {a: 10,b: function(n) {let f = (n) => n + this.a;return f(n);},c: function(n) {let f = (n) => n + this.a;let m = {a: 20};return f.call(m,n);}
};
console.log(obj2.b(1));  // 11
console.log(obj2.c(1)); // 11

(5). 箭头函数没有原型属性

var a = ()=>{return 1;
}function b(){return 2;
}console.log(a.prototype);  // undefined
console.log(b.prototype);   // {constructor: ƒ}

(6). 箭头函数不能作为Generator函数,不能使用yield关键字

(7). 箭头函数返回对象时,要加一个小括号

var func = () => ({ foo: 1 }); //正确
var func = () => { foo: 1 };   //错误

(8). 箭头函数在ES6 class中声明的方法为实例方法,不是原型方法

//deom1
class Super{sayName(){//do some thing here}
}
//通过Super.prototype可以访问到sayName方法,这种形式定义的方法,都是定义在prototype上
var a = new Super()
var b = new Super()
a.sayName === b.sayName //true
//所有实例化之后的对象共享prototypy上的sayName方法//demo2
class Super{sayName =()=>{//do some thing here}
}
//通过Super.prototype访问不到sayName方法,该方法没有定义在prototype上
var a = new Super()
var b = new Super()
a.sayName === b.sayName //false
//实例化之后的对象各自拥有自己的sayName方法,比demo1需要更多的内存空间

注:

​ 在class中尽量少用箭头函数声明方法。

(9). 多重箭头函数就是一个高阶函数,相当于内嵌函数

const add = x => y => y + x;
//相当于
function add(x){return function(y){return y + x;};
}

(10). 箭头函数常见错误

let a = {foo: 1,bar: () => console.log(this.foo)
}a.bar()  //undefined

bar函数中的this指向父作用域,而a对象没有作用域,因此this不是a,打印结果为undefined

function A() {this.foo = 1
}A.prototype.bar = () => console.log(this.foo)let a = new A()
a.bar()  //undefined

原型上使用箭头函数,this指向是其父作用域,并不是对象a,因此得不到预期结果

6.数组新增了一些API,如isArray / from / of 方法;数组实例新增了 entries(),keys() 和 values() 等方法。

1、Array.from()方法

Array.from()方法是用于类似数组的对象(即有length属性的对象)和可遍历对象转为真正的数组。
比如,使用Array.from()方法,可以轻松将JSON数组格式转为数组。

let json ={'0':'hello','1':'123','2':'panda',length:3
}
let arr = Array.from(json);
console.log(arr);12345678

控制台打印结果:["hello", "123", "panda"]

2、使用Array.of()方法

Array.of()方法是将一组值转变为数组。

let arr0 = Array.of(1,2,33,5);
console.log(arr0);//[1,2,33,5]let arr1 = Array.of('你好','hello');
console.log(arr1);//["你好", "hello"]
123456
3、find()方法

数组实例的find方法用于找出第一个符合条件的数组成员。参数是个回调函数,所有数组成员依次执行该回调函数,直到找到第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,就返回undefined;
如:回调函数可以接收3个参数,依次为当前的值(value)、当前的位置(index)、原数组(arr)

let arr2 = [1,2,3,5,7];
console.log(arr2.find(function(value,index,arr2){return value > 5;
}))1234

控制台打印结果:7

4、fill()方法

使用fill()方法给定值填充数组。
fill方法用于空数组的初始化很方便:new Array(3).fill(7);//[7,7,7]
fill方法还可以接收第二个和第三个参数,用于指定填充的起始位置和结束位置:

let arr3 = [0,1,2,3,4,5,6,7];
arr3.fill('error',2,3);
console.log(arr3);//[0,1,"error",3,4,5,6,7]123
5、遍历数组的方法:entries()、values()、keys()

这三个方法都是返回一个遍历器对象,可用for...of循环遍历,唯一区别:keys()是对键名的遍历、values()对键值的遍历、entries()是对键值对的遍历。

for(let item of ['a','b'].keys()){consloe.log(item);//0//1
}
for(let item of ['a','b'].values()){consloe.log(item);//'a'//'b'
}
let arr4 = [0,1];
for(let item of arr4.entries()){console.log(item);  //  [0, 0]//  [1, 1]
}
for(let [index,item] of arr4.entries()){console.log(index+':'+item);//0:0//1:1
}123456789101112131415161718192021

如果不用for...of进行遍历,可用使用next()方法手动跳到下一个值。

let arr5 =['a','b','c']
let entries = arr5.entries();
console.log(entries.next().value);//[0, "a"]
console.log(entries.next().value);//[1, "b"]
console.log(entries.next().value);//[2, "c"]
console.log(entries.next().value);//undefined

7.对象和数组新增了扩展运算符

​ 扩展运算符(spread)是三个点(…)。它好比 reset 参数的逆运算,将一个数组转为用逗号分割的参数列表。

console.log(...[1,2,3]);  // 1 2 3
console.log(1, ...[2, 3, 4], 5);  // 1 2 3 4 5
console.log([...document.querySelectorAll('div')]);  // [div, div]

运算符主要用于函数调用。

function push (arr, items) {arr.push(...items);console.log(arr)
}
push([1,2,3], [4, 5, 6]);
function add (x, y) {return x + y;
}
const numbers = [4, 38];
add(...numbers);  // 42

注:

上面代码中,array.push(…items)和add(…numbers)这两行,都是函数的调用,它们的都使用了扩展运算符。该运算符将一个数组,变为参数序列。

对比下扩展运算符的方便之处:

// 以往我们是这样拼接数组的
let arr1 = [1,2,3]
let arr2 = [4,5,6]
let arr3 = arr1.concat(arr2)
console.log(arr3) // [ 1, 2, 3, 4, 5, 6 ]// 现在我们用扩展运算符看看
let arr1 = [1,2,3]
let arr2 = [4,5,6]
let arr3 = [...arr1,...arr2]
console.log(arr3) // [ 1, 2, 3, 4, 5, 6 ]
// 以往我们这样来取数组中最大的值
function max(...args){return Math.max.apply(null,args)
}
console.log(max(1,2,3,4,5,6)) // 6// 现在我们用扩展运算符看看
function max(...args){return Math.max(...args) // 把args [1,2,3,4,5,6]展开为1,2,3,4,5,6
}
console.log(max(1,2,3,4,5,6)) // 6
// 扩展运算符可以把argument转为数组
function max(){console.log(arguments) // { '0': 1, '1': 2, '2': 3, '3': 4, '4': 5, '5': 6 }let arr = [...arguments]console.log(arr) // [1,2,3,4,5,6]
}max(1,2,3,4,5,6)// 但是扩展运算符不能把伪数组转为数组(除了有迭代器iterator的伪数组,如arguments)
let likeArr = { "0":1,"1":2,"length":2 }
let arr = [...likeArr] // 报错 TypeError: likeArr is not iterable// 但是可以用Array.from把伪数组转为数组
let likeArr = { "0":1,"1":2,"length":2 }
let arr = Array.from(likeArr)
console.log(arr) // [1,2]
对象的扩展运算符:

​ 对象的扩展运算符总结一句话:

​ 对象中的扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中

let bar = { a: 1, b: 2 }; let baz = { ...bar }; *// { a: 1, b: 2 }

上述方法实际上等价于:

let bar = { a: 1, b: 2 }; let baz = Object.assign({}, bar); *// { a: 1, b: 2 }

注:

​ Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

​ Object.assign方法的第一个参数是目标对象,后面的参数都是源对象。(如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性)。

对于用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉:

let bar = {a: 1, b: 2}; let baz = {...bar, ...{a:2, b: 4}};  *// {a: 2, b: 4}

注:Object.assign的拷贝属于一种浅拷贝

​ 这里有点需要注意的是扩展运算符对对象实例的拷贝属于一种浅拷贝。肯定有人要问什么是浅拷贝?我们知道javascript中有两种数据类型,分别是基础数据类型和引用数据类型。基础数据类型是按值访问的,常见的基础数据类型有Number、String、Boolean、Null、Undefined,这类变量的拷贝的时候会完整的复制一份;引用数据类型比如Array,在拷贝的时候拷贝的是对象的引用,当原对象发生变化的时候,拷贝对象也跟着变化,比如:

let obj1 = { a: 1, b: 2, c: {nickName: 'd'}};
let obj2 = { ...obj1};
obj2.c.nickName = 'd-edited';
console.log(obj1); // {a: 1, b: 2, c: {nickName: 'd-edited'}}
console.log(obj2); // {a: 1, b: 2, c: {nickName: 'd-edited'}}

​ 对obj2的修改影响到了被拷贝对象obj1,因为obj1中的对象c是一个引用数据类型,拷贝的时候拷贝的是对象的引用。

我们在开发当中,经常需要对对象进行深拷贝

1)利用JSON.stringify和JSON.parse

let swr = {name:"邵威儒",age:28,pets:['小黄']
}let swrcopy = JSON.parse(JSON.stringify(swr))
console.log(swrcopy) // { name: '邵威儒', age: 28, pets: [ '小黄' ] }
// 此时我们新增swr的属性
swr.pets.push('旺财')
console.log(swr) // { name: '邵威儒', age: 28, pets: [ '小黄', '旺财' ] }
// 但是swrcopy却不会受swr影响
console.log(swrcopy) // { name: '邵威儒', age: 28, pets: [ '小黄' ] }

这种方式进行深拷贝,只针对json数据这样的键值对有效
对于函数等等反而无效,不好用,接着继续看方法二、三。

2)

function deepCopy(fromObj,toObj) { // 深拷贝函数// 容错if(fromObj === null) return null // 当fromObj为nullif(fromObj instanceof RegExp) return new RegExp(fromObj) // 当fromObj为正则if(fromObj instanceof Date) return new Date(fromObj) // 当fromObj为DatetoObj = toObj || {}for(let key in fromObj){ // 遍历if(typeof fromObj[key] !== 'object'){ // 是否为对象toObj[key] = fromObj[key] // 如果为普通值,则直接赋值}else{if(fromObj[key] === null){toObj[key] = null}else{toObj[key] = new fromObj[key].constructor // 如果为object,则new这个object指向的构造函数deepCopy(fromObj[key],toObj[key]) // 递归          }}}return toObj
}let dog = {name:"小白",sex:"公",firends:[{name:"小黄",sex:"母"}]
}let dogcopy = deepCopy(dog)
// 此时我们把dog的属性进行增加
dog.firends.push({name:"小红",sex:"母"})
console.log(dog) // { name: '小白',sex: '公',firends: [ { name: '小黄', sex: '母' }, { name: '小红', sex: '母' } ] }
// 当我们打印dogcopy,会发现dogcopy不会受dog的影响
console.log(dogcopy) // { name: '小白',sex: '公',firends: [ { name: '小黄', sex: '母' } ] }

3)

let dog = {name:"小白",sex:"公",firends:[{name:"小黄",sex:"母"}]
}function deepCopy(obj) {if(obj === null) return nullif(typeof obj !== 'object') return objif(obj instanceof RegExp) return new RegExp(obj)if(obj instanceof Date) return new Date(obj)let newObj = new obj.constructorfor(let key in obj){newObj[key] = deepCopy(obj[key])}return newObj
}let dogcopy = deepCopy(dog)
dog.firends.push({name:"小红",sex:"母"})
console.log(dogcopy)
数组的扩展运算符

1)可以将数组转换为参数序列

function add(x, y) { return x + y; 
} 
const numbers = [4, 38]; 
add(...numbers) *// 42*

2)可以复制数组

如果直接通过下列的方式进行数组复制是不可取的:

const arr1 = [1, 2];
const arr2 = arr1;
arr2[0] = 2;
arr1 // [2, 2]

原因上面已经介绍过,用扩展运算符就很方便:

const arr1 = [1, 2]; 
const arr2 = [...arr1];

参数对象是个数组,数组里面的所有对象都是基础数据类型,将所有基础数据类型重新拷贝到新的数组中。

扩展运算符可以与解构赋值结合起来,用于生成数组
const [first, ...rest] = [1, 2, 3, 4, 5]; first *// 1* rest  *// [2, 3, 4, 5]*

注意:

​ 如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。

const [...rest, last] = [1, 2, 3, 4, 5]; *// 报错* 
const [first, ...rest, last] = [1, 2, 3, 4, 5]; *// 报错*
扩展运算符还可以将字符串转为真正的数组
[...'hello'] *// [ "h", "e", "l", "l", "o" ]*
任何 Iterator 接口的对象(参阅 Iterator 一章),都可以用扩展运算符转为真正的数组

​ 具体可以参考阮一峰老师的ECMAScript 6入门教程。

​ 比较常见的应用是可以将某些数据结构转为数组:

*// arguments对象* function foo() {  const args = [...arguments]; 
}

注:用于替换es5中的Array.prototype.slice.call(arguments)写法。

介绍的不全,都是些最常见的用法。

8.ES6新增了模块化(import / export)

​ 在之前的javascript中是没有模块化概念的。如果要进行模块化操作,需要引入第三方的类库。随着技术的发展,前后端分离,前端的业务变的越来越复杂化。直至ES6带来了模块化,才让javascript第一次支持了module。ES6的模块化分为导出(export)与导入(import)两个模块。

export的用法

​ 在ES6中每一个模块即是一个文件,在文件中定义的变量,函数,对象在外部是无法获取的。如果你希望外部可以读取模块当中的内容,就必须使用export来对其进行暴露(输出)。先来看个例子,来对一个变量进行模块化。我们先来创建一个test.js文件,来对这一个变量进行输出:

export let myName="laowang";

然后可以创建一个index.js文件,以import的形式将这个变量进行引入:

import {myName} from "./test.js";
console.log(myName);//laowang

如果要输出多个变量可以将这些变量包装成对象进行模块化输出:

let myName="laowang";
let myAge=90;
let myfn=function(){return "我是"+myName+"!今年"+myAge+"岁了"
}
export {myName,myAge,myfn
}
/******************************接收的代码调整为**********************/
import {myfn,myAge,myName} from "./test.js";
console.log(myfn());//我是laowang!今年90岁了
console.log(myAge);//90
console.log(myName);//laowang

如果你不想暴露模块当中的变量名字,可以通过as来进行操作:

let myName="laowang";
let myAge=90;
let myfn=function(){return "我是"+myName+"!今年"+myAge+"岁了"
}
export {myName as name,myAge as age,myfn as fn
}
/******************************接收的代码调整为**********************/
import {fn,age,name} from "./test.js";
console.log(fn());//我是laowang!今年90岁了
console.log(age);//90
console.log(name);//laowang

也可以直接导入整个模块,将上面的接收代码修改为:

import * as info from "./test.js";//通过*来批量接收,as 来指定接收的名字
console.log(info.fn());//我是laowang!今年90岁了
console.log(info.age);//90
console.log(info.name);//laowang
默认导出(default export)

一个模块只能有一个默认导出,对于默认导出,导入的名称可以和导出的名称不一致。

/******************************导出**********************/
export default function(){return "默认导出一个方法"
}
/******************************引入**********************/
import myFn from "./test.js";//注意这里默认导出不需要用{}。
console.log(myFn());//默认导出一个方法

可以将所有需要导出的变量放入一个对象中,然后通过default export进行导出

/******************************导出**********************/
export default {myFn(){return "默认导出一个方法"},myName:"laowang"
}
/******************************引入**********************/
import myObj from "./test.js";
console.log(myObj.myFn(),myObj.myName);//默认导出一个方法 laowang

同样也支持混合导出

/******************************导出**********************/
export default function(){return "默认导出一个方法"
}
export var myName="laowang";
/******************************引入**********************/
import myFn,{myName} from "./test.js";
console.log(myFn(),myName);//默认导出一个方法 laowang
重命名export和import

如果导入的多个文件中,变量名字相同,即会产生命名冲突的问题,为了解决该问题,ES6为提供了重命名的方法,当你在导入名称时可以这样做:

/******************************test1.js**********************/
export let myName="我来自test1.js";
/******************************test2.js**********************/
export let myName="我来自test2.js";
/******************************index.js**********************/
import {myName as name1} from "./test1.js";
import {myName as name2} from "./test2.js";
console.log(name1);//我来自test1.js
console.log(name2);//我来自test1.js

作者:张培跃
链接:https://www.jianshu.com/p/9e5f39e4792b
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

9.ES6新增了Set和Map数据结构。

特性

似于数组,但它的一大特性就是所有元素都是唯一的,没有重复。

我们可以利用这一唯一特性进行数组的去重工作。

1.单一数组的去重
let set6 = new Set([1, 2, 2, 3, 4, 3, 5])
console.log('distinct 1:', set6)

结果:

distinct 1: Set { 1, 2, 3, 4, 5 }
2.多数组的合并去重
let arr1 = [1, 2, 3, 4]
let arr2 = [2, 3, 4, 5, 6]
let set7 = new Set([...arr1, ...arr2])
console.log('distinct 2:', set7)

结果:

distinct 2: Set { 1, 2, 3, 4, 5, 6 }
操作
1.add
let set1 = new Set()
set1.add(1)
set1.add(2)
set1.add(3)
console.log('added:', set1)

结果:

added: Set { 1, 2, 3 }
2.delete
let set1 = new Set()
set1.add(1)
set1.add(2)
set1.add(3)
set1.delete(1)
console.log('deleted:', set1)

结果:

deleted: Set { 2, 3 }
3.has
let set1 = new Set()
set1.add(1)
set1.add(2)
set1.add(3)
set1.delete(1)
console.log('has(1):', set1.has(1))
console.log('has(2):', set1.has(2))

结果:

has(1): false
has(2): true
4.clear
let set1 = new Set()
set1.add(1)
set1.add(2)
set1.add(3)
set1.clear()
console.log('cleared:', set1)

结果:

cleared: Set {}
5.size属性
console.log("容器大小:",set.size); //4
6.Array 转 Set
let set2 = new Set([4,5,6])
console.log('array to set 1:', set2)let set3 = new Set(new Array(7, 8, 9))
console.log('array to set 2:', set3)

结果:

array to set 2: Set { 4, 5, 6 }
array to set 3: Set { 7, 8, 9 }
7.Set 转 Array
let set4 = new Set([4, 5, 6])
console.log('set to array 1:', [...set4])
console.log('set to array 2:', Array.from(set4))

结果:

set to array 1: [ 4, 5, 6 ]
set to array 2: [ 4, 5, 6 ]
8.遍历(keys(),values(),entries())

可以使用Set实例对象的keys(),values(),entries()方法进行遍历。

由于Set的键名和键值是同一个值,它的每一个元素的key和value是相同的,所有keys()和values()的返回值是相同的,entries()返回的元素中的key和value是相同的。

let set5 = new Set([4, 5, 'hello'])
console.log('iterate useing Set.keys()')
for(let item of set5.keys()) {console.log(item)
}console.log('iterate useing Set.values()')
for(let item of set5.values()) {console.log(item)
}console.log('iterate useing Set.entries()')
for(let item of set5.entries()) {console.log(item)
}

结果:

iterate useing Set.keys()
4
5
hello
iterate useing Set.values()
4
5
hello
iterate useing Set.entries()
[ 4, 4 ]
[ 5, 5 ]
[ 'hello', 'hello' ]

:

​ 在向Set加入值时,Set不会转换数据类型,内部在判断元素是否存在时用的类似于精确等于(===)的方法,“2”和2是不同的。

Map

​ Javascript的Object本身就是键值对的数据结构,但实际上属性和值构成的是”字符串-值“对,属性只能是字符串,如果传个对象字面量作为属性名,那么会默认把对象转换成字符串,结果这个属性名就变成”[object Object]“。

ES6提供了”值-值“对的数据结构,键名不仅可以是字符串,也可以是对象。它是一个更完善的Hash结构。

特性

1.键值对,键可以是对象。

const map1 = new Map()
const objkey = {p1: 'v1'}map1.set(objkey, 'hello')
console.log(map1.get(objkey))

结果:

hello

2.Map可以接受数组作为参数,数组成员还是一个数组,其中有两个元素,一个表示键一个表示值。

const map2 = new Map([['name', 'Aissen'],['age', 12]
])
console.log(map2.get('name'))
console.log(map2.get('age'))

结果:

Aissen
12
操作
1.size

获取map的大小。

const map3 = new Map();
map3.set('k1', 1);
map3.set('k2', 2);
map3.set('k3', 3);
console.log('%s', map3.size)

结果:

3
2.set

设置键值对,键可以是各种类型,包括undefined,function。

const map4 = new Map();
map4.set('k1', 6)        // 键是字符串
map4.set(222, '哈哈哈')     // 键是数值
map4.set(undefined, 'gagaga')    // 键是 undefinedconst fun = function() {console.log('hello');}
map4.set(fun, 'fun') // 键是 functionconsole.log('map4 size: %s', map4.size)
console.log('undefined value: %s', map4.get(undefined))
console.log('fun value: %s', map4.get(fun))

结果:

map4 size: 4
undefined value: gagaga
fun value: fun

也可对set进行链式调用。

map4.set('k2', 2).set('k3', 4).set('k4', 5)
console.log('map4 size: %s', map4.size)

结果:

map4 size: 7
3.get

获取键对应的值。

const map5 = new Map();
map5.set('k1', 6)  
console.log('map5 value: %s', map5.get('k1'))

结果:map5 value: 6

4.has

判断指定的键是否存在。

const map6 = new Map();
map6.set(undefined, 4)
console.log('map6 undefined: %s', map6.has(undefined))
console.log('map6 k1: %s', map6.has('k1'))

结果:

map6 undefined: true
map6 k1: false
5.delete

删除键值对。

const map7 = new Map();
map7.set(undefined, 4)
map7.delete(undefined)
console.log('map7 undefined: %s', map7.has(undefined))

结果:

map7 undefined: false
6.clear

删除map中的所有键值对。

const map8 = new Map();
map8.set('k1', 1);
map8.set('k2', 2);
map8.set('k3', 3);
console.log('map8, pre-clear size: %s', map8.size)
map8.clear()
console.log('map8, post-clear size: %s', map8.size)

结果:

map8, pre-clear size: 3
map8, post-clear size: 0
遍历(keys(),values(),entries(),forEach())

提供三个新的方法 —— entries(),keys()和values() —— 用于遍历数组。它们都返回一个遍历器对象,可以用for…of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。

1.keys()

遍历map的所有key。

const map9 = new Map();
map9.set('k1', 1);
map9.set('k2', 2);
map9.set('k3', 3);
for (let key of map9.keys()) {console.log(key);
}

结果:

k1 
k2 
k3 

2.values()

遍历map所有的值。

for (let value of map9.values()) {console.log(value);
}

结果:

1 
2 
3 

3.entries()

遍历map的所有键值对。

方法1:

for (let item of map9.entries()) {console.log(item[0], item[1]);
}

结果:

k1 1
k2 2
k3 3

方法2:

for (let [key, value] of map9.entries()) {console.log(key, value);
}

结果不变。

4.forEach()

遍历map的所有键值对。

map9.forEach(function(value, key, map) {console.log("Key: %s, Value: %s", key, value);
});

结果:

Key: k1, Value: 1
Key: k2, Value: 2
Key: k3, Value: 3

forEach有第二个参数,可以用来绑定this。

这样有个好处,map的存储的数据和业务处理对象可以分离,业务处理对象可以尽可能的按职责分割的明确符合SRP原则。

const output = {log: function(key, value) {console.log("Key: %s, Value: %s", key, value);}
};map9.forEach(function(value, key, map) {this.log(key, value);
}, output);
和其它结构的互转
1.Map 转 Array

使用扩展运算符三个点(…)可将map内的元素都展开的数组。

const map10 = new Map();
map10.set('k1', 1);
map10.set('k2', 2);
map10.set('k3', 3);
console.log([...map10]);    //Array.from(map10)

结果:

[ [ 'k1', 1 ], [ 'k2', 2 ], [ 'k3', 3 ] ]
2.Array 转 Map

使用数组构造Map。

const map11 = new Map([['name', 'Aissen'],['age', 12]
])
console.log(map11)

结果:

Map { 'name' => 'Aissen', 'age' => 12 }
3.Map 转 Object

写一个转换函数,遍历map的所有元素,将元素的键和值作为对象属性名和值写入Object中。

function mapToObj(map) {let obj = Object.create(null);for (let [k,v] of map) {obj[k] = v;}return obj;
}const map12 = new Map().set('k1', 1).set({pa:1}, 2);
console.log(mapToObj(map12))

结果:

{ k1: 1, '[object Object]': 2 }
4.Object 转 Map

同理,再写一个转换函数便利Object,将属性名和值作为键值对写入Map。

function objToMap(obj) {let map = new Map();for (let k of Object.keys(obj)) {map.set(k, obj[k]);}return map;
}console.log(objToMap({yes: true, no: false}))

结果:

Map { 'yes' => true, 'no' => false }
5.Set 转 Map
const set = new Set([['foo', 1],['bar', 2]
]);
const map13 = new Map(set)
console.log(map13)

结果:

Map { 'foo' => 1, 'bar' => 2 }
6.Map 转 Set
function mapToSet(map) {let set = new Set()for (let [k,v] of map) {set.add([k, v])}return set;
}const map14 = new Map().set('k1', 1).set({pa:1}, 2);
console.log(mapToSet(map14))

结果:

Set { [ 'k1', 1 ], [ { pa: 1 }, 2 ] }

10.ES6原生提供Proxy构造函数,用来生成Proxy实例

概述

Proxy 用于修改某些操作的默认行为, 等同于在语言层面做出修改, 所以书序一种 “元编程”, 即对编程语言进行编程.

Proxy 可以理解成, 在目标对象之前假设一层 “拦截” , 外界对该对象的访问, 都必须先通过这层拦截, 因此提供了一种机制, 可以对外界的访问进行过滤和改写. Proxy 这个词的意思原意是代理, 用在这里表示由 它来 “代理” 某些操作.

var obj = new Proxy({}, {get: function (target, propKey, receiver) {console.log(`getting ${propKey}!`);return Reflect.get(target, propKey, receiver);},set: function (target, propKey, value, receiver) {console.log(`setting ${propKey}!`);return Reflect.set(target, propKey, value, receiver);}
});1234567891011

上面代码对一个空对象架设了一层拦截, 重定义了属性的读取 get 和设置set` 的行为.

obj.count = 1 // setting count!  赋值操作走 set 方法.
obj.count // getting count!    取值操作 走 get 方法
12

上面代码说明,Proxy 实际上重载(overload)了点运算符,即用自己的定义覆盖了语言的原始定义。

ES6 原生提供 Proxy 构造函数, 用来生成 Proxy 实例

let proxy = new Proxy(targetObj, handler)
1

Proxy 对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,**handler参数也是一个对象,**用来定制拦截行为。

下面是另一个拦截读取属性行为的例子。

var proxy = new Proxy({}, {get: function(target, propKey) {return 35;}
});proxy.time // 35
proxy.name // 35
proxy.title // 35
123456789

注意,要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。

Proxy 实例的方法
get()

get 方法用于拦截某个属性的读取操作, 可以接收 三个 参数, 依次为 目标对象, 属性名, 和 proxy 实例本身(严格的说, 是操作行为所针对的对象), 其中最后一个参数可选.

let person = {name: 'Gene',age: 18,
}let proxy = new Proxy(person, {get(target, propKey, receiver) {console.log(target, propKey, receiver); // {name: "Gene", age: 18}, "name",  Proxy:{name:'Gene',age:18}if (propKey in target) {return target[propKey]} else {throw new ReferenceError('抱歉引用错误')}}
})console.log(proxy.name); // Gene
proxy.sex // ReferenceError: 抱歉引用错误
123456789101112131415161718

上面代码表示, 如果访问的目标对象不存在的属性, 会抛出一个错误, 如果没有这个拦截函数, 访问不存在的属性, 只会返回 undefined

get 方法可以被继承

let proxy = new Proxy({}, {get(target, p, receiver) {console.log('当前为GET方法,参数为 -->' + p) // 当前为GET方法,参数为 -->fooreturn target[p]}
})
let obj = Object.create(proxy)
obj.foo
12345678

上面代码中, 拦截操作定义在 Prototype 对象上面, 所以如果读取 obj 对象属性时, 拦截会生效.

下面是一个 get 方法的第三个参数的例子, 它总是指向原始的读操作所在的对象, 一般情况下就是 Proxy 实例.

const person = {name: 'Gene'
}
let proxy = new Proxy(person, {get(target, p, receiver) {console.log(receiver === person); // falseconsole.log(receiver === proxy); // truereturn receiver}
})
proxy.name
1234567891011

上面代码中, 我们可以看到, 实际上, get方法的第三个属性reveiver 就是指向 proxy 对象.

如果一个属性不可配置(configurable)且不可写(writable),则 Proxy 不能修改该属性,否则通过 Proxy 对象访问该属性会报错。

const target = Object.defineProperties({}, {foo: {value: 123,writable: false,configurable: false},
});const handler = {get(target, propKey) {return 'abc';}
};const proxy = new Proxy(target, handler);proxy.foo
// TypeError: Invariant check failed
123456789101112131415161718
set()

set 方法用来拦截某个属性的赋值操作, 可以接收四个参数, 依次为: 目标对象, 属性名, 属性值Proxy实例本身 , 其中最后一个参数可选.

假定Person对象有一个age属性,该属性应该是一个不大于 200 的整数,那么可以使用Proxy保证age的属性值符合要求

let validator = {set: function(obj, prop, value) {if (prop === 'age') {if (!Number.isInteger(value)) {throw new TypeError('The age is not an integer');}if (value > 200) {throw new RangeError('The age seems invalid');}}// 对于满足条件的 age 属性以及其他属性,直接保存obj[prop] = value;}
};let person = new Proxy({}, validator);person.age = 100;person.age // 100
person.age = 'young' // 报错
person.age = 300 // 报错
1234567891011121314151617181920212223

上面代码中,由于设置了存值函数set,任何不符合要求的age属性赋值,都会抛出一个错误,这是数据验证的一种实现方法。利用set方法,还可以数据绑定,即每当对象发生变化时,会自动更新 DOM。

有时,我们会在对象上面设置内部属性,属性名的第一个字符使用下划线开头,表示这些属性不应该被外部使用。结合getset方法,就可以做到防止这些内部属性被外部读写。

const handler = {get (target, key) {invariant(key, 'get');return target[key];},set (target, key, value) {invariant(key, 'set');target[key] = value;return true;}
};
function invariant (key, action) {console.log(123,key, key[0]);if (key[0] === '_') {throw new Error(`Invalid attempt to ${action} private "${key}" property`);}
}
const target = {};
const proxy = new Proxy(target, handler);
proxy._prop
// Error: Invalid attempt to get private "_prop" property
proxy._prop = 'c'
// Error: Invalid attempt to set private "_prop" property
1234567891011121314151617181920212223

上面代码中,只要读写的属性名的第一个字符是下划线,一律抛错,从而达到禁止读写内部属性的目的。

apply()

apply方法拦截函数的调用、callapply操作。

apply方法可以接受三个参数,分别是目标对象目标对象的上下文对象this)和目标对象的参数数组。

var handler = {apply (target, ctx, args) {return Reflect.apply(...arguments);}
};
12345

下面是一个例子

var target = function () { return 'I am the target'; };
var handler = {apply() {return 'I am the proxy';}
};var p = new Proxy(target, handler);console.log(p()); // "I am the proxy"
12345678910

上面代码中, 变量 p 是 Proxy 的实例, 当它作为函数调用时p() , 就会被 apply()方法拦截, 返回一个字符串.

下面是另外一个例子

var twice = {apply (target, ctx, args) {console.log(...arguments);console.log( 'Reflect.apply(...arguments)', Reflect.apply(...arguments));return Reflect.apply(...arguments) * 2;}
};
function sum (left, right) {console.log(left, right);return left + right;
};
var proxy = new Proxy(sum, twice);
proxy(1, 2) // 6
proxy.call(null, 5, 6) // 22
proxy.apply(null, [7, 8]) // 30
123456789101112131415

上面代码中,每当执行proxy函数(直接调用或callapply调用),就会被apply方法拦截。

has()

has() 方法用来拦截 HasProperty 操作, 即判断对象是否有某个属性时,这个方法会生效. 典型的操作就是 in 运算符.

has方法可以接受两个参数,分别是目标对象、需查询的属性名

var handler = {has (target, key) {if (key[0] === '_') {return false;}console.log(key in target); //return key in target;}
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler);
console.log('_prop' in proxy); // false
console.log('prop' in proxy); // true
12345678910111213

上面代码中,如果原对象的属性名的第一个字符是下划线,proxy.has就会返回false,从而不会被in运算符发现。

construct()

construct方法用于拦截new命令,下面是拦截对象的写法。

var handler = {construct (target, args, newTarget) {return new target(...args);}
};
12345

construct方法可以接受三个参数。

  • target:目标对象
  • args:构造函数的参数对象
  • newTarget:创造实例对象时,new命令作用的构造函数(下面例子的p
var p = new Proxy(function () {}, {construct: function(target, args) {console.log('called: ' + args.join(', '));return { value: args[0] * 10 };}
});new p(1) //  called: 1
console.log((new p(1)).value); // 10
123456789

construct方法返回的必须是一个对象,否则会报错。

deleteProperty()

deleteProperty方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete`命令删除。

var handler = {deleteProperty (target, key) {invariant(key, 'delete');delete target[key];return true;}
};
function invariant (key, action) {if (key[0] === '_') {throw new Error(`Invalid attempt to ${action} private "${key}" property`);}
}var target = { _prop: 'foo' };
var proxy = new Proxy(target, handler);
delete proxy._prop
// Error: Invalid attempt to delete private "_prop" property
1234567891011121314151617

上面代码中,deleteProperty方法拦截了delete操作符,删除第一个字符为下划线的属性会报错。

注意,目标对象自身的不可配置(configurable)的属性,不能被deleteProperty方法删除,否则报错。

defineProperty()

defineProperty()方法拦截了Object.defineProperty()操作。

var handler = {defineProperty (target, key, descriptor) {return false;}
};
var target = {};
var proxy = new Proxy(target, handler);
proxy.foo = 'bar' // 不会生效
12345678

上面代码中,defineProperty()方法内部没有任何操作,只返回false,导致添加新属性总是无效。注意,这里的false只是用来提示操作失败,本身并不能阻止添加新属性。

注意,如果目标对象不可扩展(non-extensible),则defineProperty()不能增加目标对象上不存在的属性,否则会报错。另外,如果目标对象的某个属性不可写(writable)或不可配置(configurable),则defineProperty()方法不得改变这两个设置。

Proxy.revocable()

Proxy.revocable() 方法, 返回一个可取消的 Proxy 实例

let target = {};
let handler = {};let {proxy, revoke} = Proxy.revocable(target, handler);proxy.foo = 123;
proxy.foo // 123revoke();
proxy.foo // TypeError: Revoked
12345678910

Proxy.revocable()方法返回一个对象,该对象的proxy属性是Proxy实例,revoke属性是一个函数,可以取消Proxy实例。上面代码中,当执行revoke函数之后,再访问Proxy实例,就会抛出一个错误。

Proxy.revocable()的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。

this问题

虽然 Proxy 可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在 Proxy 代理的情况下,目标对象内部的this关键字会指向 Proxy 代理。

const target = {m: function () {console.log(this === proxy);}
};
const handler = {};const proxy = new Proxy(target, handler);target.m() // false
proxy.m()  // true
1234567891011

上面代码中,一旦proxy代理target.m,后者内部的this就是指向proxy,而不是target

实例: Web 服务的客户端

Proxy 对象可以拦截目标对象的任意属性,这使得它很合适用来写 Web 服务的客户端。

const service = createWebService('http://example.com/data');service.employees().then(json => {const employees = JSON.parse(json);// ···
});
123456

上面代码新建了一个 Web 服务的接口,这个接口返回各种数据。Proxy 可以拦截这个对象的任意属性,所以不用为每一种数据写一个适配方法,只要写一个 Proxy 拦截就可以了。

function createWebService(baseUrl) {return new Proxy({}, {get(target, propKey, receiver) {return `我是 httpGet 返回的数据哦 -->> ${propKey}`// return () => httpGet(baseUrl + '/' + propKey); // 返回异步获取的数据}});
}console.log(createWebService('http://hao123.com').get); // 我是 httpGet 返回的数据哦 -->> get

11.ES6新增了生成器(Generator)和遍历器(Iterator)

Iterator 迭代器

JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了Map和Set。这样就需要一种统一的接口机制,来处理所有不同的数据结构。遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

1.Iterator的作用:
  • 为各种数据结构,提供一个统一的、简便的访问接口;
  • 使得数据结构的成员能够按某种次序排列
  • ES6创造了一种新的遍历命令for…of循环,Iterator接口主要供for…of消费。
2.原生具备iterator接口的数据(可用for of遍历)
  • Array
  • set容器
  • map容器
  • String
  • 函数的 arguments 对象
  • NodeList 对象
let arr3 = [1, 2, 'kobe', true];
for(let i of arr3){console.log(i); // 1 2 kobe true
}
let str = 'abcd';
for(let item of str){console.log(item); // a b c d
}   
function fun() {for (let i of arguments) {console.log(i) // 1 4 5}
}
fun(1, 4, 5)
var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (var e of engines) {console.log(e);
}
// Gecko
// Trident
// Webkit    
3.迭代器的工作原理
  • 创建一个指针对象,指向数据结构的起始位置。
  • 第一次调用next方法,指针自动指向数据结构的第一个成员
  • 接下来不断调用next方法,指针会一直往后移动,直到指向最后一个成员
  • 每调用next方法返回的是一个包含value和done的对象,{value: 当前成员的值,done: 布尔值}
    • value表示当前成员的值,done对应的布尔值表示当前的数据的结构是否遍历结束。
    • 当遍历结束的时候返回的value值是undefined,done值为true
4.手写一个迭代器
    function myIterator(arr) {let nextIndex = 0return {next: function() {return nextIndex < arr.length? { value: arr[nextIndex++], done: false }: { value: undefined, done: true }}}}let arr = [1, 4, 'ads']// 准备一个数据let iteratorObj = myIterator(arr)console.log(iteratorObj.next()) // 所有的迭代器对象都拥有next()方法,会返回一个结果对象console.log(iteratorObj.next())console.log(iteratorObj.next())console.log(iteratorObj.next())

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IABc8om5-1596162978529)(https://camo.githubusercontent.com/b348a869a0ba6df07eefd8e001e7c1db99795551/68747470733a2f2f75706c6f61642d696d616765732e6a69616e7368752e696f2f75706c6f61645f696d616765732f333137343730312d646266633863663639663062656336372e706e673f696d6167654d6f6772322f6175746f2d6f7269656e742f7374726970253743696d61676556696577322f322f772f31323430)]

5.注意点

① for of循环不支持遍历普通对象

var obj = { a: 2, b: 3 }for (let i of obj) {console.log(i) // Uncaught TypeError: obj is not iterable
}

对象的Symbol.iterator属性,指向该对象的默认遍历器方法。当使用for of去遍历某一个数据结构的时候,首先去找Symbol.iterator,找到了就去遍历,没有找到的话不能遍历,提示Uncaught TypeError: XXX is not iterable

② 当使用扩展运算符(…)或者对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator方法

let arr1 = [1,3]
let arr2 = [2,3,4,5]
arr2 = [1,...arr2,6]
console.log(arr2) // [1, 2, 3, 4, 5, 6]
Generator生成器
1.概念
  • Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同

    语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。

  • Generator 函数除了状态机,还是一个遍历器对象生成函数。

  • 可暂停函数(惰性求值), yield可暂停,next方法可启动。每次返回的是yield后的表达式结果

2.特点
  • function关键字与函数名之间有一个星号;
  • 函数体内部使用yield表达式,定义不同的内部状态
 function* generatorExample(){console.log("开始执行")yield 'hello';  yield 'generator'; }
// generatorExample() 
// 这种调用方法Generator 函数并不会执行
let MG = generatorExample() // 返回指针对象
MG.next() //开始执行  {value: "hello", done: false}

Generator 函数是分段执行的,调用next方法函数内部逻辑开始执行,遇到yield表达式停止,返回{value: yield后的表达式结果/undefined, done: false/true},再次调用next方法会从上一次停止时的yield处开始,直到最后。

function* helloWorldGenerator() {yield 'hello';yield 'world';return 'ending';
}
var hw = helloWorldGenerator();
hw.next()// { value: 'hello', done: false }
hw.next()// { value: 'world', done: false }
hw.next()// { value: 'ending', done: true }
hw.next()// { value: undefined, done: true }

第一次调用,Generator 函数开始执行,直到遇到第一个yield表达式为止。next方法返回一个对象,它的value属性就是当前yield表达式的值hello,done属性的值false,表示遍历还没有结束。

第二次调用,Generator 函数从上次yield表达式停下的地方,一直执行到下一个yield表达式。next方法返回的对象的value属性就是当前yield表达式的值world,done属性的值false,表示遍历还没有结束。

第三次调用,Generator 函数从上次yield表达式停下的地方,一直执行到return语句(如果没有return语句,就执行到函数结束)。next方法返回的对象的value属性,就是紧跟在return语句后面的表达式的值(如果没有return语句,则value属性的值为undefined),done属性的值true,表示遍历已经结束。

第四次调用,此时 Generator 函数已经运行完毕,next方法返回对象的value属性为undefined,done属性为true。以后再调用next方法,返回的都是这个值。

3.next传递参数

yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

function* generatorExample () {console.log('开始执行')let result = yield 'hello'console.log(result)yield 'generator'
}
let MG = generatorExample()
MG.next()
MG.next()
// 开始执行
// undefined
// {value: "generator", done: false}

没有传值时result默认是undefined,接下来我们向第二个next传递一个参数,看下输出结果是啥?

function* generatorExample () {console.log('开始执行')let result = yield 'hello'console.log(result)yield 'generator'
}
let MG = generatorExample()
MG.next()
MG.next(11)
// 开始执行
// 11
// {value: "generator", done: false}
4.与 Iterator 接口的关系

我们上文中提到对象没有iterator接口,用for…of遍历时便会报错。

let obj = { username: 'kobe', age: 39 }
for (let i of obj) {console.log(i) //  Uncaught TypeError: obj is not iterable
}

由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。

let obj = { username: 'kobe', age: 39 }
obj[Symbol.iterator] = function* myTest() {yield 1;yield 2;yield 3;
};
for (let i of obj) {console.log(i) // 1 2 3
}

上面代码中,Generator函数赋值给Symbol.iterator属性,从而使得obj对象具有了 Iterator 接口,可以被for of遍历了。

5.Generator的异步的应用

业务需求:

  • 发送ajax请求获取新闻内容
  • 新闻内容获取成功后再次发送请求,获取对应的新闻评论内容
  • 新闻内容获取失败则不需要再次发送请求。

如何实现(前端核心代码如下):

    function* sendXml() {// url为next传参进来的数据let url = yield getNews('http://localhost:3000/news?newsId=2');//获取新闻内容yield getNews(url);//获取对应的新闻评论内容,只有先获取新闻的数据拼凑成url,才能向后台请求}function getNews(url) {$.get(url, function (data) {console.log(data);let commentsUrl = data.commentsUrl;let url = 'http://localhost:3000' + commentsUrl;// 当获取新闻内容成功,发送请求获取对应的评论内容// 调用next传参会作为上次暂停是yield的返回值sx.next(url);})}let sx = sendXml();// 发送请求获取新闻内容sx.next();
查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 2020P气瓶充装模拟考试题库及P气瓶充装作业考试题库

    题库来源:安全生产模拟考试一点通公众号小程序2020P气瓶充装模拟考试题库及P气瓶充装作业考试题库,包含P气瓶充装模拟考试题库答案解析及P气瓶充装作业考试题库练习。由安全生产模拟考试一点通公众号结合国家P气瓶充装考试最新大纲及P气瓶充装考试真题出具,有助于P气瓶充装怎…...

    2024/4/29 6:05:20
  2. Java生鲜电商平台-优惠券系统设计详解

    Java生鲜电商平台-优惠券系统设计详解Java生鲜电商平台-优惠券系统设计详解优惠券作为电商最常用的营销手段,对于商家而言可以起到拉新、促活、提高转化的作用,对用户而言也可以获得实惠,今天就来谈谈优惠券系统的设计逻辑。我对于优惠券系统的理解就是:创建什么样的优惠券…...

    2024/5/7 6:59:55
  3. 2020B证(安全员)考试题库及B证(安全员)模拟考试

    题库来源:安全生产模拟考试一点通公众号小程序2020B证(安全员)考试题库及B证(安全员)模拟考试,包含B证(安全员)考试题库答案解析及B证(安全员)模拟考试练习。由安全生产模拟考试一点通公众号结合国家B证(安全员)考试最新大纲及B证(安全员)考试真题出具,有助于B证(安全员)模拟…...

    2024/4/15 15:38:09
  4. 线段树基础(学习笔记)

    什么是线段树呢? 它其实是一种用于区间修改、区间求和的工具。 本文先讲解单点更新的线段树,比较基础;之后的进阶版会涉及区间更新。 首先理解线段树的思想是关键,齐次要学会使用线段树的模板,最后要学会灵活使用线段树。线段树与之前学过的树状数组有些类似,先贴上一张图…...

    2024/5/5 4:37:07
  5. 快手号怎么样才能快速涨粉,快手直播怎么能快速涨粉,快手快速涨粉技巧讲解

    快手号怎么样才能快速涨粉,快手直播怎么能快速涨粉,快手快速涨粉技巧讲解; 如果你在快手发布视频后,快手视频会有一个审批,这一审批主要是设备实际操作的,假如设备审批不太好,人公会接设备的盘,审批的目地是避免废弃物內容展现,比如情色,暴力行为恐怖,违背国家规定的视…...

    2024/4/25 13:30:06
  6. 中国质造,洽洽质造——恭喜洽洽在2020年质造节获得双项大奖!

    7月29日,由数央网、数央公益联合国内众多财经媒体共同发起的2020国际质造节在天津举行。洽洽食品凭借卓越的品质和对创新的追求,在本次国际质造节上一举斩获“匠心质造奖——杰出企业奖”及“中国质造推动者大奖”双项大奖。中国“质”造,洽洽“质”造品质铸就成长,创新赢得…...

    2024/5/5 11:59:28
  7. DVWA靶场-暴力破解

    暴力破解 低级 因为等级比较低,我们上来直接抓包暴力破解:选择攻击模块分别为变量1,2选择字典,这里是我手工创建了两个简单的账号密码字典load直接导入,直接添加其他方法都行成功中级 我们查看一下源代码,对比低级别发现做了限制如下 mysqli_real_escape_string()函数:会…...

    2024/5/5 5:04:33
  8. 关于Idea介绍概念以及配置!

    Intellij IDEA开发工具: 1:IDEA开发工具的介绍 2:安装步骤(目录说明) 3:启动配置(界面介绍/主题/字体/文件编码) 4:JDK/Tomcat/Maven配置 5:集成SVN/Git 6:创建java项目 /创建web项目/创建分布式项目 7:常用快捷键的介绍 8:IDEA热部署 1、IDEA开发工具的介绍 常用…...

    2024/4/17 3:34:45
  9. 2020叉车司机考试题及叉车司机模拟考试

    题库来源:安全生产模拟考试一点通公众号小程序2020叉车司机考试题及叉车司机模拟考试,包含叉车司机考试题答案解析及叉车司机模拟考试练习。由安全生产模拟考试一点通公众号结合国家叉车司机考试最新大纲及叉车司机考试真题出具,有助于叉车司机模拟考试题考前练习。1、【判断…...

    2024/5/4 18:20:03
  10. DriverFatal error: St-link, No MCU device foundSession aborted

    问题描述: 用STM32F103做测试 ST-link插上板子,烧录一个程序后,再次烧写就报错之后ST-link就罢工了,monitor,programmer都连接不上 反正ST-link就是查无此人 最后枚举尝试,怀疑IAR,ST-LINK,芯片,还是我程序的问题,一一排除 最后发现,ioc生成工程的问题,也就是ioc有问…...

    2024/4/27 19:25:27
  11. 疫情洗礼后的实体企业倒下了吗?

    2019年突发的疫情不仅仅让整个城市停滞下来,也让很多企业感受到了寒冬带来的不期而遇,因为不能满足线上的需求,线下门店濒临倒闭,但是是不是真的实体企业就会在互联网线上时代变得不堪一击呢? 2005年淘宝在市场上大杀四方的时候,实体企业瞧不起,总是风流的讲,淘宝质量没…...

    2024/5/1 8:58:15
  12. 周观察|金融类APP侵害用户权益何时休

    来源 | 周观新金融 作者 | 周公子 提高个人信息保护觉悟靠大家。数据安全问题,自去年到现在都是大家热议的焦点。7月24日,工信部再次通报了一批侵害用户权益的APP名单,这已经是自5月来通报的第三批了,其中不乏天弘基金、小鹅花钱(微众银行小程序)、华夏基金管家、博时基…...

    2024/5/1 3:30:38
  13. 精美图文讲解Java AQS 共享式获取同步状态以及Semaphore的应用

    | 好看请赞,养成习惯你有一个思想,我有一个思想,我们交换后,一个人就有两个思想If you can NOT explain it simply, you do NOT understand it well enough现陆续将Demo代码和技术文章整理在一起 Github实践精选 ,方便大家阅读查看,本文同样收录在此,觉得不错,还请Star…...

    2024/5/1 0:17:14
  14. gridView 的可编辑性问题

    DevExpress GridView 的编辑性设置 1. gridView 选中可编辑设置 grid 可选中,即在设计器中将 ShowSelectionColumn 属性设置为 true(默认为 false) 或者在在 load 事件或者其他出使用 this.efDevGrid1.ShowSelectionColumns = true; 来设置 注册 gridView1_ShowingEditor 事…...

    2024/4/15 17:13:26
  15. Dubbo-01

    RPC(分布式服务框架)什么叫RPC RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是…...

    2024/5/1 8:53:27
  16. Android横竖屏切换测试记录

    横竖屏切换测试记录华为M6 (android 10)1. 横竖屏切换2. 切后台再显示3. 横竖屏切换变换layout-land和layout-port布局 华为M6 (android 10) 1. 横竖屏切换android:configChanges 切横屏生命周期 切竖屏生命周期 总结不设置 onPause()onDestroy()onCreate()onResume() onPause(…...

    2024/5/6 15:43:44
  17. python运算符以及优先级

    除了常见的 加、减、乘、除、取整、取余、幂之外,还有以下运算方式: 1.and 逻辑与使用方法,用于判断两者返回结果是true还是false,如:a = Trueb = Falsea and b -- False解释:当a为True,b为False时,只要有一个为False,必返回False,**引申:当前者为True时,返回第二个…...

    2024/4/15 17:13:22
  18. hiberbate入门

    这里写目录标题一、什么是hibernate二、如何在项目中添加hibernate三、hibernate 核心api分析 一、什么是hibernate ORM框架/持久层框架 dao dao jdbc hibernate jdbc ORM(Object Relational Mapping):对象关系映射。 对象与关系型数据库之间的映射管理框架…...

    2024/4/30 15:15:02
  19. 前端重要知识点面试题-总结

    calc, support, media各自的含义及用法?@support主要是用于检测浏览器是否支持CSS的某个属性,其实就是条件判断,如果支持某个属性,你可以写一套样式,如果不支持某个属性,你也可以提供另外一套样式作为替补。calc() 函数用于动态计算长度值。 calc()函数支持 "+"…...

    2024/4/24 11:11:48
  20. PuppeteerSharp:网页数据爬取工具

    一些页面的数据会通过js解密后才显示在html,若直接通过http请求是无法获取到需要解密的数据,使用该工具可在页面加载完后,该工具可获取页面加载完后的html。 /// <summary>/// 单例/// </summary>public static class ChromeFactory{internal static Browser br…...

    2024/4/27 17:50:41

最新文章

  1. postman介绍、安装、使用、功能特点、注意事项

    Postman是一款流行的API开发工具&#xff0c;它提供了丰富的功能&#xff0c;包括创建、测试、调试和文档化API。本文将介绍Postman的安装、使用方法&#xff0c;以及其功能特点和注意事项。 1. 介绍 Postman是一款用于构建、测试和调试API的工具&#xff0c;它提供了用户友好的…...

    2024/5/10 4:14:26
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/5/9 21:23:04
  3. Kafka架构概述

    Kafka的体系结构 Kafka是由Apache软件基金会管理的一个开源的分布式数据流处理平台。Kafka具有支持消息的发布/订阅模式、高吞吐量与低延迟、持久化、支持水平扩展、高可用性等特点。可以将Kafka应用于大数据实时处理、高性能数据管道、流分析、数据集成和关键任务应用等场景。…...

    2024/5/10 0:11:33
  4. 通过mapreduce程序统计旅游订单(wordcount升级版)

    通过mapreduce程序统计旅游订单&#xff08;wordcount升级版&#xff09; 本文将结合一个实际的MapReduce程序案例&#xff0c;探讨如何通过分析旅游产品的预订数据来揭示消费者的偏好。 程序概览 首先&#xff0c;让我们来看一下这个MapReduce程序的核心代码。这个程序的目…...

    2024/5/9 4:30:23
  5. 416. 分割等和子集问题(动态规划)

    题目 题解 class Solution:def canPartition(self, nums: List[int]) -> bool:# badcaseif not nums:return True# 不能被2整除if sum(nums) % 2 ! 0:return False# 状态定义&#xff1a;dp[i][j]表示当背包容量为j&#xff0c;用前i个物品是否正好可以将背包填满&#xff…...

    2024/5/10 1:36:26
  6. 【Java】ExcelWriter自适应宽度工具类(支持中文)

    工具类 import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet;/*** Excel工具类** author xiaoming* date 2023/11/17 10:40*/ public class ExcelUti…...

    2024/5/9 7:40:42
  7. Spring cloud负载均衡@LoadBalanced LoadBalancerClient

    LoadBalance vs Ribbon 由于Spring cloud2020之后移除了Ribbon&#xff0c;直接使用Spring Cloud LoadBalancer作为客户端负载均衡组件&#xff0c;我们讨论Spring负载均衡以Spring Cloud2020之后版本为主&#xff0c;学习Spring Cloud LoadBalance&#xff0c;暂不讨论Ribbon…...

    2024/5/9 2:44:26
  8. TSINGSEE青犀AI智能分析+视频监控工业园区周界安全防范方案

    一、背景需求分析 在工业产业园、化工园或生产制造园区中&#xff0c;周界防范意义重大&#xff0c;对园区的安全起到重要的作用。常规的安防方式是采用人员巡查&#xff0c;人力投入成本大而且效率低。周界一旦被破坏或入侵&#xff0c;会影响园区人员和资产安全&#xff0c;…...

    2024/5/10 2:07:45
  9. VB.net WebBrowser网页元素抓取分析方法

    在用WebBrowser编程实现网页操作自动化时&#xff0c;常要分析网页Html&#xff0c;例如网页在加载数据时&#xff0c;常会显示“系统处理中&#xff0c;请稍候..”&#xff0c;我们需要在数据加载完成后才能继续下一步操作&#xff0c;如何抓取这个信息的网页html元素变化&…...

    2024/5/9 3:15:57
  10. 【Objective-C】Objective-C汇总

    方法定义 参考&#xff1a;https://www.yiibai.com/objective_c/objective_c_functions.html Objective-C编程语言中方法定义的一般形式如下 - (return_type) method_name:( argumentType1 )argumentName1 joiningArgument2:( argumentType2 )argumentName2 ... joiningArgu…...

    2024/5/9 5:40:03
  11. 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】

    &#x1f468;‍&#x1f4bb;博客主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 收录于专栏 【洛谷算法题】 文章目录 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】&#x1f30f;题目描述&#x1f30f;输入格…...

    2024/5/9 7:40:40
  12. 【ES6.0】- 扩展运算符(...)

    【ES6.0】- 扩展运算符... 文章目录 【ES6.0】- 扩展运算符...一、概述二、拷贝数组对象三、合并操作四、参数传递五、数组去重六、字符串转字符数组七、NodeList转数组八、解构变量九、打印日志十、总结 一、概述 **扩展运算符(...)**允许一个表达式在期望多个参数&#xff0…...

    2024/5/10 2:07:43
  13. 摩根看好的前智能硬件头部品牌双11交易数据极度异常!——是模式创新还是饮鸩止渴?

    文 | 螳螂观察 作者 | 李燃 双11狂欢已落下帷幕&#xff0c;各大品牌纷纷晒出优异的成绩单&#xff0c;摩根士丹利投资的智能硬件头部品牌凯迪仕也不例外。然而有爆料称&#xff0c;在自媒体平台发布霸榜各大榜单喜讯的凯迪仕智能锁&#xff0c;多个平台数据都表现出极度异常…...

    2024/5/10 2:07:43
  14. Go语言常用命令详解(二)

    文章目录 前言常用命令go bug示例参数说明 go doc示例参数说明 go env示例 go fix示例 go fmt示例 go generate示例 总结写在最后 前言 接着上一篇继续介绍Go语言的常用命令 常用命令 以下是一些常用的Go命令&#xff0c;这些命令可以帮助您在Go开发中进行编译、测试、运行和…...

    2024/5/9 4:12:16
  15. 用欧拉路径判断图同构推出reverse合法性:1116T4

    http://cplusoj.com/d/senior/p/SS231116D 假设我们要把 a a a 变成 b b b&#xff0c;我们在 a i a_i ai​ 和 a i 1 a_{i1} ai1​ 之间连边&#xff0c; b b b 同理&#xff0c;则 a a a 能变成 b b b 的充要条件是两图 A , B A,B A,B 同构。 必要性显然&#xff0…...

    2024/5/9 7:40:35
  16. 【NGINX--1】基础知识

    1、在 Debian/Ubuntu 上安装 NGINX 在 Debian 或 Ubuntu 机器上安装 NGINX 开源版。 更新已配置源的软件包信息&#xff0c;并安装一些有助于配置官方 NGINX 软件包仓库的软件包&#xff1a; apt-get update apt install -y curl gnupg2 ca-certificates lsb-release debian-…...

    2024/5/9 19:47:07
  17. Hive默认分割符、存储格式与数据压缩

    目录 1、Hive默认分割符2、Hive存储格式3、Hive数据压缩 1、Hive默认分割符 Hive创建表时指定的行受限&#xff08;ROW FORMAT&#xff09;配置标准HQL为&#xff1a; ... ROW FORMAT DELIMITED FIELDS TERMINATED BY \u0001 COLLECTION ITEMS TERMINATED BY , MAP KEYS TERMI…...

    2024/5/9 7:40:34
  18. 【论文阅读】MAG:一种用于航天器遥测数据中有效异常检测的新方法

    文章目录 摘要1 引言2 问题描述3 拟议框架4 所提出方法的细节A.数据预处理B.变量相关分析C.MAG模型D.异常分数 5 实验A.数据集和性能指标B.实验设置与平台C.结果和比较 6 结论 摘要 异常检测是保证航天器稳定性的关键。在航天器运行过程中&#xff0c;传感器和控制器产生大量周…...

    2024/5/10 2:07:41
  19. --max-old-space-size=8192报错

    vue项目运行时&#xff0c;如果经常运行慢&#xff0c;崩溃停止服务&#xff0c;报如下错误 FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory 因为在 Node 中&#xff0c;通过JavaScript使用内存时只能使用部分内存&#xff08;64位系统&…...

    2024/5/9 5:02:59
  20. 基于深度学习的恶意软件检测

    恶意软件是指恶意软件犯罪者用来感染个人计算机或整个组织的网络的软件。 它利用目标系统漏洞&#xff0c;例如可以被劫持的合法软件&#xff08;例如浏览器或 Web 应用程序插件&#xff09;中的错误。 恶意软件渗透可能会造成灾难性的后果&#xff0c;包括数据被盗、勒索或网…...

    2024/5/9 4:31:45
  21. JS原型对象prototype

    让我简单的为大家介绍一下原型对象prototype吧&#xff01; 使用原型实现方法共享 1.构造函数通过原型分配的函数是所有对象所 共享的。 2.JavaScript 规定&#xff0c;每一个构造函数都有一个 prototype 属性&#xff0c;指向另一个对象&#xff0c;所以我们也称为原型对象…...

    2024/5/9 16:54:42
  22. C++中只能有一个实例的单例类

    C中只能有一个实例的单例类 前面讨论的 President 类很不错&#xff0c;但存在一个缺陷&#xff1a;无法禁止通过实例化多个对象来创建多名总统&#xff1a; President One, Two, Three; 由于复制构造函数是私有的&#xff0c;其中每个对象都是不可复制的&#xff0c;但您的目…...

    2024/5/10 1:31:37
  23. python django 小程序图书借阅源码

    开发工具&#xff1a; PyCharm&#xff0c;mysql5.7&#xff0c;微信开发者工具 技术说明&#xff1a; python django html 小程序 功能介绍&#xff1a; 用户端&#xff1a; 登录注册&#xff08;含授权登录&#xff09; 首页显示搜索图书&#xff0c;轮播图&#xff0…...

    2024/5/9 6:36:49
  24. 电子学会C/C++编程等级考试2022年03月(一级)真题解析

    C/C++等级考试(1~8级)全部真题・点这里 第1题:双精度浮点数的输入输出 输入一个双精度浮点数,保留8位小数,输出这个浮点数。 时间限制:1000 内存限制:65536输入 只有一行,一个双精度浮点数。输出 一行,保留8位小数的浮点数。样例输入 3.1415926535798932样例输出 3.1…...

    2024/5/9 4:33:29
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57