使用 ES6 写更好的 JavaScript part I:广受欢迎新特性

介绍

在ES2015规范敲定并且Node.js增添了大量的函数式子集的背景下,我们终于可以拍着胸脯说:未来就在眼前。

… 我早就想这样说了

但这是真的。V8引擎将很快实现规范,而且Node已经添加了大量可用于生产环境的ES2015特性。下面要列出的是一些我认为很有必要的特性,而且这些特性是不使用需要像Babel或者Traceur这样的翻译器就可以直接使用的。

这篇文章将会讲到三个相当流行的ES2015特性,并且已经在Node中支持了了:

  • 用let和const声明块级作用域;

  • 箭头函数;

  • 简写属性和方法。

让我们马上开始。

let和const声明块级作用域

作用域是你程序中变量可见的区域。换句话说就是一系列的规则,它们决定了你声明的变量在哪里是可以使用的。

大家应该都听过 ,在JavaScript中只有在函数内部才会创造新的作用域。然而你创建的98%的作用域事实上都是函数作用域,其实在JavaScript中有三种创建新作用域的方法。你可以这样:

  1. 创建一个函数。你应该已经知道这种方式。

  2. 创建一个catch块。 我绝对没哟开玩笑.

  3. 创建一个代码块。如果你用的是ES2015,在一段代码块中用let或者const声明的变量会限制它们只在这个块中可见。这叫做块级作用域。

一个代码块就是你用花括号包起来的部分。 { 像这样 }。在if/else声明和try/catch/finally块中经常出现。如果你想利用块作用域的优势,你可以用花括号包裹任意的代码来创建一个代码块

考虑下面的代码片段。

// 在 Node 中你需要使用 strict 模式尝试这个
"use strict";var foo = "foo";
function baz() {if (foo) {var bar = "bar";let foobar = foo + bar;}// foo 和 bar 这里都可见 console.log("This situation is " + foo + bar + ". I'm going home.");try {console.log("This log statement is " + foobar + "! It threw a ReferenceError at me!");} catch (err) {console.log("You got a " + err + "; no dice.");}try {console.log("Just to prove to you that " + err + " doesn't exit outside of the above `catch` block.");} catch (err) {console.log("Told you so.");}
}baz();try {console.log(invisible);
} catch (err) {console.log("invisible hasn't been declared, yet, so we get a " + err);
}
let invisible = "You can't see me, yet"; // let 声明的变量在声明前是不可访问的

还有些要强调的

  • 注意foobar在if块之外是不可见的,因为我们没有用let声明;

  • 我们可以在任何地方使用foo ,因为我们用var定义它为全局作用域可见;

  • 我们可以在baz内部任何地方使用bar, 因为var-声明的变量是在定义的整个作用域内都可见。

  • 用let or const声明的变量不能在定义前调用。换句话说,它不会像var变量一样被编译器提升到作用域的开始处。

const 与 let 类似,但有两点不同。

  1. 必须给声明为const的变量在声明时赋值。不可以先声明后赋值。

  2. 不能改变const变量的值,只有在创建它时可以给它赋值。如果你试图改变它的值,会得到一个TyepError。

let & const: Who Cares?

我们已经用var将就了二十多年了,你可能在想我们真的需要新的类型声明关键字吗?(这里作者应该是想表达这个意思)

问的好,简单的回答就是–不, 并不真正需要。但在可以用let和const的地方使用它们很有好处的。

  • let和const声明变量时都不会被提升到作用域开始的地方,这样可以使代码可读性更强,制造尽可能少的迷惑。

  • 它会尽可能的约束变量的作用域,有助于减少令人迷惑的命名冲突。

  • 这样可以让程序只有在必须重新分配变量的情况下重新分配变量。 const 可以加强常量的引用。

另一个例子就是 let 在 for 循环中的使用:

"use strict";var languages = ['Danish', 'Norwegian', 'Swedish'];//会污染全局变量!
for (var i = 0; i < languages.length; i += 1) {console.log(`${languages[i]} is a Scandinavian language.`);
}console.log(i); // 4for (let j = 0; j < languages.length; j += 1) {console.log(`${languages[j]} is a Scandinavian language.`);
}try {console.log(j); // Reference error
} catch (err) {console.log(`You got a ${err}; no dice.`);
}

在for循环中使用var声明的计数器并不会真正把计数器的值限制在本次循环中。 而let可以。

let在每次迭代时重新绑定循环变量有很大的优势,这样每个循环中拷贝自身 , 而不是共享全局范围内的变量。

"use strict";// 简洁明了
for (let i = 1; i < 6; i += 1) {setTimeout(function() {console.log("I've waited " + i + " seconds!");}, 1000 * i);
}// 功能完全混乱
for (var j = 0; j < 6; j += 1) {setTimeout(function() {console.log("I've waited " + j + " seconds for this!");}, 1000 * j);
}

第一层循环会和你想象的一样工作。而下面的会每秒输出 “I’ve waited 6 seconds!”。

好吧,我选择狗带。

动态this关键字的怪异

JavaScript的this关键字因为总是不按套路出牌而臭名昭著。

事实上,它的规则相当简单。不管怎么说,this在有些情形下会导致奇怪的用法

"use strict";const polyglot = {name : "Michel Thomas",languages : ["Spanish", "French", "Italian", "German", "Polish"],introduce : function () {// this.name is "Michel Thomas"const self = this;this.languages.forEach(function(language) {// this.name is undefined, so we have to use our saved "self" variable console.log("My name is " + self.name + ", and I speak " + language + ".");});}
}polyglot.introduce();

在introduce里, this.name是undefined。在回调函数外面,也就是forEach中, 它指向了polyglot对象。在这种情形下我们总是希望在函数内部this和函数外部的this指向同一个对象。

问题是在JavaScript中函数会根据确定性四原则在调用时定义自己的this变量。这就是著名的动态this 机制。

这些规则中没有一个是关于查找this所描述的“附近作用域”的;也就是说并没有一个确切的方法可以让JavaScript引擎能够基于包裹作用域来定义this的含义。

这就意味着当引擎查找this的值时,可以找到值,但却和回调函数之外的不是同一个值。有两种传统的方案可以解决这个问题。

  • 在函数外面把this保存到一个变量中,通常取名self,并在内部函数中使用;

  • 或者在内部函数中调用bind阻止对this的赋值。

以上两种办法均可生效,但会产生副作用。

另一方面,如果内部函数没有设置它自己的this值,JavaScript会像查找其它变量那样查找this的值:通过遍历父作用域直到找到同名的变量。这样会让我们使用附近作用域代码中的this值,这就是著名的词法this。

如果有样的特性,我们的代码将会更加的清晰,不是吗?

箭头函数中的词法this

在 ES2015 中,我们有了这一特性。箭头函数不会绑定this值,允许我们利用词法绑定this关键字。这样我们就可以像这样重构上面的代码了:

"use strict";let polyglot = {name : "Michel Thomas",languages : ["Spanish", "French", "Italian", "German", "Polish"],introduce : function () {this.languages.forEach((language) => {console.log("My name is " + this.name + ", and I speak " + language + ".");});}
}

… 这样就会按照我们想的那样工作了。

箭头函数有一些新的语法。

"use strict";let languages = ["Spanish", "French", "Italian", "German", "Polish"];// 多行箭头函数必须使用花括号, 
// 必须明确包含返回值语句let languages_lower = languages.map((language) => {return language.toLowerCase()
});// 单行箭头函数,花括号是可省的,
// 函数默认返回最后一个表达式的值
// 你可以指明返回语句,这是可选的。
let languages_lower = languages.map((language) => language.toLowerCase());// 如果你的箭头函数只有一个参数,可以省略括号
let languages_lower = languages.map(language => language.toLowerCase());// 如果箭头函数有多个参数,必须用圆括号包裹
let languages_lower = languages.map((language, unused_param) => language.toLowerCase());console.log(languages_lower); // ["spanish", "french", "italian", "german", "polish"]// 最后,如果你的函数没有参数,你必须在箭头前加上空的括号。
(() => alert("Hello!"))();

MDN关于箭头函数的文档解释的很好。

简写属性和方法

ES2015提供了在对象上定义属性和方法的一些新方式。

简写方法

在 JavaScript 中, method 是对象的一个有函数值的属性:

"use strict";const myObject = {const foo = function () {console.log('bar');},
}

在ES2015中,我们可以这样简写:

"use strict";const myObject = {foo () {console.log('bar');},* range (from, to) {while (from < to) {if (from === to)return ++from;elseyield from ++;}}
}

注意你也可以使用生成器去定义方法。只需要在函数名前面加一个星号(*)。

这些叫做 方法定义 。和传统的函数作为属性很像,但有一些不同:

  • 只能在方法定义处调用super;

  • 不允许用new调用方法定义。

我会在随后的几篇文章中讲到super关键字。如果你等不及了, Exploring ES6中有关于它的干货。

简写和推导属性

ES6还引入了简写和推导属性 。

如果对象的键值和变量名是一致的,那么你可以仅用变量名来初始化你的对象,而不是定义冗余的键值对。

"use strict";const foo = 'foo';
const bar = 'bar';// 旧语法
const myObject = {foo : foo,bar : bar
};// 新语法
const myObject = { foo, bar }

两中语法都以foo和bar键值指向foo and bar变量。后面的方式语义上更加一致;这只是个语法糖。

当用揭示模块模式来定义一些简洁的公共 API 的定义,我常常利用简写属性的优势。

"use strict";function Module () {function foo () {return 'foo';}function bar () {return 'bar';}// 这样写:const publicAPI = { foo, bar }/* 不要这样写:const publicAPI =  {foo : foo,bar : bar} */ return publicAPI;
};

这里我们创建并返回了一个publicAPI对象,键值foo指向foo方法,键值bar指向bar方法。

推导属性名

这是不常见的例子,但ES6允许你用表达式做属性名。

"use strict";const myObj = {// 设置属性名为 foo 函数的返回值[foo ()] () {return 'foo';}
};function foo () {return 'foo';
}console.log(myObj.foo() ); // 'foo'

根据Dr. Raushmayer在Exploring ES6中讲的,这种特性最主要的用途是设置属性名与Symbol值一样。

Getter 和 Setter 方法

最后,我想提一下get和set方法,它们在ES5中就已经支持了。

"use strict";// 例子采用的是 MDN's 上关于 getter 的内容
//   https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get
const speakingObj = {// 记录 “speak” 方法调用过多少次words : [],speak (word) {this.words.push(word);console.log('speakingObj says ' + word + '!');},get called () {// 返回最新的单词const words = this.words;if (!words.length)return 'speakingObj hasn\'t spoken, yet.';elsereturn words[words.length - 1];}
};console.log(speakingObj.called); // 'speakingObj hasn't spoken, yet.'speakingObj.speak('blargh'); // 'speakingObj says blargh!'console.log(speakingObj.called); // 'blargh'

使用getters时要记得下面这些:

  • Getters不接受参数;

  • 属性名不可以和getter函数重名;

  • 可以用Object.defineProperty(OBJECT, "property name", { get : function () { . . . } }) 动态创建 getter

作为最后这点的例子,我们可以这样定义上面的 getter 方法:

"use strict";const speakingObj = {// 记录 “speak” 方法调用过多少次words : [],speak (word) {this.words.push(word);console.log('speakingObj says ' + word + '!');}
};// 这只是为了证明观点。我是绝对不会这样写的
function called () {// 返回新的单词const words = this.words;if (!words.length)return 'speakingObj hasn\'t spoken, yet.';elsereturn words[words.length - 1];
};Object.defineProperty(speakingObj, "called", get : getCalled ) 
除了 getters,还有 setters。像平常一样,它们通过自定义的逻辑给对象设置属性。"use strict";// 创建一个新的 globetrotter(环球者)!
const globetrotter = {// globetrotter 现在所处国家所说的语言 const current_lang = undefined,// globetrotter 已近环游过的国家let countries = 0,// 查看环游过哪些国家了get countryCount () {return this.countries;}, // 不论 globe trotter 飞到哪里,都重新设置他的语言set languages (language) {// 增加环游过的城市数countries += 1;// 重置当前语言this.current_lang = language; };
};globetrotter.language = 'Japanese';
globetrotter.countryCount(); // 1globetrotter.language = 'Spanish';
globetrotter.countryCount(); // 2

上面讲的关于getters的也同样适用于setters,但有一点不同:

  • getter不接受参数,setters必须接受正好一个参数。

破坏这些规则中的任意一个都会抛出一个错误。

既然 Angular 2 正在引入TypeCript并且把class带到了台前,我希望get and set能够流行起来… 但还有点希望它们不要流行起来。

结论

未来的JavaScript正在变成现实,是时候把它提供的东西都用起来了。这篇文章里,我们浏览了 ES2015的三个很流行的特性:

  • let和const带来的块级作用域;

  • 箭头函数带来的this的词法作用域;

  • 简写属性和方法,以及getter和setter函数的回顾。

使用 ES6 编写更好的 JavaScript Part II:深入探究 [类]

辞旧迎新

在本文的开始,我们要说明一件事:

从本质上说,ES6的classes主要是给创建老式构造函数提供了一种更加方便的语法,并不是什么新魔法 —— Axel Rauschmayer,Exploring ES6作者

从功能上来讲,class声明就是一个语法糖,它只是比我们之前一直使用的基于原型的行为委托功能更强大一点。本文将从新语法与原型的关系入手,仔细研究ES2015的class关键字。文中将提及以下内容:

  • 定义与实例化类;

  • 使用extends创建子类;

  • 子类中super语句的调用;

  • 以及重要的标记方法(symbol method)的例子。

在此过程中,我们将特别注意 class 声明语法从本质上是如何映射到基于原型代码的。

让我们从头开始说起。

退一步说:Classes不是什么

JavaScript的『类』与Java、Python或者其他你可能用过的面向对象语言中的类不同。其实后者可能称作面向『类』的语言更为准确一些。

在传统的面向类的语言中,我们创建的类是对象的模板。需要一个新对象时,我们实例化这个类,这一步操作告诉语言引擎将这个类的方法和属性复制到一个新实体上,这个实体称作实例。实例是我们自己的对象,且在实例化之后与父类毫无内在联系。

而JavaScript没有这样的复制机制。在JavaScript中『实例化』一个类创建了一个新对象,但这个新对象却不独立于它的父类。

正相反,它创建了一个与原型相连接的对象。即使是在实例化之后,对于原型的修改也会传递到实例化的新对象去。

原型本身就是一个无比强大的设计模式。有许多使用了原型的技术模仿了传统类的机制,class便为这些技术提供了简洁的语法。

总而言之:

  • JavaScript不存在Java和其他面向对象语言中的类概念;

  • JavaScript 的class很大程度上只是原型继承的语法糖,与传统的类继承有很大的不同。

搞清楚这些之后,让我们先看一下class。

类基础:声明与表达式

我们使用class 关键字创建类,关键字之后是变量标识符,最后是一个称作类主体的代码块。这种写法称作类的声明。没有使用extends关键字的类声明被称作基类:

"use strict";// Food 是一个基类
class Food {constructor (name, protein, carbs, fat) {this.name = name;this.protein = protein;this.carbs = carbs;this.fat = fat;}toString () {return `${this.name} | ${this.protein}g P :: ${this.carbs}g C :: ${this.fat}g F`}print () {console.log( this.toString() );}
}const chicken_breast = new Food('Chicken Breast', 26, 0, 3.5);chicken_breast.print(); // 'Chicken Breast | 26g P :: 0g C :: 3.5g F'
console.log(chicken_breast.protein); // 26 (LINE A)

需要注意到以下事情:

  • 类只能包含方法定义,不能有数据属性;

  • 定义方法时,可以使用简写方法定义;

  • 与创建对象不同,我们不能在类主体中使用逗号分隔方法定义;

  • 我们可以在实例化对象上直接引用类的属性(如 LINE A)。

类有一个独有的特性,就是 contructor 构造方法。在构造方法中我们可以初始化对象的属性。

构造方法的定义并不是必须的。如果不写构造方法,引擎会为我们插入一个空的构造方法:

"use strict";class NoConstructor {/* JavaScript 会插入这样的代码:constructor () { }*/
}const nemo = new NoConstructor(); // 能工作,但没啥意思

将一个类赋值给一个变量的形式叫类表达式,这种写法可以替代上面的语法形式:

"use strict";// 这是一个匿名类表达式,在类主体中我们不能通过名称引用它
const Food = class {// 和上面一样的类定义……
}// 这是一个命名类表达式,在类主体中我们可以通过名称引用它
const Food = class FoodClass {// 和上面一样的类定义……//  添加一个新方法,证明我们可以通过内部名称引用 FoodClass……        printMacronutrients () {console.log(`${FoodClass.name} | ${FoodClass.protein} g P :: ${FoodClass.carbs} g C :: ${FoodClass.fat} g F`)}
}const chicken_breast = new Food('Chicken Breast', 26, 0, 3.5);
chicken_breast.printMacronutrients(); // 'Chicken Breast | 26g P :: 0g C :: 3.5g F'// 但是不能在外部引用
try {console.log(FoodClass.protein); // 引用错误
} catch (err) {// pass
}

这一行为与匿名函数与命名函数表达式很类似。

使用extends创建子类以及使用super调用

使用extends创建的类被称作子类,或派生类。这一用法简单明了,我们直接在上面的例子中构建:

"use strict";// FatFreeFood 是一个派生类
class FatFreeFood extends Food {constructor (name, protein, carbs) {super(name, protein, carbs, 0);}print () {super.print();console.log(`Would you look at that -- ${this.name} has no fat!`);}}const fat_free_yogurt = new FatFreeFood('Greek Yogurt', 16, 12);
fat_free_yogurt.print(); // 'Greek Yogurt | 26g P :: 16g C :: 0g F  /  Would you look at that -- Greek Yogurt has no fat!'

派生类拥有我们上文讨论的一切有关基类的特性,另外还有如下几点新特点:

  • 子类使用class关键字声明,之后紧跟一个标识符,然后使用extend关键字,最后写一个任意表达式。这个表达式通常来讲就是个标识符,但理论上也可以是函数。

  • 如果你的派生类需要引用它的父类,可以使用super关键字。

  • 一个派生类不能有一个空的构造函数。即使这个构造函数就是调用了一下super(),你也得把它显式的写出来。但派生类却可以没有构造函数。

  • 在派生类的构造函数中,必须先调用super,才能使用this关键字(译者注:仅在构造函数中是这样,在其他方法中可以直接使用this)。

在JavaScript中仅有两个super关键字的使用场景:

  1. 在子类构造函数中调用。如果初始化派生类是需要使用父类的构造函数,我们可以在子类的构造函数中调用super(parentConstructorParams),传递任意需要的参数。

  2. 引用父类的方法。在常规方法定义中,派生类可以使用点运算符来引用父类的方法:super.methodName。

我们的 FatFreeFood 演示了这两种情况:

  1. 在构造函数中,我们简单的调用了super,并将脂肪的量传入为0。

  2. 在我们的print方法中,我们先调用了super.print,之后才添加了其他的逻辑。

不管你信不信,我反正是信了以上说的已涵盖了有关class的基础语法,这就是你开始实验需要掌握的全部内容。

深入学习原型

现在我们开始关注class是怎么映射到JavaScript内部的原型机制的。我们会关注以下几点:

  • 使用构造调用创建对象;

  • 原型连接的本质;

  • 属性和方法委托;

  • 使用原型模拟类。

  • 使用构造调用创建对象

构造函数不是什么新鲜玩意儿。使用new关键字调用任意函数会使其返回一个对象 —— 这一步称作创建了一个构造调用,这种函数通常被称作构造器:

"use strict";function Food (name, protein, carbs, fat) {this.name    = name;this.protein = protein;this.carbs   = carbs;this.fat     = fat;
}// 使用 'new' 关键字调用 Food 方法,就是构造调用,该操作会返回一个对象
const chicken_breast = new Food('Chicken Breast', 26, 0, 3.5);
console.log(chicken_breast.protein) // 26// 不用 'new' 调用 Food 方法,会返回 'undefined'
const fish = Food('Halibut', 26, 0, 2);
console.log(fish); // 'undefined'

当我们使用new关键字调用函数时,JS内部执行了下面四个步骤:

  1. 创建一个新对象(这里称它为O);

  2. 给O赋予一个连接到其他对象的链接,称为原型;

  3. 将函数的this引用指向O;

  4. 函数隐式返回O。

在第三步和第四步之间,引擎会执行你函数中的具体逻辑。

知道了这一点,我们就可以重写Food方法,使之不用new关键字也能工作:

"use strict";// 演示示例:消除对 'new' 关键字的依赖
function Food (name, protein, carbs, fat) {// 第一步:创建新对象const obj = { };// 第二步:链接原型——我们在下文会更加具体地探究原型的概念Object.setPrototypeOf(obj, Food.prototype);// 第三步:设置 'this' 指向我们的新对象// 尽然我们不能再运行的执行上下文中重置 `this`// 我们在使用 'obj' 取代 'this' 来模拟第三步obj.name    = name;obj.protein = protein;obj.carbs   = carbs;obj.fat     = fat;// 第四步:返回新创建的对象return obj;
}const fish = Food('Halibut', 26, 0, 2);
console.log(fish.protein); // 26

四步中的三步都是简单明了的。创建一个对象、赋值属性、然后写一个return声明,这些操作对大多数开发者来说没有理解上的问题——然而这就是难倒众人的黑魔法原型。

直观理解原型链

在通常情况下,JavaScript中的包括函数在内的所有对象都会链接到另一个对象上,这就是原型。

如果我们访问一个对象本身没有的属性,JavaScript就会在对象的原型上检查该属性。换句话说,如果你对一个对象请求它没有的属性,它会对你说:『这个我不知道,问我的原型吧』。

在另一个对象上查找不存在属性的过程称作委托。

"use strict";// joe 没有 toString 方法……
const joe    = { name : 'Joe' },sara   = { name : 'Sara' };Object.hasOwnProperty(joe, toString); // false
Object.hasOwnProperty(sara, toString); // false// ……但我们还是可以调用它!
joe.toString(); // '[object Object]',而不是引用错误!
sara.toString(); // '[object Object]',而不是引用错误!

尽管我们的 toString 的输出完全没啥用,但请注意:这段代码没有引起任何的ReferenceError!这是因为尽管joe和sara没有toString的属性,但他们的原型有啊。

当我们寻找sara.toString()方法时,sara说:『我没有toString属性,找我的原型吧』。正如上文所说,JavaScript会亲切的询问Object.prototype 是否含有toString属性。由于原型上有这一属性,JS 就会把Object.prototype上的toString返回给我们程序并执行。

sara本身没有属性没关系——我们会把查找操作委托到原型上。

换言之,我们就可以访问到对象上并不存在的属性,只要其的原型上有这些属性。我们可以利用这一点将属性和方法赋值到对象的原型上,然后我们就可以调用这些属性,好像它们真的存在在那个对象上一样。

更给力的是,如果几个对象共享相同的原型——正如上面的joe和sara的例子一样——当我们给原型赋值属性之后,它们就都可以访问了,无需将这些属性单独拷贝到每一个对象上。

这就是为何大家把它称作原型继承——如果我的对象没有,但对象的原型有,那我的对象也能继承这个属性。

事实上,这里并没有发生什么『继承』。在面向类的语言里,继承指从父类复制属性到子类的行为。在JavaScript里,没发生这种复制的操作,事实上这就是原型继承与类继承相比的一个主要优势。

在我们探究原型究竟是怎么来的之前,我们先做一个简要回顾:

  • joe和sara没有『继承』一个toString的属性;

  • joe和sara实际上根本没有从Object.prototype上『继承』;

  • joe和sara是链接到了Object.prototype上;

  • joe和sara链接到了同一个Object.prototype上。

  • 如果想找到一个对象的(我们称它作O)原型,我们可以使用 Object.getPrototypeof(O)。

然后我们再强调一遍:对象没有『继承自』他们的原型。他们只是委托到原型上。

以上。

接下来让我们深入一下。

设置对象的原型

我们已了解到基本上每个对象(下文以O指代)都有原型(下文以P指代),然后当我们查找O上没有的属性,JavaScript引擎就会在P上寻找这个属性。

至此我们有两个问题:

  • 以上情况函数怎么玩?

  • 这些原型是从哪里来的?

名为Object的函数

在JavaScript引擎执行程序之前,它会创建一个环境让程序在内部执行,在执行环境中会创建一个函数,叫做Object, 以及一个关联对象,叫做Object.prototype。

换句话说,Object和Object.prototype在任意执行中的JavaScript程序中永远存在。

这个Object乍一看好像和其他函数没什么区别,但特别之处在于它是一个构造器——在调用它时返回一个新对象:

"use strict";typeof new Object(); // "object"
typeof Object();     // 这个 Object 函数的特点是不需要使用 new 关键字调用

这个Object.prototype对象是个……对象。正如其他对象一样,它有属性。

https://i.imgsafe.org/ebbd5e3.png

关于Object和Object.prototype你需要知道以下几点:

  1. Object函数有一个叫做.prototype的属性,指向一个对象(Object.prototype);

  2. Object.prototype对象有一个叫做.constructor的属性,指向一个函数(Object)。

实际上,这个总体方案对于JavaScript中的所有函数都是适用的。当我们创建一个函数——下文称作 someFunction——这个函数就会有一个属性.prototype,指向一个叫做someFunction.prototype 的对象。

与之相反,someFunction.prototype对象会有一个叫做.contructor的属性,它的引用指回函数someFunction。

"use strict";function foo () {  console.log('Foo!');  }console.log(foo.prototype); // 指向一个叫 'foo' 的对象
console.log(foo.prototype.constructor); // 指向 'foo' 函数foo.prototype.constructor(); // 输出 'Foo!' —— 仅为证明确实有 'foo.prototype.constructor' 这么个方法且指向原函数

需要记住以下几个要点:

  1. 所有的函数都有一个属性,叫做 .prototype,它指向这个函数的关联对象。

  2. 所有函数的原型都有一个属性,叫做 .constructor,它指向这个函数本身。

  3. 一个函数原型的 .constructor 并非必须指向创建这个函数原型的函数……有点绕,我们等下会深入探讨一下。

设置函数的原型有一些规则,在开始之前,我们先概括设置对象原型的三个规则:

  1. 『默认』规则;

  2. 使用new隐式设置原型;

  3. 使用Object.create显式设置原型。

默认规则

考虑下这段代码:

"use strict";const foo = { status : 'foobar' };

十分简单,我们做的事儿就是创建一个叫foo的对象,然后给他一个叫status的属性。

然后JavaScript在幕后多做了点工作。当我们在字面上创建一个对象时,JavaScript将对象的原型指向Object.prototype并设置其原型的.constructor指向Object:

"use strict";const foo = { status : 'foobar' };Object.getPrototypeOf(foo) === Object.prototype; // true
foo.constructor === Object; // true

使用new隐式设置原型

让我们再看下之前调整过的 Food 例子。

"use strict";function Food (name, protein, carbs, fat) {this.name    = name;this.protein = protein;this.carbs   = carbs;this.fat     = fat;
}

现在我们知道函数Food将会与一个叫做Food.prototype的对象关联。

当我们使用new关键字创建一个对象,JavaScript将会:

  1. 设置这个对象的原型指向我们使用new调用的函数的.prototype属性;

  2. 设置这个对象的.constructor指向我们使用new调用到的构造函数。

const tootsie_roll = new Food('Tootsie Roll', 0, 26, 0);Object.getPrototypeOf(tootsie_roll) === Food.prototype; // true
tootsie_roll.constructor === Food; // true

这就可以让我们搞出下面这样的黑魔法:

"use strict";Food.prototype.cook = function cook () {console.log(`${this.name} is cooking!`);
};const dinner = new Food('Lamb Chops', 52, 8, 32);
dinner.cook(); // 'Lamb Chops are cooking!'

使用Object.create显式设置原型

最后我们可以使用Object.create方法手工设置对象的原型引用。

"use strict";const foo = {speak () {console.log('Foo!');}
};const bar = Object.create(foo);bar.speak(); // 'Foo!'
Object.getPrototypeOf(bar) === foo; // true

还记得使用new调用函数的时候,JavaScript在幕后干了哪四件事儿吗?Object.create就干了这三件事儿:

  1. 创建一个新对象;

  2. 设置它的原型引用;

  3. 返回这个新对象。

你可以自己去看下MDN上写的那个polyfill。
(译者注:polyfill就是给老代码实现现有新功能的补丁代码,这里就是指老版本JS没有Object.create函数,MDN上有手工撸的一个替代方案)

模拟 class 行为

直接使用原型来模拟面向类的行为需要一些技巧。

"use strict";function Food (name, protein, carbs, fat) {this.name    = name;this.protein = protein;this.carbs   = carbs;this.fat     = fat;
}Food.prototype.toString = function () {return `${this.name} | ${this.protein}g P :: ${this.carbs}g C :: ${this.fat}g F`;
};function FatFreeFood (name, protein, carbs) {Food.call(this, name, protein, carbs, 0);
}// 设置 "subclass" 关系
// =====================
// LINE A :: 使用 Object.create 手动设置 FatFreeFood's 『父类』.
FatFreeFood.prototype = Object.create(Food.prototype);// LINE B :: 手工重置 constructor 的引用
Object.defineProperty(FatFreeFood.constructor, "constructor", {enumerable : false,writeable  : true,value      : FatFreeFood
});

在Line A,我们需要设置FatFreeFood.prototype使之等于一个新对象,这个新对象的原型引用是Food.prototype。如果没这么搞,我们的子类就不能访问『超类』的方法。

不幸的是,这个导致了相当诡异的结果:FatFreeFood.constructor是Function,而不是FatFreeFood。为了保证一切正常,我们需要在Line B手工设置FatFreeFood.constructor。

让开发者从使用原型对类行为笨拙的模仿中脱离苦海是class关键字的产生动机之一。它确实也提供了避免原型语法常见陷阱的解决方案。

现在我们已经探究了太多关于JavaScript的原型机制,你应该更容易理解class关键字让一切变得多么简单了吧!

深入探究下方法

现在我们已了解到JavaScript原型系统的必要性,我们将深入探究一下类支持的三种方法,以及一种特殊情况,以结束本文的讨论。

  • 构造器;

  • 静态方法;

  • 原型方法;

  • 一种原型方法的特殊情况:『标记方法』。

并非我提出的这三组方法,这要归功于Rauschmayer博士在探索ES6一书中的定义。

类构造器

一个类的constructor方法用于关注我们的初始化逻辑,constructor方法有以下几个特殊点:

  1. 只有在构造方法里,我们才可以调用父类的构造器;

  2. 它在背后处理了所有设置原型链的工作;

  3. 它被用作类的定义。

第二点就是在JavaScript中使用class的一个主要好处,我们来引用一下《探索 ES6》书里的15.2.3.1 的标题:

子类的原型就是超类

正如我们所见,手工设置非常繁琐且容易出错。如果我们使用class关键字,JavaScript在内部会负责搞定这些设置,这一点也是使用class的优势。

第三点有点意思。在JavaScript中类仅仅是个函数——它等同于与类中的constructor方法。

"use strict";class Food {// 和之前一样的类定义……
}typeof Food; // 'function'

与一般把函数作为构造器的方式不同,我们不能不用new关键字而直接调用类构造器:

const burrito = Food('Heaven', 100, 100, 25); // 类型错误

这就引发了另一个问题:当我们不用new调用函数构造器的时候发生了什么?

简短的回答是:对于任何没有显式返回的函数来说都是返回undefined。我们只需要相信用我们构造函数的用户都会使用构造调用。这就是社区为何约定构造方法的首字母大写:提醒使用者要用new来调用。

"use strict";function Food (name, protein, carbs, fat) {this.name    = name;this.protein = protein;this.carbs   = carbs;this.fat     = fat;
}const fish = Food('Halibut', 26, 0, 2); // D'oh . . .
console.log(fish); // 'undefined'

长一点的回答是:返回undefined,除非你手工检测是否使用被new调用,然后进行自己的处理。

ES2015引入了一个属性使得这种检测变得简单: new.target.

new.target是一个定义在所有使用new调用的函数上的属性,包括类构造器。 当我们使用new关键字调用函数时,函数体内的new.target的值就是这个函数本身。如果函数没有被new调用,这个值就是undefined。

"use strict";// 强行构造调用
function Food (name, protein, carbs, fat) {// 如果用户忘了手工调用一下if (!new.target)return new Food(name, protein, carbs, fat);this.name    = name;this.protein = protein;this.carbs   = carbs;this.fat     = fat;
}const fish = Food('Halibut', 26, 0, 2); // 糟了,不过没关系!
fish; // 'Food {name: "Halibut", protein: 20, carbs: 5, fat: 0}'

在ES5里用起来也还行:

"use strict";function Food (name, protein, carbs, fat) {if (!(this instanceof Food))return new Food(name, protein, carbs, fat);this.name    = name;this.protein = protein;this.carbs   = carbs;this.fat     = fat;
}

MDN文档讲述了new.target的更多细节,而且给有兴趣者配上了ES2015规范作为参考。规范里有关 [[Construct]] 的描述很有启发性。

静态方法

静态方法是构造方法自己的方法,不能被类的实例化对象调用。我们使用static关键字定义静态方法。

"use strict";class Food {// 和之前一样……// 添加静态方法static describe () {console.log('"Food" 是一种存储了营养信息的数据类型');}
}Food.describe(); // '"Food" 是一种存储了营养信息的数据类型'

静态方法与老式构造函数中直接属性赋值相似:

"use strict";function Food (name, protein, carbs, fat) {Food.count += 1;this.name    = name;this.protein = protein;this.carbs   = carbs;this.fat     = fat;
}Food.count = 0;
Food.describe = function count () {console.log(`你创建了 ${Food.count} 个 food`);
};const dummy = new Food();
Food.describe(); // "你创建了 1 个 food"

原型方法

任何不是构造方法和静态方法的方法都是原型方法。之所以叫原型方法,是因为我们之前通过给构造函数的原型上附加方法的方式来实现这一功能。

"use strict";// 使用 ES6:
class Food {constructor (name, protein, carbs, fat) {this.name = name;this.protein = protein;this.carbs = carbs;this.fat = fat;}toString () {  return `${this.name} | ${this.protein}g P :: ${this.carbs}g C :: ${this.fat}g F`;}print () {  console.log( this.toString() );  }
}// 在 ES5 里:
function Food  (name, protein, carbs, fat) {this.name = name;this.protein = protein;this.carbs = carbs;this.fat = fat;
}// 『原型方法』的命名大概来自我们之前通过给构造函数的原型上附加方法的方式来实现这一功能。
Food.prototype.toString = function toString () {return `${this.name} | ${this.protein}g P :: ${this.carbs}g C :: ${this.fat}g F`;
};Food.prototype.print = function print () {console.log( this.toString() );
};

应该说明,在方法定义时完全可以使用生成器。

"use strict";class Range {constructor(from, to) {this.from = from;this.to   = to;}* generate () {let counter = this.from,to      = this.to;while (counter < to) {if (counter == to)return counter++;elseyield counter++;}}
}const range = new Range(0, 3);
const gen = range.generate();
for (let val of range.generate()) {console.log(`Generator 的值是 ${ val }. `);//  Prints://    Generator 的值是 0.//    Generator 的值是 1.//    Generator 的值是 2.
}

标志方法

最后我们说说标志方法。这是一些名为Symbol值的方法,当我们在自定义对象中使用内置构造器时,JavaScript引擎可以识别并使用这些方法。

MDN文档提供了一个Symbol是什么的简要概览:

Symbol是一个唯一且不变的数据类型,可以作为一个对象的属性标示符。

创建一个新的symbol,会给我们提供一个被认为是程序里的唯一标识的值。这一点对于命名对象的属性十分有用:我们可以确保不会不小心覆盖任何属性。使用Symbol做键值也不是无数的,所以他们很大程度上对外界是不可见的(也不完全是,可以通过Reflect.ownKeys获得)

"use strict";const secureObject = {// 这个键可以看作是唯一的[new Symbol("name")] : 'Dr. Secure A. F.'
};console.log( Object.getKeys(superSecureObject) ); // [] -- 标志属性不太好获取    
console.log( Reflect.ownKeys(secureObject) ); // [Symbol("name")] -- 但也不是完全隐藏的

对我们来讲更有意思的是,这给我们提供了一种方式来告诉 JavaScript 引擎使用特定方法来达到特定的目的。

所谓的『众所周知的Symbol』是一些特定对象的键,当你在定义对象中使用时他们时,JavaScript引擎会触发一些特定方法。

这对于JavaScript来说有点怪异,我们还是看个例子吧:

"use strict";// 继承 Array 可以让我们直观的使用 'length'
// 同时可以让我们访问到内置方法,如
// map、filter、reduce、push、pop 等
class FoodSet extends Array {// foods 把传递的任意参数收集为一个数组// 参见:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operatorconstructor(...foods) {super();this.foods = [];foods.forEach((food) => this.foods.push(food))}// 自定义迭代器行为,请注意,这不是多么好用的迭代器,但是个不错的例子// 键名前必须写星号* [Symbol.iterator] () {let position = 0;while (position < this.foods.length) {if (position === this.foods.length) {return "Done!"} else {yield `${this.foods[ position++ ]} is the food item at position ${position}`;}}}// 当我们的用户使用内置的数组方法,返回一个数组类型对象// 而不是 FoodSet 类型的。这使得我们的 FoodSet 可以被一些// 期望操作数组的代码操作static get [Symbol.species] () {return Array;}
}const foodset = new FoodSet(new Food('Fish', 26, 0, 16), new Food('Hamburger', 26, 48, 24));// 当我们使用 for ... of 操作 FoodSet 时,JavaScript 将会使用
// 我们之前用 [Symbol.iterator] 做键值的方法
for (let food of foodset) {// 打印全部 foodconsole.log( food );
}// 当我们执行数组的 `filter` 方法时,JavaScript 创建并返回一个新对象
// 我们在什么对象上执行 `filter` 方法,新对象就使用这个对象作为默认构造器来创建
// 然而大部分代码都希望 filter 返回一个数组,于是我们通过重写 [Symbol.species]
// 的方式告诉 JavaScript 使用数组的构造器
const healthy_foods = foodset.filter((food) => food.name !== 'Hamburger');console.log( healthy_foods instanceof FoodSet ); //
console.log( healthy_foods instanceof Array );

当你使用for...of遍历一个对象时,JavaScript将会尝试执行对象的迭代器方法,这一方法就是该对象 Symbol.iterator属性上关联的方法。如果我们提供了自己的方法定义,JavaScript就会使用我们自定义的。如果没有自己制定的话,如果有默认的实现就用默认的,没有的话就不执行。

Symbo.species更奇异了。在自定义的类中,默认的Symbol.species函数就是类的构造函数。当我们的子类有内置的集合(例如Array和Set)时,我们通常希望在使用父类的实例时也能使用子类。

通过方法返回父类的实例而不是派生类的实例,使我们更能确保我们子类在大多数代码里的可用性。而Symbol.species可以实现这一功能。

如果不怎么需要这个功能就别费力去搞了。Symbol的这种用法——或者说有关Symbol的全部用法——都还比较罕见。这些例子只是为了演示:

  1. 我们可以在自定义类中使用JavaScript内置的特定构造器;

  2. 用两个普通的例子展示了怎么实现这一点。

结论

ES2015的class关键字没有带给我们 Java 里或是SmallTalk里那种『真正的类』。宁可说它只是提供了一种更加方便的语法来创建通过原型关联的对象,本质上没有什么新东西。

使用ES6写更好的JavaScript part III:好用的集合和反引号

简介

ES2015发生了一些重大变革,像promises和generators. 但并非新标准的一切都高不可攀。 – 相当一部分新特性可以快速上手。

在这篇文章里,我们来看下新特性带来的好处:

  • 新的集合: map,weakmap,set, weakset

  • 大部分的new String methods

  • 模板字符串。

我们开始这个系列的最后一章吧。

模板字符串

模板字符串 解决了三个痛点,允许你做如下操作:

  1. 定义在字符串内部的表达式,称为 字符串插值。

  2. 写多行字符串无须用换行符 (n) 拼接。

  3. 使用“raw”字符串 – 在反斜杠内的字符串不会被转义,视为常量。

“use strict”;/* 三个模板字符串的例子:字符串插值,多行字符串,raw 字符串。
================================= */
// ================================== 
// 1. 字符串插值 :: 解析任何一个字符串中的表达式。 
console.log(1 + 1 = ${1 + 1});// ================================== 
// 2. 多行字符串 :: 这样写: 
let childe_roland = 
I saw them and I knew them all. And yet <br> Dauntless the slug-horn to my lips I set, <br> And blew “Childe Roland to the Dark Tower came.”// … 代替下面的写法: 
child_roland = 
‘I saw them and I knew them all. And yet\n’ + 
‘Dauntless the slug-horn to my lips I set,\n’ + 
‘And blew “Childe Roland to the Dark Tower came.”’;// ================================== 
// 3. raw 字符串 :: 在字符串前加 raw 前缀,javascript 会忽略转义字符。 
// 依然会解析包在 ${} 的表达式 
const unescaped = String.rawThis ${string()} doesn't contain a newline!\nfunction string () { return “string”; }console.log(unescaped); // ‘This string doesn’t contain a newline!\n’ – 注意 \n 会被原样输出// 你可以像 React 使用 JSX 一样,用模板字符串创建 HTML 模板 
const template = 
`ExampleI’m a pure JS & HTML template!`function getClass () { 
// Check application state, calculate a class based on that state 
return “some-stateful-class”; 
}console.log(template); // 这样使用略显笨,自己试试吧!// 另一个常用的例子是打印变量名: 
const user = { name : ‘Joe’ };console.log(“User’s name is ” + user.name + “.”); // 有点冗长 
console.log(User's name is ${user.name}.); // 这样稍好一些
  1. 使用字符串插值,用反引号代替引号包裹字符串,并把我们想要的表达式嵌入在${}中。

  2. 对于多行字符串,只需要把你要写的字符串包裹在反引号里,在要换行的地方直接换行。 JavaScript 会在换行处插入新行。

  3. 使用原生字符串,在模板字符串前加前缀String.raw,仍然使用反引号包裹字符串。

模板字符串或许只不过是一种语法糖 … 但它比语法糖略胜一筹。

新的字符串方法

ES2015也给String新增了一些方法。他们主要归为两类:

  1. 通用的便捷方法

  2. 扩充 Unicode 支持的方法。

在本文里我们只讲第一类,同时unicode特定方法也有相当好的用例 。如果你感兴趣的话,这是地址在MDN的文档里,有一个关于字符串新方法的完整列表。

startsWith & endsWith

对新手而言,我们有String.prototype.startsWith。 它对任何字符串都有效,它需要两个参数:

  1. 一个是 search string 还有

  2. 整形的位置参数 n。这是可选的。

String.prototype.startsWith方法会检查以nth位起的字符串是否以search string开始。如果没有位置参数,则默认从头开始。

如果字符串以要搜索的字符串开头返回 true,否则返回 false。

"use strict";const contrived_example = "This is one impressively contrived example!";// 这个字符串是以 "This is one" 开头吗?
console.log(contrived_example.startsWith("This is one")); // true// 这个字符串的第四个字符以 "is" 开头?
console.log(contrived_example.startsWith("is", 4)); // false// 这个字符串的第五个字符以 "is" 开始?
console.log(contrived_example.startsWith("is", 5)); // true

endsWith

String.prototype.endsWith和startswith相似: 它也需要两个参数:一个是要搜索的字符串,一个是位置。

然而String.prototype.endsWith位置参数会告诉函数要搜索的字符串在原始字符串中被当做结尾处理。

换句话说,它会切掉nth后的所有字符串,并检查是否以要搜索的字符结尾。

"use strict";const contrived_example = "This is one impressively contrived example!";console.log(contrived_example.endsWith("contrived example!")); // trueconsole.log(contrived_example.slice(0, 11)); // "This is one"
console.log(contrived_example.endsWith("one", 11)); // true// 通常情况下,传一个位置参数向下面这样:
function substringEndsWith (string, search_string, position) {// Chop off the end of the stringconst substring = string.slice(0, position);// 检查被截取的字符串是否已 search_string 结尾return substring.endsWith(search_string);
}

includes

ES2015也添加了String.prototype.includes。 你需要用字符串调用它,并且要传递一个搜索项。如果字符串包含搜索项会返回true,反之返回false。

"use strict";const contrived_example = "This is one impressively contrived example!";// 这个字符串是否包含单词 impressively ?
contrived_example.includes("impressively"); // true

ES2015之前,我们只能这样:

"use strict";
contrived_example.indexOf("impressively") !== -1 // true

不算太坏。但是,String.prototype.includes是 一个改善,它屏蔽了任意整数返回值为true的漏洞。

repeat

还有String.prototype.repeat。可以对任意字符串使用,像includes一样,它会或多或少地完成函数名指示的工作。

它只需要一个参数: 一个整型的count。使用案例说明一切,上代码:

const na = "na";console.log(na.repeat(5) + ", Batman!"); // 'nanananana, Batman!'

raw

最后,我们有String.raw,我们在上面简单介绍过。

一个模板字符串以 String.raw 为前缀,它将不会在字符串中转义:

/* 单右斜线要转义,我们需要双右斜线才能打印一个右斜线,\n 在普通字符串里会被解析为换行*   */
console.log('This string \\ has fewer \\ backslashes \\ and \n breaks the line.');// 不想这样写的话用 raw 字符串
String.raw`This string \\ has too many \\ backslashes \\ and \n doesn't break the line.`

Unicode方法

虽然我们不涉及剩余的 string 方法,但是如果我不告诉你去这个主题的必读部分就会显得我疏忽。

  • Dr Rauschmayer对于Unicode in JavaScript的介绍

  • 他关于ES2015’s Unicode Support in Exploring ES6和The Absolute Minimum Every Software Developer Needs to Know About Unicode 的讨论。

无论如何我不得不跳过它的最后一部分。虽然有些老但是还是有优点的。

这里是文档中缺失的字符串方法,这样你会知道缺哪些东西了。

  • String.fromCodePoint & String.prototype.codePointAt;

  • String.prototype.normalize;

  • Unicode point escapes.

集合

ES2015新增了一些集合类型:

  1. Map和WeakMap

  2. Set和WeakSet。

合适的Map和Set类型十分方便使用,还有弱变量是一个令人兴奋的改动,虽然它对Javascript来说像舶来品一样。

Map

map就是简单的键值对。最简单的理解方式就是和object类似,一个键对应一个值。

"use strict";// 我们可以把 foo 当键,bar 当值
const obj = { foo : 'bar' };// 对象键为 foo 的值为 bar
obj.foo === 'bar'; // true

新的Map类型在概念上是相似的,但是可以使用任意的数据类型作为键 – 不止strings和symbols–还有除了pitfalls associated with trying to use an objects a map的一些东西。

下面的片段例举了 Map 的 API.

"use strict";// 构造器
let scotch_inventory = new Map();// BASIC API METHODS
// Map.prototype.set (K, V) :: 创建一个键 K,并设置它的值为 V。
scotch_inventory.set('Lagavulin 18', 2);
scotch_inventory.set('The Dalmore', 1);// 你可以创建一个 map 里面包含一个有两个元素的数组
scotch_inventory = new Map([['Lagavulin 18', 2], ['The Dalmore', 1]]);// 所有的 map 都有 size 属性,这个属性会告诉你 map 里有多少个键值对。
// 用 Map 或 Set 的时候,一定要使用 size ,不能使用 length
console.log(scotch_inventory.size); // 2// Map.prototype.get(K) :: 返回键相关的值。如果键不存在返回 undefined
console.log(scotch_inventory.get('The Dalmore')); // 1
console.log(scotch_inventory.get('Glenfiddich 18')); // undefined// Map.prototype.has(K) :: 如果 map 里包含键 K 返回true,否则返回 false
console.log(scotch_inventory.has('The Dalmore')); // true
console.log(scotch_inventory.has('Glenfiddich 18')); // false// Map.prototype.delete(K) :: 从 map 里删除键 K。成功返回true,不存在返回 false
console.log(scotch_inventory.delete('The Dalmore')); // true -- breaks my heart// Map.prototype.clear() :: 清楚 map 中的所有键值对
scotch_inventory.clear();
console.log( scotch_inventory ); // Map {} -- long night// 遍历方法
// Map 提供了多种方法遍历键值。 
//  重置值,继续探索
scotch_inventory.set('Lagavulin 18', 1);
scotch_inventory.set('Glenfiddich 18', 1);/* Map.prototype.forEach(callback[, thisArg]) :: 对 map 里的每个键值对执行一个回调函数 *   你可以在回调函数内部设置 'this' 的值,通过传递一个 thisArg 参数,那是可选的而且没有太大必要那样做*   最后,注意回调函数已经被传了键和值 */
scotch_inventory.forEach(function (quantity, scotch) {console.log(`Excuse me while I sip this ${scotch}.`);
});// Map.prototype.keys() :: 返回一个 map 中的所有键
const scotch_names = scotch_inventory.keys();
for (let name of scotch_names) {console.log(`We've got ${name} in the cellar.`);
}// Map.prototype.values() :: 返回 map 中的所有值
const quantities = scotch_inventory.values();
for (let quantity of quantities) {console.log(`I just drank ${quantity} of . . . Uh . . . I forget`);
}// Map.prototype.entries() :: 返回 map 的所有键值对,提供一个包含两个元素的数组 
//   以后会经常看到 map 里的键值对和 "entries" 关联 
const entries = scotch_inventory.entries();
for (let entry of entries) {console.log(`I remember! I drank ${entry[1]} bottle of ${entry[0]}!`);
}

但是Object在保存键值对的时候仍然有用。 如果符合下面的全部条件,你可能还是想用Object:

  1. 当你写代码的时候,你知道你的键值对。

  2. 你知道你可能不会去增加或删除你的键值对。

  3. 你使用的键全都是 string 或 symbol。

另一方面,如果符合以下任意条件,你可能会想使用一个 map。

  1. 你需要遍历整个map – 然而这对 object 来说是难以置信的.

  2. 当你写代码的时候不需要知道键的名字或数量。

  3. 你需要复杂的键,像 Object 或 别的 Map (!).

像遍历一个map一样遍历一个object是可行的,但奇妙的是–还会有一些坑潜伏在暗处。 Map更容易使用,并且增加了一些可集成的优势。然而object是以随机顺序遍历的,map是以插入的顺序遍历的。

添加随意动态键名的键值对给一个object是可行的。但奇妙的是: 比如说如果你曾经遍历过一个伪 map,你需要记住手动更新条目数。

最后一条,如果你要设置的键名不是string或symbol,你除了选择Map别无选择。

上面的这些只是一些指导性的意见,并不是最好的规则。

WeakMap

你可能听说过一个特别棒的特性垃圾回收器,它会定期地检查不再使用的对象并清除。

To quote Dr Rauschmayer:

WeakMap 不会阻止它的键值被垃圾回收。那意味着你可以把数据和对象关联起来不用担心内存泄漏。

换句换说,就是你的程序丢掉了WeakMap键的所有外部引用,他能自动垃圾回收他们的值。

尽管大大简化了用例,考虑到SPA(单页面应用) 就是用来展示用户希望展示的东西,像一些物品描述和一张图片,我们可以理解为API返回的JSON。

理论上来说我们可以通过缓存响应结果来减少请求服务器的次数。我们可以这样用Map :

"use strict";const cache = new Map();function put (element, result) {cache.set(element, result);
}function retrieve (element) {return cache.get(element);
}

… 这是行得通的,但是有内存泄漏的危险。

因为这是一个SPA,用户或许想离开这个视图,这样的话我们的 “视图”object就会失效,会被垃圾回收。

不幸的是,如果你使用的是正常的Map ,当这些object不使用时,你必须自行清除。

使用WeakMap替代就可以解决上面的问题:

"use strict";const cache = new WeakMap(); // 不会再有内存泄露了// 剩下的都一样

这样当应用失去不需要的元素的引用时,垃圾回收系统可以自动重用那些元素。

WeakMap的API和Map相似,但有如下几点不同:

  1. 在WeakMap里你可以使用object作为键。 这意味着不能以String和Symbol做键。

  2. WeakMap只有set,get,has,和delete方法 – 那意味着你不能遍历weak map.

  3. WeakMaps没有size属性。

不能遍历或检查WeakMap的长度的原因是,在遍历过程中可能会遇到垃圾回收系统的运行: 这一瞬间是满的,下一秒就没了。

这种不可预测的行为需要谨慎对待,TC39(ECMA第39届技术委员会)曾试图避免禁止WeakMap的遍历和长度检测。

其他的案例,可以在这里找到Use Cases for WeakMap,来自Exploring ES6.

Set

Set就是只包含一个值的集合。换句换说,每个set的元素只会出现一次。

这是一个有用的数据类型,如果你要追踪唯一并且固定的object ,比如说聊天室的当前用户。

Set和Map有完全相同的API。主要的不同是Set没有set方法,因为它不能存储键值对。剩下的几乎相同。

"use strict";// 构造器
let scotch_collection = new Set();// 基本的 API 方法
// Set.prototype.add (O) :: 和 set 一样,添加一个对象
scotch_collection.add('Lagavulin 18');
scotch_collection.add('The Dalmore');// 你也可以用数组构造一个 set
scotch_collection = new Set(['Lagavulin 18', 'The Dalmore']);// 所有的 set 都有一个 length 属性。这个属性会告诉你 set 里有多少对象
//   用 set 或 map 的时候,一定记住用 size,不用 length
console.log(scotch_collection.size); // 2// Set.prototype.has(O) :: 包含对象 O 返回 true 否则返回 false
console.log(scotch_collection.has('The Dalmore')); // true
console.log(scotch_collection.has('Glenfiddich 18')); // false// Set.prototype.delete(O) :: 删除 set 中的 O 对象,成功返回 true,不存在返回 false
scotch_collection.delete('The Dalmore'); // true -- break my heart// Set.prototype.clear() :: 删除 set 中的所有对象
scotch_collection.clear();
console.log( scotch_collection ); // Set {} -- long night./* 迭代方法* Set 提供了多种方法遍历*  重新设置值,继续探索 */
scotch_collection.add('Lagavulin 18');
scotch_collection.add('Glenfiddich 18');/* Set.prototype.forEach(callback[, thisArg]) :: 执行一个函数,回调函数*  set 里在每个的键值对。 You can set the value of 'this' inside *  the callback by passing a thisArg, but that's optional and seldom necessary. */
scotch_collection.forEach(function (scotch) {console.log(`Excuse me while I sip this ${scotch}.`);
});// Set.prototype.values() :: 返回 set 中的所有值
let scotch_names = scotch_collection.values();
for (let name of scotch_names) {console.log(`I just drank ${name} . . . I think.`);
}// Set.prototype.keys() ::  对 set 来说,和 Set.prototype.values() 方法一致
scotch_names = scotch_collection.keys();
for (let name of scotch_names) {console.log(`I just drank ${name} . . . I think.`);
}/* Set.prototype.entries() :: 返回 map 的所有键值对,提供一个包含两个元素的数组 *   这有点多余,但是这种方法可以保留 map API 的可操作性*    */
const entries = scotch_collection.entries();
for (let entry of entries) {console.log(`I got some ${entry[0]} in my cup and more ${entry[1]} in my flask!`);
}

WeakSet

WeakSet相对于Set就像WeakMap相对于 Map :

  1. 在WeakSet里object的引用是弱类型的。

  2. WeakSet没有property属性。

  3. 不能遍历WeakSet。

Weak set的用例并不多,但是这儿有一些Domenic Denicola称呼它们为“perfect for branding” – 意思就是标记一个对象以满足其他需求。

这儿是他给的例子:

/* 下面这个例子来自 Weakset 使用案例的邮件讨论 *    邮件的内容和讨论的其余部分在这儿:*      https://mail.mozilla.org/pipermail/es-discuss/2015-June/043027.html*/const foos = new WeakSet();class Foo {constructor() {foos.add(this);}method() {if (!foos.has(this)) {throw new TypeError("Foo.prototype.method called on an incompatible object!");}}
}

这是一个轻量科学的方法防止大家在一个没有被Foo构造出的object上使用method。

使用的WeakSet的优势是允许foo里的object使用完后被垃圾回收。

总结

这篇文章里,我们已经了解了ES2015带来的一些好处,从string的便捷方法和模板变量到适当的Map和Set实现。

String方法和模板字符串易于上手。同时你很快也就不用到处用weak set了,我认为你很快就会喜欢上Set和Map。

整理转载:https://github.com/xitu/gold-miner

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 详解1000+项目数据分析出来的10大JavaScript错误

    译者按&#xff1a; null/undefined引发的错误在10大错误中比例很高。而它们很可能导致严重问题&#xff0c;所以要重视起来。 原文: Top 10 JavaScript errors from 1000 projects (and how to avoid them) 译者: Fundebug 为了保证可读性&#xff0c;本文采用意译而非直译。另…...

    2024/3/16 2:38:31
  2. 整理面经·JS·Vue·ES6

    JS方面 1.浏览器的缓存机制&#xff08;强缓存和协商缓存&#xff09; 答&#xff1a;浏览器在加载资源的时候会根据这个资源的一些http header 判断是否命中强缓存 如果命中 那个浏览器将不会去请求服务器&#xff0c;而是直接从缓存中加载这个资源。如果强缓存没有命中 浏览…...

    2024/3/16 2:38:29
  3. 埋线双眼皮少二针

    ...

    2024/3/16 2:38:29
  4. 埋线双眼皮容易失败吗

    ...

    2024/3/16 2:38:27
  5. 埋线双眼皮全切双眼皮多久恢复

    ...

    2024/3/28 20:04:13
  6. 埋线双眼皮取线麻烦吗

    ...

    2024/3/13 14:34:37
  7. 埋线双眼皮前的准备

    ...

    2024/3/13 14:34:35
  8. 10个Node.js最佳实践:Node Gurus的启示

    10个Node.js最佳实践&#xff1a;来自Node Gurus的启示是由来宾作者Azat Mardan撰写的。 SitePoint来宾帖子旨在为您带来来自Web社区知名作家和演讲者的引人入胜的内容。 在我上一篇文章《 2017年成为更好的Node Developer的十个技巧》中 &#xff0c;我介绍了今天可以应用于代…...

    2024/3/13 14:34:34
  9. 工欲善其事,必先利其器——Web开发的10大IDE

    工欲善其事&#xff0c;必先利其器——Web开发的10大IDE一、Visual Studio Code二、PhpStorm三、Atom四、Pycharm五、NetBeans六、WebStorm七、Sublime Text八、Brackets九、IntelliJ IDEA十、HBuilderX集成开发环境 (IDE) 是编码时极其重要的工具。在当今的市场上&#xff0c;…...

    2024/3/16 2:38:27
  10. Node.js 源码调试

    任何信息的价值都有时效性和适用性&#xff0c;本文写时 Node.js 的最新发行版是 v11.14.0&#xff0c;LTS 是 v10.15.3&#xff0c;文中出现的源码均来自 tag: v11.14.0。使用的电脑环境是&#xff1a;macOs 10.14.2。 前言 Node.js is a JavaScript runtime built on Chrome’…...

    2024/3/16 2:38:25
  11. 全栈工程师-史上最强VSCODE插件-提高开发效率

    当你点进来的时候 &#xff0c;你可能是被标题吸引进来的&#xff0c;也有可能是 偶然间&#xff0c;看到的&#xff0c;首先恭喜你&#xff0c;已经准备好向全栈开发工程师靠近 &#xff0c;那我们不说废话&#xff0c;直接开始&#xff0c;咱们先从安装步骤开始讲起 &#xf…...

    2024/3/16 2:38:24
  12. TypeScript的简介与安装和调试

    什么是TypeScript&#xff1f; TypeScript是ES6的超集&#xff0c;所有的ES6代码都是完全有效且可编译的TypeScript。当前版本的JavaScript其实是ECMAScript5&#xff08;ES5&#xff09;&#xff0c;下一代JS标准则是ES6。如果你想要学习更多ES6知识&#xff0c;你可以去阮一…...

    2024/3/16 2:38:23
  13. 埋线双眼皮七天效果图

    ...

    2024/3/16 2:38:24
  14. 埋线双眼皮年龄限制

    ...

    2024/3/16 2:38:23
  15. 埋线双眼皮年龄

    ...

    2024/3/16 2:38:21
  16. 埋线双眼皮能做全切吗

    ...

    2024/3/16 2:38:22
  17. 埋线双眼皮能坚持多久多久

    ...

    2024/3/28 21:03:14
  18. 前端oss上传接口_前端高级进阶:前端和运维之间的羁(ji)绊(qing))

    前端一说起刀耕火种&#xff0c;那肯定紧随着前端工程化这一话题。随着 react/vue/angular&#xff0c;es6&#xff0c;webpack&#xff0c;babel&#xff0c;typescript 以及 node 的发展&#xff0c;前端已经在逐渐替代过去 script 引 cdn 开发的方式了&#xff0c;掀起了工程…...

    2024/3/16 2:38:19
  19. mysql 的触发器详解

    我们有些mysql语句在执行的时候自动执行另外一个语句,需要用到触发器 mysql 的触发器一共分为几种 1,insert 插入触发 2,delete 删除触发 3,update 触发 创建触发器要注意的信息 1,唯一的触发器名 2,触发器关联的表 3,触发器应该响应的活动 4,触发器什么时候执行(只有…...

    2024/3/16 2:38:19
  20. 上传文件到七牛后返回hash/key/persistentId值, 如何获取文件的url访问地址?

    在javascript(或其他服务端语言), 往7牛官方上传地址(http://upload.qiniu.com/), 提交上传后, 返回值大概如下: {"hash":"Fn1Fxzactt76AaJdn9F4XQAkBkmp","key":"e12c391016.mp4","persistentId":"z0.5b5ed2c838b9f3…...

    2024/3/16 2:38:16

最新文章

  1. 使用shell管理和配置文件系统(4)

    1、请创建用户user1,user2&#xff0c;并给他们设置明文密码为redhat 在Linux系统中&#xff0c;出于安全考虑&#xff0c;通常不建议使用明文密码&#xff0c;而是使用加密的密码。然而&#xff0c;为了回答你的问题&#xff0c;我可以指导你如何创建用户并设置密码&#xff…...

    2024/3/29 7:59:34
  2. 梯度消失和梯度爆炸的一些处理方法

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

    2024/3/20 10:50:27
  3. 记一次 .NET某防伪验证系统 崩溃分析

    一&#xff1a;背景 1. 讲故事 昨晚给训练营里面的一位朋友分析了一个程序崩溃的故障&#xff0c;因为看小伙子昨天在群里问了一天也没搞定&#xff0c;干脆自己亲自上阵吧&#xff0c;抓取的dump也是我极力推荐的用 procdump 注册 AEDebug 的方式&#xff0c;省去了很多沟通…...

    2024/3/29 7:56:11
  4. 00000基础搭建vue+flask前后端分离项目

    我完全是参考的这个vue3flask前后端分离环境速建_flask vue3-CSDN博客 安装了node_js&#xff08;添加了环境变量&#xff09; 环境变量 把原来的镜像源换成了淘宝镜像源 npm config set registry https://registry.npmmirror.com/ 查看版本证明安装成功 npm - v 安装npm i…...

    2024/3/28 21:15:05
  5. 【Linux】Linux基本开发工具(yum) (vi/vim)的使用

    本文章内容&#xff1a; 学习yum工具&#xff0c;进行软件安装掌握vim编辑器使用 Linux 软件包管理器 yum 什么是软件包 在Linux下安装软件, 一个通常的办法是下载到程序的源代码, 并进行编译, 得到可执行程序.但是这样太麻烦了, 于是有些人把一些常用的软件提前编译好, 做成…...

    2024/3/28 18:13:09
  6. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/3/27 10:21:24
  7. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/3/24 20:11:25
  8. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/3/29 2:45:46
  9. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/3/24 20:11:23
  10. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/3/29 5:19:52
  11. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/3/28 17:01:12
  12. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/3/24 5:55:47
  13. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/3/29 1:13:26
  14. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/3/26 23:04:51
  15. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/3/29 7:41:19
  16. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/3/24 20:11:18
  17. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/3/28 9:10:53
  18. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/3/29 0:49:46
  19. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/3/24 20:11:15
  20. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/3/27 7:12:50
  21. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/3/24 20:11:13
  22. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/3/26 11:21:23
  23. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/3/28 18:26:34
  24. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/3/28 12:42:28
  25. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/3/28 20:09:10
  26. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

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

    2022/11/19 21:17:18
  27. 错误使用 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
  28. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

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

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

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

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

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

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

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

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

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

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

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

    2022/11/19 21:17:10
  34. 电脑桌面一直是清理请关闭计算机,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
  35. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    2022/11/19 21:16:58
  45. 如何在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