2023 年のフロントエンド面接の質問と回答

JS
データ型
インタビュアー: JavaScript の基本データ型と参照データ型とは何ですか? そして、それぞれのデータ型はどのように保存されるのでしょうか? ⭐⭐⭐⭐⭐

回答:
基本的なデータ型は次のとおりです。

数値
文字列
ブール値
Null
未定義
シンボル (ES6 の新しいデータ型)
bigInt
参照データ型を総称してオブジェクト型と呼びます。

オブジェクト
配列
日付
関数
RegExp
基本データ型のデータは直接スタックに格納されますが、参照データ型のデータはヒープに格納され、データの参照アドレスがスタックに保存されます。ヒープ メモリ内のオブジェクトを、対応するデータに関連付けて、すぐに見つけられるようにします。

ちなみにスタックメモリは自動で割り当てられます。ヒープ メモリは動的に割り当てられ、自動的に解放されません。したがって、オブジェクトの使用を終了するたびに、オブジェクトを null に設定して、無駄なメモリの消費を減らします。

型変換
インタビュアー: なぜ JS では 0.2+0.1>0.3 なのでしょうか?⭐⭐⭐⭐

答え:

JS では、浮動小数点数は 64 ビットの固定長で表され、そのうち 1 ビットは符号ビットを表し、11 ビットは指数ビットを表すために使用され、残りの 52 ビットは仮数ビットです。仮数ビット。

0.1 から 2 進数への変換は無限ループ数 0.0001100110011001100...(1100 ループ)

10 進数から 2 進数に変換する方法: https://jingyan.baidu.com/article/425e69e6e93ca9be15fc1626.html 10
進数から 2 進数に変換する方法は、整数の場合とは異なることを知っておく必要があります。見て。

仮数部は52ビットしか保存できないので精度が落ちますが、メモリに保存してから取り出して10進数に変換すると、元の0.1ではなく0.100000000000000005551115123126になってしまい、なぜ02になるのでしょうか? +0.1 の理由は、

// 演算を実行する前に 0.1 と 0.2 をバイナリに変換します
0.00011001100110011001100110011001100110011001100110011010 +
0.001100110011001100110011001100110011001 1 001100110011010 =
0.0100110011001100110011001100110011001100110011001100111

// 10 進数に変換すると、ちょうど 0.30000000000000004 になります。

インタビュアー: では、なぜ 0.2+0.3=0.5 なのでしょうか?⭐⭐⭐⭐


// 0.001100110011001100110011001100110011001100110011001101 +
0.010011001100110011001100110011001100110011を計算する前に、0.2 と 0.3 をバイナリに変換します。
0011001101 = 0.1000000000000000000000000000000000000000000000000001 //仮数部が 52 桁を超えています

//実際の値は 52 ビットの仮数のみを取り、これは
0.1000000000000000000000000000000000000000000000000000 //0.5になります。

答え: 0.2 と 0.3 はそれぞれ計算のためにバイナリに変換されます。メモリ内では、それらの仮数は 52 ビットに等しく、それらの合計は 52 ビットより大きくなければなりません。そして、それらの加算ではたまたま最初の 52 個の仮数がすべて 0 になります。インターセプトの場合、正確には 0.10000000000000000000000000000000000000000000000000、つまり 0.5 です。

インタビュアー: 0.1 はもはや 0.1 ではないのに、console.log(0.1) のときはなぜ 0.1 のままなのでしょうか?⭐⭐⭐

回答: console.log では、バイナリは 10 進数に変換され、10 進数は文字列形式に変換されます。変換プロセス中に近似が行われるため、出力されるのは近似文字列です。

インタビュアー: データ型を決定するにはいくつかの方法があります ⭐⭐⭐⭐⭐

答え:

の種類

欠点: typeof null の値は Object であり、null なのか Object
インスタンスオブなのかを区別することは不可能です。


欠点: ターゲット オブジェクトコンストラクターのプロトタイプ チェーン上にオブジェクトが存在するかどうかしか判断できません。

Object.prototype.toString.call()

最も優れた基本的な型検出メソッドの 1 つである Object.prototype.toString.call() は、null、string、

ブール値、数値、未定義、配列、関数、オブジェクト、日付、数学データ型。

欠点: あれこれのインスタンスに細分化できない

// -----------------------------------------typeof
typeof undefined // 'undefined' 
typeof '10' // 'String' 
typeof 10 // 'Number' 
typeof false // 'Boolean' 
typeof Symbol() // 'Symbol' 
typeof Function // ‘function' 
typeof null // ‘Object’ 
typeof [] // 'Object' 
typeof {} // 'Object'


// -----------------------------------------instanceof
function Foo() { }
var f1 = new Foo();
var d = new Number(1)


console.log(f1 instanceof Foo);// true
console.log(d instanceof Number); //true
console.log(123 instanceof Number); //false   -->不能判断字面量的基本数据类型


// -----------------------------------------constructor
var d = new Number(1)
var e = 1
function fn() {
  console.log("ming");
}
var date = new Date();
var arr = [1, 2, 3];
var reg = /[hbc]at/gi;

console.log(e.constructor);//ƒ Number() { [native code] }
console.log(e.constructor.name);//Number
console.log(fn.constructor.name) // Function 
console.log(date.constructor.name)// Date 
console.log(arr.constructor.name) // Array 
console.log(reg.constructor.name) // RegExp




//-----------------------------------------Object.prototype.toString.call()
console.log(Object.prototype.toString.call(undefined)); // "[object Undefined]" 
console.log(Object.prototype.toString.call(null)); // "[object Null]" 
console.log(Object.prototype.toString.call(123)); // "[object Number]" 
console.log(Object.prototype.toString.call("abc")); // "[object String]" 
console.log(Object.prototype.toString.call(true)); // "[object Boolean]" 


function fn() {
  console.log("ming");
}
var date = new Date();
var arr = [1, 2, 3];
var reg = /[hbc]at/gi;
console.log(Object.prototype.toString.call(fn));// "[object Function]" 
console.log(Object.prototype.toString.call(date));// "[object Date]" 
console.log(Object.prototype.toString.call(arr)); // "[object Array]"
console.log(Object.prototype.toString.call(reg));// "[object RegExp]"

原則のインスタンス⭐⭐⭐⭐⭐

実際には、instanceof の原理は、
ターゲット オブジェクトのプロトタイプ チェーン関数を見つけることです。 myInstance(L, R) {//L は、instanceof の左側を表し、R は右側を表します。
var RP = R.prototype
var LP = L. proto
while (true) { if (LP = = null) { return false } if(LP == RP) { return true } LP = LP.proto } } console.log(myInstance({},Object));インタビュアー: なぜですかnull の型は Object⭐⭐⭐⭐










答え:

JavaScriptでは異なるオブジェクトをバイナリで格納しているため、バイナリ数の上3桁がすべて0であればシステムがオブジェクト型と判断し、nullのバイナリ数がすべて0であれば当然オブジェクト型と判断しますオブジェクトになること。

このバグは JavaScript の最初のバージョンに残されています。他の 5 つの識別ビットを拡張してみましょう。

000 オブジェクト
1 整数型
010 倍精度型
100 文字列
110 ブール型

インタビュアー:そして=違いは何ですか⭐⭐⭐⭐⭐

答え:

=== は厳密な意味での等価性であり、両側のデータ型と値のサイズを比較します。

データ型が異なる場合は false を返します。
データ型は同じですが、値のサイズが異なります。false を返す
== は、厳密な意味では等しくありません。

両側が同じタイプなので、サイズを比較してください

両面のタイプが異なりますので、以下の表で比較してください。

Null == 未定義 ->true
String == Number ->最初に String を Number に変換し、次にサイズを比較します
Boolean == Number ->次に Boolean を Number に変換し、次に
Object == String、Number、Symbol -> Object Convert toプリミティブ型

面接官: 手書きの電話、応募、バインド⭐⭐⭐⭐⭐

答え:

call と apply の実装の主なアイデアは次のとおりです:
それが関数呼び出しであるかどうかを判断します。関数呼び出しでない場合は、例外がスローされます。
新しいオブジェクト (コンテキスト) を通じて関数を呼び出します
。コンテキストを取得し、呼び出す必要がある関数として設定します。
呼び出しが完了したら、fn を削除します。
バインド実装のアイデアにより、
関数呼び出しであるかどうかが決定されます。関数呼び出しでない場合は、例外がスローされます。
戻り
関数関数の呼び出し方法を決定します。new が出た場合は
空のオブジェクトが返されます。ただし、インスタンスの __proto__ は _this のプロトタイプを指し、 Array.prototype
をカリー化する関数を完了します
。slice.call()
呼び出し:

Function.prototype.myCall = function (context) {
  // 先判断调用myCall是不是一个函数
  // 这里的this就是调用myCall的
  if (typeof this !== 'function') {
    throw new TypeError("Not a Function")
  }

  // 不传参数默认为window
  context = context || window

  // 保存this
  context.fn = this

  // 保存参数
  let args = Array.from(arguments).slice(1)   //Array.from 把伪数组对象转为数组

  // 调用函数
  let result = context.fn(...args)

  delete context.fn

  return result

}

適用する

Function.prototype.myApply = function (context) {
// 判断this是不是函数
if (typeof this !== “function”) {
throw new TypeError(“Not a Function”)
}

  let result

  // 默认是window
  context = context || window

  // 保存this
  context.fn = this

  // 是否传参
  if (arguments[1]) {
    result = context.fn(...arguments[1])
  } else {
    result = context.fn()
  }
  delete context.fn

  return result
}

練る

Function.prototype.myBind = function(context){
  // 判断是否是一个函数
  if(typeof this !== "function") {
    throw new TypeError("Not a Function")
  }
  // 保存调用bind的函数
  const _this = this 
  // 保存参数
  const args = Array.prototype.slice.call(arguments,1)
  // 返回一个函数
  return function F () {
    // 判断是不是new出来的
    if(this instanceof F) {
      // 如果是new出来的
      // 返回一个空对象,且使创建出来的实例的__proto__指向_this的prototype,且完成函数柯里化
      return new _this(...args,...arguments)
    }else{
      // 如果不是new出来的改变this指向,且完成函数柯里化
      return _this.apply(context,args.concat(...arguments))
    }
  } 
}

インタビュアー: リテラルによって作成されたオブジェクトと new によって作成されたオブジェクトの違いは何ですか? new の内部には何が実装されていますか? new⭐⭐⭐⭐⭐を手動で作成します。

答え:

リテラル:

リテラルを使用してオブジェクトを作成すると、よりシンプルで読みやすくなり、
スコープ分析が必要なくなり、高速になります

新しいオブジェクトを作成し
、新しいオブジェクトの __proto__ が元の関数のプロトタイプを指すようにし、
この点を (新しい obj を指すように) 変更して関数を実行します。実行結果は結果として保存され
、実行関数が null または未定義の場合は、前の新しいオブジェクトを返し、そうでない場合は、結果を
手書きで返します。

// 手写一个new
function myNew(fn, ...args) {
  // 创建一个空对象
  let obj = {}
  // 使空对象的隐式原型指向原函数的显式原型
  obj.__proto__ = fn.prototype
  // this指向obj
  let result = fn.apply(obj, args)
  // 返回
  return result instanceof Object ? result : obj
}

インタビュアー: リテラル new によって作成されたオブジェクトと Object.create(null)⭐⭐⭐ によって作成されたオブジェクトの違いは何ですか?

答え:

リテラルおよび new によって作成されたオブジェクトは Object のメソッドとプロパティを継承し、その暗黙的なプロトタイプは Object の明示的なプロトタイプを指します。

Object.create(null) で作成されたオブジェクトのプロトタイプは null なので、プロトタイプチェーンの先頭なので当然 Object のメソッドやプロパティを継承しません。

実行スタックと実行コンテキスト
インタビュアー: スコープとは何ですか、スコープ チェーンとは何ですか? ⭐⭐⭐⭐

答え:

変数や関数の使用可能な範囲を指定することをスコープといいます。
各関数にはスコープチェーンがあります。変数や関数を探すときは、ローカルスコープからグローバルスコープまで検索する必要があります。これらのスコープの集合をスコープといいますチェーンです。
インタビュアー: 実行スタックと実行コンテキストとは何ですか? ⭐⭐⭐⭐

答え:

実行コンテキストは次のように分割されます。

グローバル実行コンテキストは
、グローバル ウィンドウ オブジェクトを作成し、これがウィンドウを指すように規定します。js を実行すると、スタックの一番下にプッシュされ、ブラウザを閉じるとポップアップします。関数実行コンテキスト関数が呼び出されるたびに、新しい関数実行コンテキストが実行の
ために作成されます。コンテキストは作成フェーズと実行フェーズに分かれています。作成フェーズ: 関数環境は変数オブジェクトを作成します: 引数オブジェクト (および値の割り当て)、関数宣言 (および値の割り当て) )、変数宣言 (値を割り当てない)、関数式宣言 (値を割り当てない); これが指すことが決定され、スコープが決定されます 実行フェーズ: 変数の割り当て、関数式の割り当て、変数オブジェクト プログラミングのアクティブ オブジェクトの作成eval 実行コンテキスト。実行スタック:





まずスタックの特性ですが、先入れ後出し、
実行環境に入ると実行コンテキストが作成されてスタックにプッシュされ、プログラムの実行が完了すると実行コンテキストが破棄されスタックが復元されます。弾けた。
スタックの一番下は常にグローバル環境の実行コンテキストであり、スタックの一番上は常に実行関数の実行コンテキストです。グローバル実行コンテキストは
ブラウザを閉じたときにのみ
ポップアップし
ます。 jsクロージャ. こちらの記事がおすすめです: jsのクロージャを徹底理解する

インタビュアー: クロージャとは何ですか? 閉鎖って何をするの?閉鎖申請?⭐⭐⭐⭐⭐

答え:

関数の実行は、内部プライベート変数を外部干渉から保護するプライベート実行コンテキストを形成し、保護と保存の役割を果たします。

効果:

保護
名前の競合を回避します。
保存
循環バインディングによって引き起こされるインデックスの問題を解決
します。 変数は破棄されません。
ガベージ コレクション メカニズムによってリサイクルされないように、関数内で変数を使用できます。
アプリケーション:

デザイン パターンでのシングルトン モード for
ループでの i 操作の保持
アンチシェイクとスロットル
関数のカリー化
欠点

メモリ リークの問題が発生します
プロトタイプとプロトタイプ チェーン
インタビュアー: プロトタイプとは何ですか? プロトタイプチェーンとは何ですか? ⭐⭐⭐⭐⭐を理解する方法

答え:

プロトタイプ: プロトタイプは、暗黙的プロトタイプと明示的プロトタイプに分けられ、各オブジェクトには、自身のコンストラクターの明示的プロトタイプを指す暗黙的プロトタイプがあります。

プロトタイプチェーン: 複数の __proto__ の集合がプロトタイプチェーンになります

すべてのインスタンスの __proto__ は、コンストラクターのプロトタイプを指します。
すべてのプロトタイプはオブジェクトです。当然、その __proto__ は、Object() のプロトタイプを指します。
すべてのコンストラクターの暗黙のプロトタイプは、Function() を指します。表示されるプロトタイプ
の暗黙のプロトタイプオブジェクトは null
継承です
インタビュアー: JS における一般的な継承メソッドは何ですか? そして、それぞれの継承方法の長所と短所。⭐⭐⭐⭐⭐

答え:

プロトタイプ継承、複合継承、寄生複合継承、ES6 拡張

プロトタイプの継承

// ----------------------方法一:原型继承
// 原型继承
// 把父类的实例作为子类的原型
// 缺点:子类的实例共享了父类构造函数的引用属性   不能传参

var person = {
  friends: ["a", "b", "c", "d"]
}

var p1 = Object.create(person)

p1.friends.push("aaa")//缺点:子类的实例共享了父类构造函数的引用属性

console.log(p1);
console.log(person);//缺点:子类的实例共享了父类构造函数的引用属性

構成継承

// ----------------------方法二:组合继承
// 在子函数中运行父函数,但是要利用call把this改变一下,
// 再在子函数的prototype里面new Father() ,使Father的原型中的方法也得到继承,最后改变Son的原型中的constructor

// 缺点:调用了两次父类的构造函数,造成了不必要的消耗,父类方法可以复用
// 优点可传参,不共享父类引用属性
function Father(name) {
  this.name = name
  this.hobby = ["篮球", "足球", "乒乓球"]
}

Father.prototype.getName = function () {
  console.log(this.name);
}

function Son(name, age) {
  Father.call(this, name)
  this.age = age
}

Son.prototype = new Father()
Son.prototype.constructor = Son


var s = new Son("ming", 20)

console.log(s);

寄生組み合わせの継承

// ----------------------方法三:寄生组合继承
function Father(name) {
  this.name = name
  this.hobby = ["篮球", "足球", "乒乓球"]
}

Father.prototype.getName = function () {
  console.log(this.name);
}

function Son(name, age) {
  Father.call(this, name)
  this.age = age
}

Son.prototype = Object.create(Father.prototype)
Son.prototype.constructor = Son

var s2 = new Son("ming", 18)
console.log(s2);

伸ばす

// ----------------------方法四:ES6的extend(寄生组合继承的语法糖)
//     子类只要继承父类,可以不写 constructor ,一旦写了,则在 constructor 中的第一句话
// 必须是 super 。

class Son3 extends Father { // Son.prototype.__proto__ = Father.prototype
  constructor(y) {
    super(200)  // super(200) => Father.call(this,200)
    this.y = y
  }
}

メモリ リーク、ガベージ コレクション メカニズム
インタビュアー: メモリ リークとは何ですか⭐⭐⭐⭐⭐

答え:

メモリ リークとは、使用されなくなったメモリが期限内に解放されず、メモリが使用できなくなることを意味します。これがメモリ リークです。

インタビュアー: なぜメモリリークが起こるのですか⭐⭐⭐⭐⭐

答え:

メモリ リークは、js を介してオブジェクトにアクセスできないことを意味しますが、ガベージ コレクション メカニズムはオブジェクトがまだ参照されているとみなしているため、ガベージ コレクション メカニズムはオブジェクトを解放せず、メモリ ブロックが解放されないことになります。この量が積み重なるとシステムに大きな問題が生じ、システムはますます行き詰まり、崩壊することさえあります。

インタビュアー: ガベージ コレクション メカニズムの戦略は何ですか? ⭐⭐⭐⭐⭐

答え:

マークアンドスイープ
ガベージ コレクション メカニズムは、ルートを取得してそれらにマークを付け、次にそれらのすべての参照にアクセスしてマークを付け、次にこれらのオブジェクトにアクセスしてその参照にマークを付けます...この進行の最後に、マークされていない (到達不能な) ものがある場合は、オブジェクトが見つかった場合は削除に進み、実行環境に入った後は削除できません
参照カウント方法:
変数が宣言され、その変数に参照型の値が代入された場合、値のカウントは +1 値が代入された場合値が別の変数に置き換えられた場合、カウントは +1 になります。値が他の値に置き換えられた場合、カウントは -1 になります。カウントが 0 になると、値にアクセスできないことを意味し、ガベージ コレクション メカニズムによってオブジェクトがクリアされます。欠点: 2 つのオブジェクトが循環参照する場合、参照
カウントは何もできません。循環参照を複数回実行するとクラッシュなどの問題が発生します。そのため、後にマークアンドスイープ方式に置き換えられました。
深いコピーと浅いコピー
手書きの浅いコピー 深いコピー ⭐⭐⭐⭐⭐

// ----------------------------------------------浅拷贝
// 只是把对象的属性和属性值拷贝到另一个对象中
var obj1 = {
  a: {
    a1: { a2: 1 },
    a10: { a11: 123, a111: { a1111: 123123 } }
  },
  b: 123,
  c: "123"
}
// 方式1
function shallowClone1(o) {
  let obj = {}

  for (let i in o) {
    obj[i] = o[i]
  }
  return obj
}

// 方式2
var shallowObj2 = { ...obj1 }

// 方式3
var shallowObj3 = Object.assign({}, obj1)

let shallowObj = shallowClone1(obj1);

shallowObj.a.a1 = 999
shallowObj.b = true

console.log(obj1);  //第一层的没有被改变,一层以下就被改变了



// --------------

おすすめ

転載: blog.csdn.net/sinat_52319736/article/details/129244829