この記事では、JavaScriptに関する関連知識を紹介しており、主に面接でよくある質問をまとめて紹介しています。一緒に見ていきましょう。皆様のお役に立てれば幸いです。
[関連する推奨事項:JavaScript ビデオ チュートリアル、Web フロントエンド]
1.1 Js にはどのようなデータ型があるのか
JavaScript には 8 つのデータ型があります
基本データ型: 未定義、Null、ブール値、数値、文字列、シンボル、BigInt。
複雑なデータ型: Object
Symbol と BigInt は ES6 の新しいデータ型です:
Symbol は、作成後に一意で不変のデータ型を表します。これは主に、グローバル変数の競合の可能性の問題を解決することを目的としています。
BigInt は、任意の精度の形式で整数を表現できる数値型のデータです。BigInt を使用すると、数値が Number で表現できる範囲を超えている場合でも、大きな整数を安全に保存および操作できます。安全な整数範囲。
1.2 ヒープ領域とスタック領域についての理解を話してください
オペレーティング システムでは、メモリはスタック領域とヒープ領域
#に分かれており、スタック領域は関数のパラメータ値やローカル変数の値などを格納するためにコンパイラによって自動的に確保・解放されます。データ構造内のスタックのように動作します。
ヒープ領域のメモリは、通常、開発者によって割り当ておよび解放されます。開発者が解放しない場合、プログラムの終了時にガベージ コレクション機構によって再利用される可能性があります。 。
データ構造内:
データ構造内では、スタック内のデータへのアクセス方法は先入れ、後出しです。
ヒープは優先度によってソートされた優先キューであり、サイズに応じて優先度を指定できます。
データ格納方法
元のデータ型はスタック内の単純なデータ セグメントに直接格納され、小さなスペースと固定サイズを占有します。は頻繁に使用されるデータであるため、スタックに格納されます。
は、データ型がヒープに格納されるオブジェクトを指しますが、これは大きな領域を占有し、サイズが固定されていません。スタックに格納すると、プログラムのパフォーマンスに影響します。参照データ型は、ヒープ内のエンティティの開始アドレスを指すポインターをスタックに格納します。インタプリタが参照値を探すとき、まずスタック上のアドレスを取得し、次にヒープからエンティティを取得します。
1.3 データ型の検出方法とは
では、データ型を判断する方法は一般的に次のとおりです。スルー: typeof、よく使用される 4 つのメソッド:instanceof、constructor、toString
#1.4 配列を判断する方法は何ですか
Object.prototype.toString.call() で判断する
プロトタイプチェーンで判断する
ES6 の配列を介して .isArray() が判断します
instanceofで判断します
Array.prototype を渡します.isPrototypeOf
1.5 null と unknown の違い
まず第一に、Unknown と Null は基本的なデータ型です。これら 2 つの基本データ型は、それぞれ、未定義と null の値を 1 つだけ持ちます。
未定義は未定義を意味し、null は空のオブジェクトを意味します。一般に、変数が宣言されていても定義されていない場合は、未定義が返されます。Null は主に、初期化としてオブジェクトを返す可能性のある一部の変数に値を割り当てるために使用されます。
undefine は JavaScript の予約語ではないため、変数名として使用できますが、これは未定義値の判定に影響を与えるため非常に危険です。 void 0 などのいくつかのメソッドを通じて、安全な未定義値を取得できます。
typeof を使用してこれら 2 つの型を判断する場合、Null 型を指定すると「object」が返されます。これは歴史的な問題です。 2 つのタイプの値を比較するために 2 つの等号を使用する場合は true を返し、3 つの等号を使用する場合は false を返します。
1.6 typeof null の結果とその理由は何ですか?
typeof null の結果は Object です。
JavaScript の最初のバージョンでは、すべての値は 32 ビット単位で保存され、各単位には小さな型タグ (1 ~ 3 ビット) と保存される現在の値の実際のデータが含まれていました。 。タイプ ラベルは各ユニットの下位ビットに格納され、次の 5 つのデータ タイプがあります。
000: object - 当前存储的数据指向一个对象。 1: int - 当前存储的数据是一个 31 位的有符号整数。 010: double - 当前存储的数据指向一个双精度的浮点数。 100: string - 当前存储的数据指向一个字符串。 110: boolean - 当前存储的数据是布尔值。
最下位ビットが 1 の場合、タイプ ラベル フラグの長さは 1 ビットのみです。最下位ビットが 0 の場合、タイプ ラベル フラグの長さは 3 ビットで、ストレージ用に 2 つの追加データ タイプが提供されます。ビットの。
2 つの特別なデータ型があります:
未定義の値は (-2)30 (整数の範囲を超えた数値) です;
null の値はマシン コードの NULL ポインタです (null ポインタの値はすべて 0)
1.7 なぜ 0.1 0.2 ! == 0.3、それらを等しくする方法 (精度が失われます)
コンピュータはデータをバイナリで保存するため、コンピュータが 0.1 0.2 を計算するとき、実際には 2 つの数値の 2 進和を計算しています。 JS には数値型が 1 つだけあります: Number. その実装は IEEE 754 標準に従い、標準の倍精度浮動小数点数である 64 ビットの固定長を使用して表現します。 2 進科学表記法では、倍精度浮動小数点数の小数部分は、その前の 1 を加えた最大 52 桁までしか保持できません。これは、実際には有効数字 53 桁を保持することを意味します。残りの桁は、「0 の丸め」に従って破棄する必要があります。原則として1までとさせていただきます。 この原則によると、2 進数 0.1 と 0.2 を加算して 10 進数に変換すると、0.300000000000000004 となります。したがって、不等式 の解決策は、「機械精度」と呼ばれることが多い誤差の範囲を設定することです。 JavaScript の場合、この値は通常 2 ~ 52 です。ES6 では、Number.EPSILON 属性が提供され、その値は 2 ~ 52 です。0.1 0.2 ~ 0.3 が Number.EPSILON より小さいかどうかを判断するだけで済みます。 0.1 0.2 ===0.3function numberepsilon(arg1,arg2){ return Math.abs(arg1 - arg2) < Number.EPSILON; } console.log(numberepsilon(0.1 + 0.2, 0.3)); // true
1.8 と判断されます 安全な未定義値を取得するにはどうすればよいですか?
未定義は識別子なので、変数として使用して代入することができますが、通常の未定義の判定に影響を与えます。式 void ___ には戻り値がないため、結果は未定義です。 void は式の結果を変更しません。式が値を返さなくなるだけです。したがって、void 0 を使用すると、未定義を取得できます。1.9 typeof NaN の結果は何ですか?
NaN は「数値ではない」という意味です。NaN は「センチネル値」 (センチネル値、特別な目的を持つ通常の値) であり、数値がその値に含まれていることを示すために使用されます。数値型。エラー条件は「数学的演算を実行しましたが成功しませんでした。これは失敗後に返された結果です」です。typeof NaN; // "number"
1.10 isNaN 関数と Number.isNaN 関数の違いは何ですか?
関数 isNaN はパラメーターを受け取った後、パラメーターを数値に変換しようとします。数値に変換できない値はすべて true を返します。 -数値を渡すとtrueも返され、NaNの判定に影響します。 関数 Number.isNaN は、最初に受信パラメータが数値かどうかを判断します。数値の場合は、引き続き NaN かどうかを判断します。データ型変換は実行されません。このメソッドは詳細です。 NaN 判定が正確です。1.11 == 演算子のキャスト ルールは何ですか?
==の場合、比較する両者の型が異なる場合、型変換が行われます。 x と y が同じかどうかを比較する場合、次のような判定処理が行われます: まず、2 つの型が同じかどうかを判定します。 2 つのサイズが比較されます。 型が同じでない場合は、型変換が実行されます。 は、最初に null と unknown が比較されているかどうかを判断し、比較されている場合は、 true を返します 2 つの型が文字列と数値であるかどうかを判断し、そうである場合、文字は次のようになります 文字列を数値に変換します1 == '1' ↓ 1 == 1
'1' == true ↓ '1' == 1 ↓ 1 == 1
'1' == { name: 'js' } ↓'1' == '[object Object]'
1.12 他の値の型を文字列に変換するための変換規則は何ですか?
Null 型と未定義型、null は "null" に変換され、未定義は "未定義" に変換されます。 Boolean 型、true は "true" に変換されます。 false は「false」に変換されます。 数値型の値は直接変換されますが、非常に小さい数値や大きい数値は指数形式を使用します。 シンボル型の値は直接変換されますが、明示的なキャストのみが許可されます。暗黙的なキャストを使用するとエラーが発生します。对普通对象来说,除非自行定义 toString() 方法,否则会调用 toString()(Object.prototype.toString())来返回内部属性 [[Class]] 的值,如"[object Object]"。如果对象有自己的 toString() 方法,字符串化时就会调用该方法并使用其返回值。
1.13. 其他值类型转成数字的转换规则?
Undefined 类型的值转换为 NaN。
Null 类型的值转换为 0。
Boolean 类型的值,true 转换为 1,false 转换为 0。
String 类型的值转换如同使用 Number() 函数进行转换,如果包含非数字值则转换为 NaN,空字符串为 0。
Symbol 类型的值不能转换为数字,会报错。
对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其强制转换为数字。
为了将值转换为相应的基本类型值, 隐式转换会首先检查该值是否有valueOf()方法。如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用 toString() 的返回值(如果存在)来进行强制类型转换。
如果 valueOf() 和 toString() 均不返回基本类型值,会产生 TypeError 错误。
1.14 其他值类型转成布尔类型的转换规则?
以下这些是假值: undefined 、 null 、 false 、 +0、-0 和 NaN 、 ""
假值的布尔强制类型转换结果为 false。从逻辑上说,假值列表以外的都应该是真值。
1.15. || 和 && 操作符的返回值?
|| 和 && 首先会对第一个操作数执行条件判断,如果其不是布尔值就先强制转换为布尔类型,然后再执行条件判断。
对于 || 来说,如果条件判断结果为 true 就返回第一个操作数的值,如果为 false 就返回第二个操作数的值。
&& 则相反,如果条件判断结果为 true 就返回第二个操作数的值,如果为 false 就返回第一个操作数的值。
|| 和 && 返回它们其中一个操作数的值,而非条件判断的结果
1.16. Object.is() 与比较操作符 “===”、“==” 的区别?
使用双等号(==)进行相等判断时,如果两边的类型不一致,则会进行强制类型转化后再进行比较。
使用三等号(===)进行相等判断时,如果两边的类型不一致时,不会做强制类型准换,直接返回 false。
使用 Object.is 来进行相等判断时,一般情况下和三等号的判断相同,它处理了一些特殊的情况,比如 -0 和 +0 不再相等,两个 NaN 是相等的。
1.17. 什么是 JavaScript 中的包装类型?
在 JavaScript 中,基本类型是没有属性和方法的,但是为了便于操作基本类型的值,在调用基本类型的属性或方法时 JavaScript 会在后台隐式地将基本类型的值转换为对象。如:
const a = "abc"; a.length; // 3
在访问'abc'.length时,JavaScript 将'abc'在后台转换成String('abc'),然后再访问其length属性。
1.18 Js中隐式转换规则
在 if 语句、逻辑语句、数学运算逻辑、== 等情况下都可能出现隐式类型转换。
坑: 判断时, 尽量不要用 = = , 要用 = = = ( 两个等号判断, 如果类型不同, 默认会进行隐式类型转换再比较)
1.19 说说你对this的理解
this是一个在运行时才进行绑定的引用,在不同的情况下它可能会被绑定不同的对象。
1.20 如何判断 this 的指向
第一种是函数调用模式,当一个函数不是一个对象的属性时,直接作为函数来调用时,this 指向全局对象。
第二种是方法调用模式,如果一个函数作为一个对象的方法来调用时,this 指向这个对象。
第三种是构造器调用模式,如果一个函数用 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象。
第四种是 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。其中 apply 方法接收两个参数:一个是 this 绑定的对象,一个是参数数组。call 方法接收的参数,第一个是 this 绑定的对象,后面的其余参数是传入函数执行的参数。也就是说,在使用 call() 方法时,传递给函数的参数必须逐个列举出来。bind 方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this 指向除了使用 new 时会被改变,其他情况下都不会改变。
this绑定的优先级
new绑定优先级 > 显示绑定优先级 > 隐式绑定优先级 > 默认绑定优先级
1.21 Map和Object的区别
1.22 说说你对JSON的理解
JSON 是一种基于文本的轻量级的数据交换格式。它可以被任何的编程语言读取和作为数据格式来传递。
在项目开发中,使用 JSON 作为前后端数据交换的方式。在前端通过将一个符合 JSON 格式的数据结构序列化为 JSON 字符串,然后将它传递到后端,后端通过 JSON 格式的字符串解析后生成对应的数据结构,以此来实现前后端数据的一个传递。
因为 JSON 的语法是基于 js 的,因此很容易将 JSON 和 js 中的对象弄混,但是应该注意的是 JSON 和 js 中的对象不是一回事,JSON 中对象格式更加严格,比如说在 JSON 中属性值不能为函数,不能出现 NaN 这样的属性值等,因此大多数的 js 对象是不符合 JSON 对象的格式的。
在 js 中提供了两个函数来实现 js 数据结构和 JSON 格式的转换处理,
JSON.stringify 函数,通过传入一个符合 JSON 格式的数据结构,将其转换为一个 JSON 字符串。如果传入的数据结构不符合 JSON 格式,那么在序列化的时候会对这些值进行对应的特殊处理,使其符合规范。在前端向后端发送数据时,可以调用这个函数将数据对象转化为 JSON 格式的字符串。
JSON.parse() 函数,这个函数用来将 JSON 格式的字符串转换为一个 js 数据结构,如果传入的字符串不是标准的 JSON 格式的字符串的话,将会抛出错误。当从后端接收到 JSON 格式的字符串时,可以通过这个方法来将其解析为一个 js 数据结构,以此来进行数据的访问。
1.222 String和JSON.stringify的区别
console.log(String("abc")); // abc console.log(JSON.stringify("abc")); // "abc" console.log(String({ key: "value" })); // [object Object] console.log(JSON.stringify({ key: "value" })); // {"key":"value"} console.log(String([1, 2, 3])); // 1,2,3 console.log(JSON.stringify([1, 2, 3])); // [1,2,3] const obj = { title: "devpoint", toString() { return "obj"; }, }; console.log(String(obj)); // obj console.log(JSON.stringify(obj)); // {"title":"devpoint"}
当需要将一个数组和一个普通对象转换为字符串时,经常使用JSON.stringify。
如果需要对象的toString方法被重写,则需要使用String()。
在其他情况下,使用String()将变量转换为字符串。
1.23 什么是伪数组(类数组)
一个拥有 length 属性和若干索引属性的对象就可以被称为类数组对象,类数组对象和数组类似,但是不能调用数组的方法。
常见的类数组对象有 arguments 和 DOM 方法的返回结果,还有一个函数也可以被看作是类数组对象,因为它含有 length 属性值,代表可接收的参数个数。
1.24 类数组转换成数组的方法有哪些
常见的类数组转换为数组的方法有这样几种:
通过 call 调用数组的 slice 方法来实现转换
Array.prototype.slice.call(arrayLike);
通过 call 调用数组的 splice 方法来实现转换
Array.prototype.splice.call(arrayLike, 0);
通过 apply 调用数组的 concat 方法来实现转换
Array.prototype.concat.apply([], arrayLike);
通过 Array.from 方法来实现转换
Array.from(arrayLike);
1.25 Unicode、UTF-8、UTF-16、UTF-32的区别?
Unicode 是编码字符集(字符集),而UTF-8、UTF-16、UTF-32是字符集编码(编码规则);
UTF-16 使用变长码元序列的编码方式,相较于定长码元序列的UTF-32算法更复杂,甚至比同样是变长码元序列的UTF-8也更为复杂,因为其引入了独特的代理对这样的代理机制;
UTF-8需要判断每个字节中的开头标志信息,所以如果某个字节在传送过程中出错了,就会导致后面的字节也会解析出错;而UTF-16不会判断开头标志,即使错也只会错一个字符,所以容错能力教强;
如果字符内容全部英文或英文与其他文字混合,但英文占绝大部分,那么用UTF-8就比UTF-16节省了很多空间;而如果字符内容全部是中文这样类似的字符或者混合字符中中文占绝大多数,那么UTF-16就占优势了,可以节省很多空间;
1.26 常见的位运算符有哪些?其计算规则是什么?
现代计算机中数据都是以二进制的形式存储的,即0、1两种状态,计算机对二进制数据进行的运算加减乘除等都是叫位运算,即将符号位共同参与运算的运算。
常见的位运算有以下几种:
1.27 为什么函数的 arguments 参数是类数组而不是数组?如何遍历类数组?
arguments是一个对象,它的属性是从 0 开始依次递增的数字,还有callee和length等属性,与数组相似;但是它却没有数组常见的方法属性,如forEach, reduce等,所以叫它们类数组。
要遍历类数组,有三个方法:
(1)将数组的方法应用到类数组上,这时候就可以使用call和apply方法,如:
function foo(){ Array.prototype.forEach.call(arguments, a => console.log(a)) }
(2)使用Array.from方法将类数组转化成数组:
function foo(){ const arrArgs = Array.from(arguments) arrArgs.forEach(a => console.log(a)) }
(3)使用展开运算符将类数组转化成数组
function foo(){ const arrArgs = [...arguments] arrArgs.forEach(a => console.log(a)) }
1.28 escape、encodeURI、encodeURIComponent 的区别
encodeURI 是对整个 URI 进行转义,将 URI 中的非法字符转换为合法字符,所以对于一些在 URI 中有特殊意义的字符不会进行转义。
encodeURIComponent 是对 URI 的组成部分进行转义,所以一些特殊字符也会得到转义。
escape 和 encodeURI 的作用相同,不过它们对于 unicode 编码为 0xff 之外字符的时候会有区别,escape 是直接在字符的 unicode 编码前加上 %u,而 encodeURI 首先会将字符转换为 UTF-8 的格式,再在每个字节前加上 %。
1.29 什么是尾调用,使用尾调用有什么好处?
尾调用指的是函数的最后一步调用另一个函数。代码执行是基于执行栈的,所以当在一个函数里调用另一个函数时,会保留当前的执行上下文,然后再新建另外一个执行上下文加入栈中。使用尾调用的话,因为已经是函数的最后一步,所以这时可以不必再保留当前的执行上下文,从而节省了内存,这就是尾调用优化。
但是 ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。
1.30 use strict是什么? 它有什么用?
use strict 是一种 ECMAscript5 添加的(严格模式)运行模式,这种模式使得 Javascript 在更严格的条件下运行。设立严格模式的目的如下:
消除 Javascript 语法的不合理、不严谨之处,减少怪异行为;
消除代码运行的不安全之处,保证代码运行的安全;
提高编译器效率,增加运行速度;
为未来新版本的 Javascript 做好铺垫。
区别:
禁止使用 with 语句。
禁止 this 关键字指向全局对象。
对象不能有重名的属性。
1.31 如何判断一个对象是否属于某个类?
第一种方式,使用 instanceof 运算符来判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
第二种方式,通过对象的 constructor 属性来判断,对象的 constructor 属性指向该对象的构造函数,但是这种方式不是很安全,因为 constructor 属性可以被改写。
第三种方式,如果需要判断的是某个内置的引用类型的话,可以使用 Object.prototype.toString() 方法来打印对象的[[Class]] 属性来进行判断。
1.32 强类型语言和弱类型语言的区别
强类型语言:强类型语言也称为强类型定义语言,是一种总是强制类型定义的语言,要求变量的使用要严格符合定义,所有变量都必须先定义后使用。Java和C++等语言都是强制类型定义的,也就是说,一旦一个变量被指定了某个数据类型,如果不经过强制转换,那么它就永远是这个数据类型了。例如你有一个整数,如果不显式地进行转换,你不能将其视为一个字符串。
弱い型付き言語: 弱い型付き言語は、弱い型付き定義言語とも呼ばれ、強い型付き定義の逆です。 JavaScript 言語は、型付けが弱い言語です。簡単に理解すると、変数の型を無視できる言語です。たとえば、JavaScript は型指定が弱く、文字列「12」と整数 3 を連結して文字列「123」を取得すると、追加時に強制的に型変換が行われます。
2 つの比較: 強く型付けされた言語は、速度の点で弱く型付けされた言語よりわずかに劣る可能性がありますが、強く型付けされた言語によってもたらされる厳密性は、多くのエラーを効果的に回避するのに役立ちます。
1.33 インタープリタ言語とコンパイル言語の違い
(1) インタープリタ言語は、ソースを解釈するために専用のインタープリタを使用します。プログラムを 1 行ずつプラットフォーム固有のマシンコードに解釈し、即座に実行します。コードは、実行前に変換されるのではなく、実行時にインタプリタによって 1 行ずつ動的に変換されて実行されます。インタプリタ型言語は事前にコンパイルする必要がなく、ソースコードを直接マシンコードに解釈して即座に実行するため、特定のプラットフォームが対応するインタプリタを提供していればプログラムを実行できます。その特徴は次のように要約されます。
インタープリタ型言語を実行するたびに、ソース コードをマシン コードに解釈して実行する必要があり、効率が低くなります。
プラットフォームが提供する限り、対応するインタプリタを使用すると、ソース コードを実行できるため、ソース プログラムの移植が容易になります。
JavaScript、Python などはインタプリタ言語です。
(2) コンパイル言語は、専用のコンパイラを使用して、高級言語のソース コードを、特定のプラットフォーム向けにプラットフォーム ハードウェアで一度に実行できるマシン コードにコンパイルし、それをマシン コードにパッケージ化します。プラットフォーム ハードウェアで実行できる実行可能プログラムの形式を識別します。コンパイル言語で書かれたプログラムは、実行前にソースコードをexe形式ファイルなどの機械語ファイルにコンパイルする特別なコンパイル処理が必要ですが、今後実行する際にはコンパイル結果を直接利用することができます。 exe を直接実行する場合と同様です。コンパイル言語は一度コンパイルするだけでよく、その後実行するときにコンパイルする必要がないため、実行効率が高いです。その特徴をまとめると、
はプラットフォーム関連の機械語ファイルに一度にコンパイルされ、実行時には開発環境から切り離されるため動作効率が高く、
は関連性が高く、 ;
C、C などはコンパイルされた言語です。
この 2 つの主な違いは、前者のソース プログラムはコンパイル後にプラットフォーム上で実行できるのに対し、後者は動作中にコンパイルされることです。したがって、前者は高速に動作し、後者は優れたクロスプラットフォーム パフォーマンスを発揮します。
1.34 for...in と for...of
for...of の違いは、次の新しいトラバーサル メソッドです。 ES6 では、イテレータ インターフェイスを含むデータ構造 (配列、オブジェクトなど) をトラバースし、各項目の値を返すことができます。ES3 の for...in との違いは次のとおりです。 of トラバーサルはオブジェクトのキー値を取得します。for …in はオブジェクトのキー名を取得します。
for… in はオブジェクトのプロトタイプ チェーン全体をトラバースします。パフォーマンスは非常に低いため、推奨されません。一方、for…of は現在のオブジェクトのみを走査し、プロトタイプ チェーンは走査しません。
配列走査の場合、for...in は配列内のすべての列挙可能な属性 (プロトタイプ チェーン上の列挙可能な属性を含む) を返します。 、および for...of は配列の添え字に対応する属性値のみを返します;
概要: for...in ループは主にオブジェクトを走査するように設計されており、配列の走査には適していません。 ...of ループは、配列、配列のようなオブジェクト、文字列、セット、マップ、およびジェネレーター オブジェクトを走査するために使用できます。
1.35 ajax、axios、fetchの違い(1) AJAX Ajaxは「AsynchronousJavascriptAndXML」(非同期JavaScriptとXML)、これは、インタラクティブな Web アプリケーションを作成するための Web 開発テクノロジを意味します。 Webページ全体を再読み込みすることなく、Webページの一部を更新できる技術です。 Ajax を使用すると、バックグラウンドでサーバーと少量のデータを交換することで、Web ページを非同期に更新できます。これは、ページ全体を再読み込みしなくても、Web ページの一部を更新できることを意味します。従来の Web ページ (Ajax なし) では、コンテンツを更新する必要がある場合、Web ページ全体をリロードする必要があります。欠点は次のとおりです。
fetch はネットワーク リクエストのエラーのみを報告します。400 と 500 は成功したリクエストとして扱われます。サーバーが 400 と 500 のエラー コードを返しても、拒否されません。ネットワーク エラーによりリクエストが発生した場合のみ、完了できない場合、フェッチは拒否されます。
##リクエストを変換してリターン
1.36 配列トラバーサル メソッドとは何ですか
1.37 forEach メソッドとマップ メソッドの違いは何ですか
このメソッドは、配列を走査するときに使用されます。この 2 つの違いは次のとおりです。forEach() メソッドは、各要素に対して提供された関数とデータに対する操作を実行します。元の配列が変更されます。このメソッドには戻り値がありません。map() メソッドは、元の配列の値を変更しませんが、新しい配列を返します。新しい配列の値は、関数呼び出し後の元の配列の値;
1.38 Qian のコピーとディープ コピーについてのご意見をお聞かせください
浅いコピー##浅いコピーとは、元のデータ属性値の正確なコピーを持つ新しいデータの作成を指します属性が基本タイプの場合、基本タイプの値コピーされます。属性が参照型の場合、メモリ アドレスがコピーされます。
つまり、浅いコピーは 1 つのレイヤーをコピーし、深い参照型はメモリ アドレスを共有します 共通の浅いコピー: Object.assign Object.create##slice
concat()
Expand 演算子
ディープ コピー
一般的なディープ コピー メソッドは次のとおりです:
オブジェクトに関数または未定義がある場合、それは直接破棄されますコンストラクターによって生成されたjson内にオブジェクトがある場合、オブジェクトのコンストラクター
#1.40 lodash をご存知ですか?どのような共通 API がありますか?
Lodash は、一貫性のあるモジュール式の高性能 JavaScript ユーティリティ ライブラリです。
#__.cloneDeep ディープ コピー __.reject 条件に基づいて要素を削除します。__.drop(array, [n=1] ) 関数: 配列の最初の n 要素を削除し、残りの部分を返します。
1.41 LHS とRHS クエリ LHS (左側) と RHS (右側) は、JS エンジンがコード実行フェーズ中に変数を操作する 2 つの方法です。変数クエリの目的は、変数の代入またはクエリです。 LHS は、変数が代入演算子 (=) の左側にあると理解できます (例: a = 1)。現在のエンジンによる変数 a の検索の目的は、変数の代入です。この場合、エンジンは変数 a の元の値を気にせず、変数 a に値 1 を割り当てるだけです。 RHS は、変数が代入演算子 (=) の右側にあると理解できます (例: console.log(a))。エンジンによる変数 a の検索の目的は、クエリです。は変数 a に対応する実際の値を見つける必要があります。出力する前に値は何ですか。1.42 include は、indexOf よりもどのように優れているのでしょうか?
includes は NaN を検出できますが、indexOf は NaN を検出できません。また、includes は NaN と一致するために内部で Number.isNaN を使用しますN 1.43 AMD と CMD の違い?1.44 (a == 1 && a == 2 && a == 3) それは本当でしょうか?
方案一:重写toString()或valueOf()
let a = { i: 1, toString: function () { return a.i++; } } console.log(a == 1 && a == 2 && a == 3); // true
方案二:数组
数组的toString接口默认调用数组的join方法,重写join方法。定义a为数字,每次比较时就会调用 toString()方法,我们把数组的shift方法覆盖toString即可:
let a = [1,2,3]; a.toString = a.shift; console.log(a == 1 && a == 2 && a == 3); // true
当然把toString改为valueOf也是一样效果:
let a = [1,2,3]; a. valueOf = a.shift; console.log(a == 1 && a == 2 && a == 3); // true
方案三:使用Object.defineProperty()
Object.defineProperty()用于定义对象中的属性,接收三个参数:object对象、对象中的属性,属性描述符。属性描述符中get:访问该属性时自动调用。
var _a = 1; Object.defineProperty(this,'a',{ get:function(){ return _a++ } }) console.log(a===1 && a===2 && a===3)//true
1.45 JS中的 MUL 函数
MUL表示数的简单乘法。在这种技术中,将一个值作为参数传递给一个函数,而该函数将返回另一个函数,将第二个值传递给该函数,然后重复继续。例如:xyz可以表示为
const mul = x => y => z => x * y * z console.log(mul(1)(2)(3)) // 6
1.46 深度遍历广度遍历的区别?
对于算法来说 无非就是时间换空间 空间换时间
1、深度优先不需要记住所有的节点, 所以占用空间小, 而广度优先需要先记录所有的节点占用空间大
2、深度优先有回溯的操作(没有路走了需要回头)所以相对而言时间会长一点
3、深度优先采用的是堆栈的形式, 即先进后出
4、广度优先则采用的是队列的形式, 即先进先出
1.47 JS中的设计模式有哪些?
单例模式
保证一个类仅有一个实例,并提供一个访问它的全局访问点。实现的方法为先判断实例存在与否,如果存在则直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。
策略模式
定义一系列的算法,把他们一个个封装起来,并且使他们可以相互替换。
代理模式
为一个对象提供一个代用品或占位符,以便控制对它的访问。
中介者模式
通过一个中介者对象,其他所有的相关对象都通过该中介者对象来通信,而不是相互引用,当其中的一个对象发生改变时,只需要通知中介者对象即可。通过中介者模式可以解除对象与对象之间的紧耦合关系。
装饰者模式
在不改变对象自身的基础上,在程序运行期间给对象动态地添加方法。
1.48 forEach如何跳出循环?
forEach是不能通过break或者return来实现跳出循环的,为什么呢?实现过forEach的同学应该都知道,forEach的的回调函数形成了一个作用域,在里面使用return并不会跳出,只会被当做continue
可以利用try catch
function getItemById(arr, id) { var item = null; try { arr.forEach(function (curItem, i) { if (curItem.id == id) { item = curItem; throw Error(); } }) } catch (e) { } return item; }
1.49 JS中如何将页面重定向到另一个页面?
1、使用 location.href:window.location.href ="url"
2、使用 location.replace: window.location.replace("url");
1.50 移动端如何实现上拉加载,下拉刷新?
上拉加载
上拉加载的本质是页面触底,或者快要触底时的动作
判断页面触底我们需要先了解一下下面几个属性
scrollTop:滚动视窗的高度距离window顶部的距离,它会随着往上滚动而不断增加,初始值是0,它是一个变化的值
clientHeight:它是一个定值,表示屏幕可视区域的高度;
scrollHeight:页面不能滚动时也是存在的,此时scrollHeight等于clientHeight。scrollHeight表示body所有元素的总长度(包括body元素自身的padding)
综上我们得出一个触底公式:
scrollTop + clientHeight >= scrollHeight
下拉刷新
下拉刷新的本质是页面本身置于顶部时,用户下拉时需要触发的动作
关于下拉刷新的原生实现,主要分成三步:
监听原生touchstart事件,记录其初始位置的值,e.touches[0].pageY;
监听原生touchmove事件,记录并计算当前滑动的位置值与初始位置值的差值,大于0表示向下拉动,并借助CSS3的translateY属性使元素跟随手势向下滑动对应的差值,同时也应设置一个允许滑动的最大值;
监听原生touchend事件,若此时元素滑动达到最大值,则触发callback,同时将translateY重设为0,元素回到初始位置
1.51 JS 中的数组和函数在内存中是如何存储的?
JavaScript における配列ストレージは、大きく 2 つの状況に分ける必要があります。
同じタイプのデータを持つ配列は、連続したメモリ空間を割り当てます。
同じタイプではないデータを持つ配列は、ハッシュ マッピングを使用します。メモリ空間の割り当て
注意: 連続メモリ空間は、インデックス (ポインタ) に基づいて格納場所を直接計算するだけで十分であると想像できます。ハッシュ マップの場合は、最初にインデックス値を計算する必要があり、次にインデックス値に競合がある場合は 2 回目の検索を実行する必要があります (ハッシュがどのように保存されているかを知る必要があります)。
2.1 クロージャとは何ですか?
公式声明: クロージャとは、別の関数のスコープ内の変数にアクセスする権利を持つ関数を指します。
MDN ステートメント: クロージャは特別なオブジェクトです。これは、関数と関数が作成される環境の 2 つの部分で構成されます。環境は、クロージャの作成時にスコープ内にあったローカル変数で構成されます。
詳細な回答
ブラウザがページをロードすると、実行のためにコードがスタック メモリ (ECStack) に配置されます。関数がスタックにプッシュされると、プライベート コンテキストが作成されます。 (EC) が生成されます。このコンテキストは内部を保護できます。変数 (AO) の使用は外部干渉を受けず、現在の実行コンテキスト内の一部のコンテンツがコンテキスト外のコンテンツによって占有されている場合、現在のコンテキストは保護されません。スタックから解放されることで内部の変数や変数値を保存できるので、クロージャは内部のプライベート変数を保存・保護するための仕組みだと思います。
2.2 クロージャの役割
クロージャには 2 つの一般的な用途があります。
クロージャの 1 つ目の目的は、次のことを可能にすることです。関数の外から関数内の変数にアクセスできるようになります。クロージャを使用すると、外部からクロージャ関数を呼び出すことで関数内の変数に外部からアクセスでき、このメソッドを使用してプライベート変数を作成できます。
クロージャのもう 1 つの用途は、終了した関数コンテキスト内の変数オブジェクトをメモリに残すことです。クロージャ関数は変数オブジェクトへの参照を保持するため、変数オブジェクトはリサイクルされません。
2.3 プロジェクトにおけるクロージャの参考シナリオとそれが引き起こす問題
実際のプロジェクトでは、クロージャはコンテンツをラップするために使用されます。これにより、独自のコードをプライベートとして保護し、グローバル変数や他のコードとの競合を防ぐことができます。これは、保護メカニズムを使用することです。
ただし、解放されていないコンテキストを使用するとスタック メモリ領域が占有され、過度に使用するとメモリ リークが発生する可能性があるため、クロージャを多用することはお勧めできません。
クロージャによって引き起こされるメモリ リークの問題を解決する方法は、クロージャ関数を使用した後に手動で解放することです。
2.4 クロージャの使用シナリオ
関数を返す
関数をパラメータとして返す
IIFE (Self -関数の実行)
ループ割り当て
コールバック関数の使用はクロージャの使用です
スロットリングとアンチシェイク
関数のカリー化
2.5 クロージャ実行処理
##プライベートコンテキストの形成##スタックにプッシュして一連の操作を実行
(1) . スコープ チェーンを初期化します (両端 <現在のスコープ、上位スコープ> ) 現在のスコープ、上位スコープ>
(2). これを初期化します
(3). 引数を初期化します
(4) . 仮パラメータの割り当て
(5). 変数のプロモーション
(6). コードの実行
変数を見つけたら、まずそれが自分にとってプライベートなものかどうかを確認してください。プライベートではないので、スコープ チェーンに従って検索します。上位のものでない場合は、EC(G) までオンラインで検索を続けます。変数の検索は、実際にはスコープ チェーンの結合プロセスであり、クエリ チェーンを結合します。式はスコープチェーンです。
通常の状況では、コードの実行が完了すると、プライベート コンテキストがスタックからポップされてリサイクルされます。ただし、特殊な状況では、実行完了後に現在のプライベート コンテキスト内の何かが実行コンテキスト以外の何かによって占有されている場合、現在のプライベート コンテキストはスタックから解放されません。つまり、コンテキストは破棄されません。クロージャーが形成されます。
2.6 実行コンテキストの種類
(1) グローバル実行コンテキスト関数内にないものはすべてグローバル実行コンテキストでは、最初にグローバル ウィンドウ オブジェクトを作成し、この値をこのグローバル オブジェクトに等しく設定します。グローバル実行コンテキストはプログラム内に 1 つだけあります。
(2) 関数実行コンテキスト
関数が呼び出されると、その関数に対して新しい実行コンテキストが作成されます。関数コンテキストはいくつでも作成できます。
(3) eval 関数の実行コンテキスト
eval 関数で実行されるコードは独自の実行コンテキストを持ちますが、eval 関数は一般的に使用されないため、紹介しません。
2.7 実行コンテキスト スタックとは
JavaScript エンジンは、実行コンテキスト スタックを使用して実行コンテキストを管理します
JavaScript がコードを実行するとき、最初にグローバル コードに遭遇し、グローバル実行コンテキストを作成して実行スタックにプッシュします。関数呼び出しに遭遇するたびに、関数の新しい実行コンテキストを作成し、実行スタックにプッシュします。スタックの最上部では、エンジンは実行コンテキスト スタックの最上部にある関数を実行します。関数の実行が完了すると、実行コンテキストはスタックからポップされ、次のコンテキストの実行を続けます。すべてのコードが実行されると、グローバル実行コンテキストがスタックからポップされます。
2.8 実行コンテキストの 3 つのフェーズ
作成フェーズ→実行フェーズ→リサイクル フェーズ
作成フェーズ
(1) このバインディング
グローバル実行コンテキストでは、これはグローバル オブジェクト (ウィンドウ オブジェクト) を指します
関数実行コンテキストでは、このポイントは関数の呼び出し方法によって異なります。参照オブジェクトによって呼び出された場合、this はそのオブジェクトに設定されます。それ以外の場合、this の値はグローバル オブジェクトまたは未定義に設定されます
#(2) レキシカル環境コンポーネントを作成します字句環境は、識別子と変数のマッピングを持つデータ構造です。識別子は変数/関数名を指し、変数は実際のオブジェクトまたは元のデータへの参照です。字句環境内には 2 つのコンポーネントがあります: 太字: 環境レコーダー: 変数と関数宣言の実際の場所を保存するために使用されます。 外部環境参照: 親スコープにアクセスできます。(3)変数環境コンポーネントを作成する変数環境は字句環境でもあり、その環境レコーダーは、実行コンテキストの変数宣言ステートメントによって作成されたバインド関係を保持します。実行フェーズこの段階では、変数の割り当てとコードの実行が実行されますJavaScript エンジンがソースで宣言された実際の場所で変数の値を見つけられない場合#リサイクル フェーズ
実行コンテキストをスタックから外し、仮想マシンが実行コンテキストをリサイクルするのを待ちます
2.9 スコープの理解について話すスコープは、エンジンが現在のスコープ内の識別子名に基づいて変数の検索を実行する方法を制御する一連のルールとみなすことができます。ネストされたサブスコープ。
簡単に言えば、スコープは変数の有効範囲です。変数データは特定の空間で読み書きでき、この空間が変数のスコープとなります。
(1) グローバルスコープscript タグ内に直接記述した JS コードはグローバルスコープになります。グローバルスコープで宣言された変数をグローバル変数(ブロックレベルの外で定義された変数)と呼びます。
グローバル変数は世界中のどこでも使用できますが、ローカル スコープの変数にはグローバル スコープではアクセスできません。
グローバル スコープは、ページが開かれたときに作成され、ページが閉じられたときに破棄されます。
ウィンドウ オブジェクトのすべてのプロパティにはグローバル スコープがあります
var および function コマンドで宣言されたグローバル変数と関数は、ウィンドウ オブジェクトのプロパティとメソッドです
let コマンド、constコマンド、classコマンドで宣言したグローバル変数はウィンドウオブジェクトの属性に属さない
(2) 関数スコープ(ローカルスコープ)A関数スコープは関数が呼び出されたときに作成され、関数が実行された後、スコープは破棄されます。関数が呼び出されるたびに、新しい関数スコープが作成され、それらは互いに独立しています。
グローバル変数は関数スコープ内でアクセスできますが、関数内の変数には関数の外からアクセスできません。
関数スコープで変数を操作する場合、変数は最初に独自のスコープ内で検索します。変数が存在する場合は、それが直接使用されます。そうでない場合は、グローバル変数が見つかるまで前のスコープ内で検索されます。グローバル スコープが見つかった場合は、それが直接使用されます。それでもスコープ内で見つからない場合は、エラーが報告されます。
(3) ブロックレベルのスコープES6 以前の JavaScript では、関数スコープと字句スコープが使用されていましたが、ES6 ではブロックレベルのスコープが導入されました。
中括弧 {} で囲まれたステートメントはすべてブロックに属します。ブロック内で let と const を使用して宣言された変数には、外部からアクセスできません。この種のスコープ ルールはブロック レベルと呼ばれます。
非厳密モードで作成された var 宣言または関数宣言を通じて宣言された変数には、ブロックレベルのスコープがありません。
(4) 字句スコープ字句スコープは静的スコープであり、関数がどこでどのように呼び出されても、字句スコープのスコープのみが決定されます。関数が宣言されている場所によって決まります。
コンパイルの字句解析フェーズでは、基本的にすべての識別子がどこにあり、どのように宣言されているかを知ることができるため、実行中に識別子を見つける方法を予測できます。
つまり、字句スコープとは、コードを作成するときに変数のスコープを決定することを意味します。
2.10 スコープ チェーンとはjs で変数を使用する場合、まず js エンジンは現在の変数を下位に行こうとします。スコープ 変数を検索します。見つからない場合は、変数が見つかるかグローバル スコープに到達するまで、その上位スコープを検索します。このような変数スコープ アクセスの連鎖構造をスコープ チェーンと呼びます。
詳細な回答
スコープ チェーンは本質的に、変数オブジェクトを指すポインターのリストです。変数オブジェクトは、実行環境内のすべての変数と関数を含むオブジェクトです。スコープ チェーンのフロント エンドは常に、現在の実行コンテキストの変数オブジェクトです。グローバル実行コンテキストの変数オブジェクト (つまり、グローバル オブジェクト) は、常にスコープ チェーンの最後のオブジェクトです。
2.11 スコープ チェーンの役割
スコープ チェーンの役割は、スコープ チェーンにアクセスできるすべての変数と関数が、実行環境はアクセス可能 スコープチェーンを介したシーケンシャルアクセスにより、外部環境の変数や関数にアクセスできます。
2.12 スコープの一般的なアプリケーション シナリオ
スコープの一般的なアプリケーション シナリオの 1 つはモジュール化です。
JavaScript はモジュール化をネイティブにサポートしていないため、グローバル スコープの汚染や変数名の競合、コード構造の肥大化、再利用性の低さなど、多くのよだれが出るような問題が発生しています。正式なモジュール化ソリューションが導入される前は、この種の問題を解決するために、開発者は関数スコープを使用してモジュールを作成することを考えていました。
2.13 Js での事前解析について話しますか?
JS エンジンがコードを実行すると、次の手順に従って動作します:
1. 変数の宣言を現在のスコープの前にプロモートします。 、宣言のみがプロモートされますが、代入はプロモートされません
#2. 関数宣言を現在のスコープの先頭にプロモートします。ステートメントのみがプロモートされますが、呼び出しはプロモートされません
3. var をプロモートする場合、最初の関数をプロモートします
#2.14 変数のプロモートと関数のプロモートの違いは何ですか?
変数のプロモーション
簡単に言うと、JavaScript コードが実行される前にエンジンがプリコンパイルされ、プリコンパイル期間中に、変数宣言と関数宣言がそれらの宣言にプロモートされます。スコープの先頭では、関数内で宣言された変数のみが関数スコープの先頭に昇格されます 関数内で定義された変数が外側と同じ場合、関数本体内の変数が昇格されます頂点に。
関数プロモーション
関数プロモーションは関数宣言の記述を改善するだけであり、関数式の記述は存在しません関数プロモーション
関数プロモーションの優先順位は変数プロモーションの優先順位よりも高くなります。つまり、関数ホイスティングは変数ホイスティングの上にあります
2.14 スコープチェーンを拡張するにはどうすればよいですか?
#スコープ チェーンは拡張できます。スコープ チェーンの拡張: 実行環境は、グローバルとローカル (関数) の 2 種類のみです。ただし、一部のステートメントではスコープ チェーンのフロントエンドに変数オブジェクトを一時的に追加することができ、その変数オブジェクトはコードの実行後に削除されます。具体的には、これら 2 つのステートメントが実行されると、スコープ チェーンが強化されます。catch ステートメントの try-catch ブロック: からスローされた変数を含む新しい変数オブジェクトが作成されます。エラーオブジェクトの宣言。with ステートメント: with ステートメントは、指定されたオブジェクトをスコープ チェーンに追加します。2.15 ブラウザのガベージコレクションの仕組み
(1) メモリのライフサイクル
割り当てられるメモリJS 環境では通常、次のライフ サイクルがあります。メモリ割り当て: 変数、関数、オブジェクトを宣言すると、システムはそれらに自動的にメモリを割り当てます。メモリ使用量: つまり読み取りです。メモリのリサイクル: 使用後、使用されなくなったメモリはガベージ コレクションによって自動的にリサイクルされますグローバル変数は通常、値は使用されなくなった場合、自動的にリサイクルされます。(2) ガベージ コレクションの概念
ガベージ コレクション: JavaScript の場合コードが実行されている場合、変数と値を保存するためにメモリ領域を割り当てる必要があります。変数が操作に関与しなくなった場合、システムは占有されたメモリ領域を再利用する必要があります (これがガベージ コレクションです)。リサイクル メカニズム:Javascript には自動ガベージ コレクション メカニズムがあり、使用されなくなった変数やオブジェクトによって占有されているメモリを定期的に解放します。原理は、使用されなくなった変数を見つけることです。使用した後、占有しているメモリを解放します。JavaScript には、ローカル変数とグローバル変数の 2 種類の変数があります。グローバル変数のライフ サイクルは、ページがアンロードされるまで継続しますが、ローカル変数は関数内で宣言され、そのライフ サイクルは関数の実行から関数の実行の終了まで始まります。このプロセス中に、ローカル変数は次のように保存されます。それらの値はヒープまたはスタックに保存され、関数の実行が終了すると、これらのローカル変数は使用されなくなり、それらが占有していたスペースは解放されます。ただし、ローカル変数が外部関数によって使用される場合、状況の 1 つはクロージャです。関数の実行が終了した後も、関数の外部の変数は関数内のローカル変数を指し続けます。このとき、ローカル変数はまだ使用されており、使用されているためリサイクルされません。(3) ガベージコレクション方法
これは比較的まれに使用されますが、IE で使用される参照カウント アルゴリズムです。参照カウントは、各値が参照された回数を追跡します。変数が宣言され、その変数に参照型が割り当てられている場合、値への参照の数は 1 です。逆に、この値への参照を含む変数が別の値を取得すると、この値への参照の数は 1 つ減ります。参照数が 0 になると、その変数には値が存在しないことを意味するため、この変数が占有していたメモリ空間は、次回マシンリサイクル期間中に実行されるときに解放されます。
このメソッドでは、循環参照の問題が発生します。たとえば、obj1 と obj2 は属性を通じて相互に参照し、両方のオブジェクトの参照数は 2 です。ループカウントを使用する場合、関数の実行後、両方のオブジェクトがスコープから出て関数の実行が終了するため、obj1 と obj2 は存在し続けるため、それらの参照数が 0 になることはなく、循環参照が発生します。
2. マークのクリア方法
最新のブラウザでは、参照カウント アルゴリズムが使用されなくなりました。
最新のブラウザのほとんどは、マーク アンド クリア アルゴリズムに基づいていくつかの改良されたアルゴリズムを使用しており、全体的な考え方は同じです。
マークのクリアは、ブラウザで一般的なガベージ コレクション方法です。変数が実行環境に入ると、「環境に入る」とマークされます。「環境に入る」とマークされた変数は、再利用できないため、使用済み。変数が環境から離れると、その変数は「環境からの離脱」としてマークされ、「環境からの離脱」としてマークされた変数はメモリから解放されます。
ガベージ コレクターは、実行時にメモリに格納されているすべての変数にマークを付けます。次に、環境内の変数と、環境内の変数によって参照されるタグを削除します。これ以降にマークされた変数は、環境内の変数がこれらの変数にアクセスできなくなるため、削除される変数とみなされます。やっと。ガベージ コレクターはメモリ クリーニング作業を完了し、マークされた値を破棄し、それらが占有しているメモリ領域を再利用します。
(4) ガベージ コレクションを削減する方法
ブラウザは自動的にガベージ コレクションを実行できますが、コードが複雑になると、ガベージ コレクションのコストが比較的高くなります。したがって、ガベージ コレクションは最小限に抑える必要があります。
配列を最適化する: 配列をクリアする場合、最も簡単な方法は値 [ ] を割り当てることですが、同時に新しい空のオブジェクトが作成され、配列の長さを設定できます。これは、配列をクリアするという目的を達成するために使用されます。
オブジェクトの最適化: オブジェクトをできるだけ再利用します。使用されなくなったオブジェクトについては、null に設定し、できるだけ早くリサイクルします。
関数の最適化: ループ内の関数式が再利用できる場合は、関数の外側に配置してみてください。
# (5) メモリ リークとは# プログラムが過失やエラーにより使用されなくなったメモリを解放できないことを指します
(6) メモリ リークが発生する状況は次のとおりです。次の 4 つの状況でメモリ リークが発生します。
予期しないグローバル変数: 未宣言の変数の使用により予期せぬ変数が発生します。グローバル変数が作成され、この変数はメモリ内に残り、リサイクルできなくなります。
タイマーまたはコールバック関数を忘れた場合: setInterval タイマーを設定し、それをキャンセルするのを忘れました。ループ関数に外部変数への参照がある場合、変数はメモリ内に残り、リサイクルできません。
DOM 参照から分離: DOM 要素への参照を取得し、要素が削除されます。この要素への参照は常に保持されるため、再利用できません。
クロージャ: クロージャを不適切に使用すると、一部の変数がメモリ内に残ります。
3. 関数と関数型プログラミング3.1 関数型プログラミングとは関数 形式的プログラミングは、プログラムを作成するための方法論である「プログラミング パラダイム」 (プログラミング パラダイム) です。
プログラミング パラダイムには、命令型プログラミング、宣言型プログラミング、関数型プログラミングの 3 つがあります。
命令型プログラミングと比較すると、関数型プログラミングは、命令型プログラミング、宣言型プログラミング、関数型プログラミングの 3 つです。実行プロセスよりもプログラムの実行結果に重点を置き、複雑な実行プロセスを設計するのではなく、多数の単純な実行ユニットを使用して計算結果を漸進的にし、複雑な演算を層ごとに導き出すことを提唱しています
3.2 関数型プログラミングの長所と短所利点
状態管理の向上: ステートレスにすることが目的であるため、状態が 1 以下であれば、これらの未知数を最小限に抑え、コードを最適化し、エラー状況を減らすことができます。
より簡単な再利用: 固定入力 -> 固定出力、他の外部変数の影響がなく、副作用もありません。このようにして、コードを再利用するときに、コードの内部実装と外部効果を考慮する必要がなくなります。もっと細かく言えば、関数は複数の小さな関数で構成されている場合もあります。強力な再利用性は、より強力な組み合わせをもたらします
隠れたメリット。コード量の削減と保守性の向上
欠点
性能:函数式编程相对于指令式编程,性能绝对是一个短板,因为它往往会对一个方法进行过度包装,从而产生上下文切换的性能开销
资源占用:在 JS 中为了实现对象状态的不可变,往往会创建新的对象,因此,它对垃圾回收所产生的压力远远超过其他编程方式
递归陷阱:在函数式编程中,为了实现迭代,通常会采用递归操作
3.3 什么是纯函数,它有什么优点
纯函数是对给定的输入返还相同输出的函数,并且要求你所有的数据都是不可变的,即纯函数=无状态+数据不可变
特性:
函数内部传入指定的值,就会返回确定唯一的值
不会造成超出作用域的变化,例如修改全局变量或引用传递的参数
优势:
使用纯函数,我们可以产生可测试的代码
不依赖外部环境计算,不会产生副作用,提高函数的复用性
可读性更强 ,函数不管是否是纯函数 都会有一个语义化的名称,更便于阅读
可以组装成复杂任务的可能性。符合模块化概念及单一职责原则
3.4 什么是组合函数 (compose)
在函数式编程中,有一个很重要的概念就是函数组合,实际上就是把处理的函数数据像管道一样连接起来,然后让数据穿过管道连接起来,得到最终的结果。
组合函数,其实大致思想就是将 多个函数组合成一个函数,c(b(a(a(1)))) 这种写法简写为 compose(c, b, a, a)(x) 。但是注意这里如果一个函数都没有传入,那就是传入的是什么就返回什么,并且函数的执行顺序是和传入的顺序相反的。
var compose = (...funcs) => { // funcs(数组):记录的是所有的函数 // 这里其实也是利用了柯里化的思想,函数执行,生成一个闭包,预先把一些信息存储,供下级上下文使用 return (x) => { var len = funcs.length; // 如果没有函数执行,直接返回结果 if (len === 0) return x; if (len === 1) funcs[0](x); return funcs.reduceRight((res, func) => { return func(res); }, x); }; }; var resFn = compose(c, b, a, a); resFn(1);
组合函数的思想,在很多框架中也被使用,例如:redux,实现效果来说是其实和上面的代码等价。
3.5 什么是惰性函数
惰性载入表示函数执行的分支只会在函数第一次掉用的时候执行,在第一次调用过程中,该函数会被覆盖为另一个按照合适方式执行的函数,这样任何对原函数的调用就不用再经过执行的分支了
惰性函数相当于有记忆的功能一样,当它已经判断了一遍的话,第二遍就不会再判断了。
比如现在要求写一个test函数,这个函数返回首次调用时的new Date().getTime(),注意是首次,而且不允许有全局变量的污染
//一般会这样实现 var test = (function () { var t = null; return function () { if (t) { return t; } t = new Date().getTime(); return t; } })(); // 用惰性函数实现 var test = function () { var t = new Date().getTime(); test = function () { return t; } return test(); } console.log(test()); console.log(test()); console.log(test());
3.6 什么是高阶函数
高阶函数是指使用其他函数作为参数、或者返回一个函数作为结果的函数。
3.7 说说你对函数柯里化的理解
柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。
函数柯里化的好处:
(1)参数复用:需要输入多个参数,最终只需输入一个,其余通过 arguments 来获取
(2)提前确认:避免重复去判断某一条件是否符合,不符合则 return 不再继续执行下面的操作
(3)延迟运行:避免重复的去执行程序,等真正需要结果的时候再执行
3.8 什么是箭头函数,有什么特征
使用 "箭头" ( => ) 来定义函数. 箭头函数相当于匿名函数, 并且简化了函数定义
箭头函数的特征:
箭头函数没有this, this指向定义箭头函数所处的外部环境
箭头函数的this永远不会变,call、apply、bind也无法改变
箭头函数只能声明成匿名函数,但可以通过表达式的方式让箭头函数具名
箭头函数没有原型prototype
箭头函数不能当做一个构造函数 因为 this 的指向问题
箭头函数没有 arguments 在箭头函数内部访问这个变量访问的是外部环境的arguments, 可以使用 ...代替
3.9 说说你对递归函数的理解
如果一个函数在内部调用自身本身,这个函数就是递归函数
其核心思想是把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解
一般来说,递归需要有边界条件、递归前进阶段和递归返回阶段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回
优点:结构清晰、可读性强
缺点:效率低、调用栈可能会溢出,其实每一次函数调用会在内存栈中分配空间,而每个进程的栈的容量是有限的,当调用的层次太多时,就会超出栈的容量,从而导致栈溢出。
3.10 什么是尾递归
尾递归,即在函数尾位置调用自身(或是一个尾调用本身的其他函数等等)。
在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储,递归次数过多容易造成栈溢出
这时候,我们就可以使用尾递归,即一个函数中所有递归形式的调用都出现在函数的末尾,对于尾递归来说,由于只存在一个调用记录,所以永远不会发生"栈溢出"错误
3.11 函数传参,传递复杂数据类型和简单数据类型有什么区别
传递复杂数据类型传递的是引用的地址,修改会改变
简单数据类型传递的是具体的值,不会相互影响
/* let a = 8 function fn(a) { a = 9 } fn(a) console.log(a) // 8 */ let a = { age: 8 } function fn(a) { a.age = 9 } fn(a) console.log(a.age) // 9
3.12 函数声明与函数表达式的区别
函数声明: funtion开头,有函数提升
函数表达式: 不是funtion开头,没有函数提升
3.13 什么是函数缓存,如何实现?
概念
函数缓存,就是将函数运算过的结果进行缓存
本质上就是用空间(缓存存储)换时间(计算过程)
常用于缓存数据计算结果和缓存对象
如何实现
实现函数缓存主要依靠闭包、柯里化、高阶函数
应用场景
对于昂贵的函数调用,执行复杂计算的函数
对于具有有限且高度重复输入范围的函数
对于具有重复输入值的递归函数
对于纯函数,即每次使用特定输入调用时返回相同输出的函数
3.14 call、apply、bind三者的异同
共同点 :
都可以改变this指向;
三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefined或null,则默认指向全局window
不同点:
call 和 apply 会调用函数, 并且改变函数内部this指向.
call 和 apply传递的参数不一样,call传递参数使用逗号隔开,apply使用数组传递,且apply和call是一次性传入参数,而bind可以分为多次传入
bind是返回绑定this之后的函数
应用场景
call 经常做继承.
apply经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的this指
【相关推荐:JavaScript视频教程、web前端】
以上がJavaScript 10,000語インタビューまとめの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。