JavaScript ist eine unterhaltsame Sprache, die wir alle aufgrund ihrer Natur lieben. Der Browser ist der Ort, an dem JavaScript hauptsächlich ausgeführt wird, und beide arbeiten in unseren Diensten zusammen. JS hat einige Konzepte, die die Leute eher auf die leichte Schulter nehmen und manchmal ignorieren. Konzepte wie Prototypen, Schließungen und Ereignisschleifen gehören immer noch zu den obskuren Bereichen, über die die meisten JS-Entwickler einen Umweg machen. Wie wir wissen, ist Unwissenheit eine gefährliche Sache und kann zu Fehlern führen.
Wenn Sie weitere hochwertige Artikel lesen möchten, klicken Sie bitte auf den GitHub-Blog. Hunderte hochwertige Artikel warten jedes Jahr auf Sie!
Als nächstes schauen wir uns ein paar Fragen an. Sie können auch versuchen, darüber nachzudenken und sie dann zu beantworten.
var a = 10; function foo() { console.log(a); // ?? var a = 20; } foo();
var a = 10; function foo() { console.log(a); // ?? let a = 20; } foo();
var array = []; for(var i = 0; i <3; i++) { array.push(() => i); } var newArray = array.map(el => el()); console.log(newArray); // ??
function foo() { setTimeout(foo, 0); // 是否存在堆栈溢出错误? };
function foo() { return Promise.resolve().then(foo); };
var obj = { x: 1, y: 2, z: 3 }; [...obj]; // TypeError
var obj = { a: 1, b: 2 }; Object.setPrototypeOf(obj, {c: 3}); Object.defineProperty(obj, 'd', { value: 4, enumerable: false }); // what properties will be printed when we run the for-in loop? for(let prop in obj) { console.log(prop); }
var x = 10; var foo = { x: 90, getX: function() { return this.x; } }; foo.getX(); // prints 90 var xGetter = foo.getX; xGetter(); // prints ??
Beantworten wir nun jede Frage von Anfang bis Ende. Ich werde Ihnen eine kurze Erklärung geben, während ich versuche, diese Verhaltensweisen zu entmystifizieren und einige Referenzen anzugeben.
undefined
Variablen, die mit dem Schlüsselwort var
deklariert wurden, werden in JavaScript hochgestuft und der Wert wird im Speicher zugewiesenundefined
. Die Initialisierung erfolgt jedoch genau dort, wo Sie der Variablen einen Wert zuweisen. Darüber hinaus haben die mit var
deklarierten Variablen einen Funktionsbereich, während let
und const
einen Blockbereich haben. So sieht der Prozess also aus:
var a = 10; // 全局使用域 function foo() { // var a 的声明将被提升到到函数的顶部。 // 比如:var a console.log(a); // 打印 undefined // 实际初始化值20只发生在这里 var a = 20; // local scope }
ReferenceError:a undefined
. let
- und const
-Deklarationen ermöglichen, dass der Gültigkeitsbereich einer Variablen auf den Block, die Anweisung oder den Ausdruck beschränkt wird, in dem sie verwendet wird. Im Gegensatz zu var
werden diese Variablen nicht hochgestuft und haben eine sogenannte Temporary Dead Zone (TDZ) . Der Versuch, auf diese Variablen in TDZ zuzugreifen, löst ReferenceError
aus, da auf sie nur zugegriffen werden kann, wenn die Ausführung die Deklaration erreicht.
var a = 10; // 全局使用域 function foo() { // TDZ 开始 // 创建了未初始化的'a' console.log(a); // ReferenceError // TDZ结束,'a'仅在此处初始化,值为20 let a = 20; }
[3, 3, 3]
Deklarieren einer Variablen mit dem Schlüsselwort for
am Anfang der var
-Schleife Diese Variable erstellt eine einzelne Bindung (Speicherplatz). Lesen Sie mehr über Schließungen. Schauen wir uns noch einmal die for-Schleife an.
// 误解作用域:认为存在块级作用域 var array = []; for (var i = 0; i < 3; i++) { // 三个箭头函数体中的每个`'i'`都指向相同的绑定, // 这就是为什么它们在循环结束时返回相同的值'3'。 array.push(() => i); } var newArray = array.map(el => el()); console.log(newArray); // [3, 3, 3]
Wenn Sie mit let
eine Variable mit Blockbereich deklarieren, wird für jede Schleifeniteration eine neue Bindung erstellt.
// 使用ES6块级作用域 var array = []; for (let i = 0; i < 3; i++) { // 这一次,每个'i'指的是一个新的的绑定,并保留当前的值。 // 因此,每个箭头函数返回一个不同的值。 array.push(() => i); } var newArray = array.map(el => el()); console.log(newArray); // [0, 1, 2]
Eine andere Möglichkeit, dieses Problem zu lösen, ist die Verwendung von Verschlüssen.
let array = []; for (var i = 0; i < 3; i++) { array[i] = (function(x) { return function() { return x; }; })(i); } const newArray = array.map(el => el()); console.log(newArray); // [0, 1, 2]
Das JavaScript-Parallelitätsmodell basiert auf der „Ereignisschleife“. Wenn wir sagen „Der Browser ist die Heimat von JS“, meine ich eigentlich, dass der Browser die Laufzeitumgebung für die Ausführung unseres JS-Codes bereitstellt.
Zu den Hauptkomponenten des Browsers gehören Call Stack, Event Loop, Task Queue und Web API. Globale Funktionen wie setTimeout
, setInterval
und Promise
sind nicht Teil von JavaScript, sondern Teil der Web-API.
Der JS-Aufrufstapel ist Last-In-First-Out (LIFO). Die Engine nimmt jeweils eine Funktion vom Stapel und führt den Code sequentiell von oben nach unten aus. Immer wenn es auf asynchronen Code wie setTimeout
trifft, übergibt es ihn an Web API
(Pfeil 1). Wenn also das Ereignis ausgelöst wird, wird callback
an die Aufgabenwarteschlange gesendet (Pfeil 2).
Ereignisschleife überwacht ständig die Aufgabenwarteschlange und verarbeitet Rückrufe einzeln in der Reihenfolge, in der sie in der Warteschlange stehen. Immer wenn der Aufrufstapel leer ist, ruft die Ereignisschleife den Rückruf ab und legt ihn zur Verarbeitung im Stack (Pfeil 3) ab. Denken Sie daran: Wenn der Aufrufstapel nicht leer ist, wird die Ereignisschleife keine Rückrufe auf den Stapel verschieben .
Mit diesem Wissen bewaffnet, beantworten wir nun die zuvor gestellte Frage:
foo()
会将foo
函数放入调用堆栈(call stack)。setTimeout
。foo
回调函数传递给WebAPIs(箭头1)并从函数返回,调用堆栈再次为空foo
将被发送到任务队列foo
回调并将其推入调用堆栈进行处理。大多数时候,开发人员假设在事件循环
在底层来看,JavaScript中有宏任务和微任务。setTimeout
回调是宏任务,而Promise
回调是微任务。
主要的区别在于他们的执行方式。宏任务在单个循环周期中一次一个地推入堆栈,但是微任务队列总是在执行后返回到事件循环之前清空。因此,如果你以处理条目的速度向这个队列添加条目,那么你就永远在处理微任务。只有当微任务队列为空时,事件循环才会重新渲染页面、
现在,当你在控制台中运行以下代码段
function foo() { return Promise.resolve().then(foo); };
每次调用'foo
'都会继续在微任务队列上添加另一个'foo
'回调,因此事件循环无法继续处理其他事件(滚动,单击等),直到该队列完全清空为止。 因此,它会阻止渲染。
展开语法 和 for-of 语句遍历iterable
对象定义要遍历的数据。Array
或Map
是具有默认迭代行为的内置迭代器。对象不是可迭代的,但是可以通过使用iterable和iterator协议使它们可迭代。
在Mozilla文档中,如果一个对象实现了@@iterator
方法,那么它就是可迭代的,这意味着这个对象(或者它原型链上的一个对象)必须有一个带有@@iterator
键的属性,这个键可以通过常量Symbol.iterator
获得。
上述语句可能看起来有点冗长,但是下面的示例将更有意义:
var obj = { x: 1, y: 2, z: 3 }; obj[Symbol.iterator] = function() { // iterator 是一个具有 next 方法的对象, // 它的返回至少有一个对象 // 两个属性:value&done。 // 返回一个 iterator 对象 return { next: function() { if (this._countDown === 3) { const lastValue = this._countDown; return { value: this._countDown, done: true }; } this._countDown = this._countDown + 1; return { value: this._countDown, done: false }; }, _countDown: 0 }; }; [...obj]; // 打印 [1, 2, 3]
还可以使用 generator 函数来定制对象的迭代行为:
var obj = {x:1, y:2, z: 3} obj[Symbol.iterator] = function*() { yield 1; yield 2; yield 3; } [...obj]; // 打印 [1, 2, 3]
for-in
循环遍历对象本身的可枚举属性以及对象从其原型继承的属性。 可枚举属性是可以在for-in
循环期间包含和访问的属性。
var obj = { a: 1, b: 2 }; var descriptor = Object.getOwnPropertyDescriptor(obj, "a"); console.log(descriptor.enumerable); // true console.log(descriptor); // { value: 1, writable: true, enumerable: true, configurable: true }
现在你已经掌握了这些知识,应该很容易理解为什么我们的代码要打印这些特定的属性
var obj = { a: 1, b: 2 }; //a,b 都是 enumerables 属性 // 将{c:3}设置为'obj'的原型,并且我们知道 // for-in 循环也迭代 obj 继承的属性 // 从它的原型,'c'也可以被访问。 Object.setPrototypeOf(obj, { c: 3 }); // 我们在'obj'中定义了另外一个属性'd',但是 // 将'enumerable'设置为false。 这意味着'd'将被忽略。 Object.defineProperty(obj, "d", { value: 4, enumerable: false }); for (let prop in obj) { console.log(prop); } // 打印 // a // b // c
在全局范围内初始化x
时,它成为window对象的属性(不是严格的模式)。看看下面的代码:
var x = 10; // global scope var foo = { x: 90, getX: function() { return this.x; } }; foo.getX(); // prints 90 let xGetter = foo.getX; xGetter(); // prints 10
咱们可以断言:
window.x === 10; // true
this
始终指向调用方法的对象。因此,在foo.getx()
的例子中,它指向foo
对象,返回90
的值。而在xGetter()
的情况下,this
指向 window对象, 返回 window 中的x
的值,即10
。
要获取 foo.x
的值,可以通过使用Function.prototype.bind
将this
的值绑定到foo
对象来创建新函数。
let getFooX = foo.getX.bind(foo); getFooX(); // 90
就这样! 如果你的所有答案都正确,那么干漂亮。 咱们都是通过犯错来学习的。 这一切都是为了了解背后的“原因”。
推荐教程:《JS教程》
Das obige ist der detaillierte Inhalt von8 Fragen zum Testen Ihrer JavaScript-Grundlagen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!