JavaScript の基本をテストするための 8 つの質問
JavaScript は、その性質上、私たち全員に愛される楽しい言語です。ブラウザは主に JavaScript が実行される場所であり、この 2 つはサービス内で連携して動作します。 JS には、人々が軽視する傾向があり、時には無視する可能性のある概念がいくつかあります。プロトタイプ、クロージャ、イベント ループなどの概念は、ほとんどの JS 開発者が遠回りしてしまうあいまいな領域の 1 つです。ご存知のとおり、無知は危険であり、間違いを引き起こす可能性があります。
もっと質の高い記事を読みたい場合は、GitHub ブログをクリックしてください。毎年数百の質の高い記事があなたを待っています。
次に、いくつかの質問を見てみましょう。質問について考えてから回答することもできます。
質問 1: ブラウザのコンソールには何が表示されますか?
var a = 10; function foo() { console.log(a); // ?? var a = 20; } foo();
質問 2: var の代わりに let または const を使用した場合、出力は同じですか
var a = 10; function foo() { console.log(a); // ?? let a = 20; } foo();
質問 3: 内容は何ですか「newArray」要素?
var array = []; for(var i = 0; i <3; i++) { array.push(() => i); } var newArray = array.map(el => el()); console.log(newArray); // ??
質問 4: ブラウザ コンソールで「foo」関数を実行すると、スタック オーバーフロー エラーが発生しますか?
function foo() { setTimeout(foo, 0); // 是否存在堆栈溢出错误? };
質問 5: コンソールで次の関数を実行した場合、ページ (タブ) の UI は引き続き応答しますか?
function foo() { return Promise.resolve().then(foo); };
質問 6: 型エラーを発生させずに、次のステートメントのスプレッド操作を何とか使用できますか
var obj = { x: 1, y: 2, z: 3 }; [...obj]; // TypeError
質問 7: 次のコード スニペットを実行すると、コンソールに何が表示されますか?
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); }
質問 8: xGetter() はどのような値を出力しますか?
var x = 10; var foo = { x: 90, getX: function() { return this.x; } }; foo.getX(); // prints 90 var xGetter = foo.getX; xGetter(); // prints ??
回答
それでは、各質問に最初から最後まで答えてみましょう。これらの動作をわかりやすく説明し、いくつかの参考文献を示しながら簡単に説明します。
質問 1: 未定義
分析:
var
キーワードを使用して宣言された変数は JavaScript でプロモートされ、代入されます。メモリ内の値 未定義
。ただし、初期化は変数に値を代入した場所で行われます。さらに、var
で宣言された変数は関数スコープですが、let
と const
はブロックスコープです。したがって、プロセスは次のようになります。
var a = 10; // 全局使用域 function foo() { // var a 的声明将被提升到到函数的顶部。 // 比如:var a console.log(a); // 打印 undefined // 实际初始化值20只发生在这里 var a = 20; // local scope }
質問 2: ReferenceError: a unknown
。
分析:
let
および const
宣言により、変数のスコープを、それが使用されているブロック、ステートメント、または式に制限できます。 .モード。 var
とは異なり、これらの変数は昇格されず、いわゆる 一時デッド ゾーン (TDZ) があります。 TDZ 内のこれらの変数にアクセスしようとすると、ReferenceError
が発生します。これらの変数は、実行が宣言に到達した場合にのみアクセスできるためです。
var a = 10; // 全局使用域 function foo() { // TDZ 开始 // 创建了未初始化的'a' console.log(a); // ReferenceError // TDZ结束,'a'仅在此处初始化,值为20 let a = 20; }
質問 3: [3, 3, 3]
分析:
for ループ内の ヘッダーで
var
キーワードを使用して変数を宣言すると、その変数に対する単一のバインディング (ストレージ スペース) が作成されます。閉鎖について詳しくはこちらをご覧ください。もう一度 for ループを見てみましょう。
// 误解作用域:认为存在块级作用域 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]
let
を使用してブロックレベルのスコープを持つ変数を宣言すると、ループの反復ごとに新しいバインディングが作成されます。
// 使用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]
この問題を解決するもう 1 つの方法は、クロージャを使用することです。
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]
質問 4: オーバーフローなし
分析:
JavaScript 同時実行モデルは、「イベント ループ」に基づいています。 「ブラウザは JS の本拠地である」と言うとき、私が実際に言いたいのは、ブラウザが JS コードを実行するためのランタイム環境を提供するということです。
ブラウザの主なコンポーネントには、コール スタック、イベント ループ、タスク キュー、Web APIが含まれます。 setTimeout
、setInterval
、Promise
などのグローバル関数は JavaScript の一部ではなく、Web API の一部です。
JS 呼び出しスタックは後入れ先出し (LIFO) です。エンジンは一度に 1 つの関数をスタックから取り出し、コードを上から下に順番に実行します。 setTimeout
のような非同期コードに遭遇すると、それを Web API
に渡します (矢印 1)。したがって、イベントがトリガーされるたびに、callback
がタスク キューに送信されます (矢印 2)。
イベント ループタスク キューを継続的に監視し、キューに入れられた順序でコールバックを一度に 1 つずつ処理します。 コール スタックが空の場合は常に、イベント ループがコールバックを取得し、処理のためにコールバックを スタック(矢印 3)に置きます。コール スタックが空でない場合、 イベント ループはコールバックをスタックにプッシュしないことに注意してください。
この知識を踏まえて、前述の質問に答えてみましょう:步骤
- 调用
foo()
会将foo
函数放入调用堆栈(call stack)。 - 在处理内部代码时,JS引擎遇到
setTimeout
。 - 然后将
foo
回调函数传递给WebAPIs(箭头1)并从函数返回,调用堆栈再次为空 - 计时器被设置为0,因此
foo
将被发送到任务队列(箭头2)。 - 由于调用堆栈是空的,事件循环将选择
foo
回调并将其推入调用堆栈进行处理。 - 进程再次重复,堆栈不会溢出。
问题5 : 不会响应
解析:
大多数时候,开发人员假设在事件循环
在底层来看,JavaScript中有宏任务和微任务。setTimeout
回调是宏任务,而Promise
回调是微任务。
主要的区别在于他们的执行方式。宏任务在单个循环周期中一次一个地推入堆栈,但是微任务队列总是在执行后返回到事件循环之前清空。因此,如果你以处理条目的速度向这个队列添加条目,那么你就永远在处理微任务。只有当微任务队列为空时,事件循环才会重新渲染页面、
现在,当你在控制台中运行以下代码段
function foo() { return Promise.resolve().then(foo); };
每次调用'foo
'都会继续在微任务队列上添加另一个'foo
'回调,因此事件循环无法继续处理其他事件(滚动,单击等),直到该队列完全清空为止。 因此,它会阻止渲染。
问题6 : 会导致TypeError错误
解析:
展开语法 和 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]
问题7 : a, b, c
解析:
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
问题8 : 10
解析:
在全局范围内初始化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教程》
以上がJavaScript の基本をテストするための 8 つの質問の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

Video Face Swap
完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック









HTML の表の境界線に関するガイド。ここでは、HTML でのテーブルの境界線の例を示しながら、テーブル境界線を定義する複数の方法について説明します。

これは、HTML でのネストされたテーブルのガイドです。ここでは、テーブル内にテーブルを作成する方法をそれぞれの例とともに説明します。

HTML マージン左のガイド。ここでは、HTML margin-left の概要とその例、およびそのコード実装について説明します。

HTML テーブル レイアウトのガイド。ここでは、HTML テーブル レイアウトの値と例および出力について詳しく説明します。

HTML 入力プレースホルダーのガイド。ここでは、コードと出力とともに HTML 入力プレースホルダーの例について説明します。

HTML でのテキストの移動に関するガイド。ここでは、概要、マーキー タグが構文でどのように機能するか、および実装例について説明します。

HTML オンクリック ボタンのガイド。ここでは、それらの紹介、動作、例、およびさまざまなイベントでの onclick イベントについてそれぞれ説明します。
