70個JavaScript知識點詳細總結(下)【實踐】

70個JavaScript知識點詳細總結(下)【實踐】


英文原文地址:https://dev.to/macmacky/70-javascript-interview-questions-5gfi#61-what-are-the-ways-of-making-objects-in-javascript

上一篇1-34個JavaScript知識點總結請看《 》


好了,廢話少說,進入正題:

35. 手動實現Array.prototype.filter方法

filter() 方法創建一個新數組, 其包含通過所提供函數實現的測試的所有元素。

<code>function filter(arr, filterCallback) {
// 首先,檢查傳遞的參數是否正確。
if (!Array.isArray(arr) || !arr.length || typeof filterCallback !== 'function')
{
return [];
} else {
let result = [];
// 每次調用此函數時,我們都會創建一個 result 數組
// 因為我們不想改變原始數組。
for (let i = 0, len = arr.length; i < len; i++) {
// 檢查 filterCallback 的返回值是否是真值
if (filterCallback(arr[i], i, arr)) {
// 如果條件為真,則將數組元素 push 到 result 中
result.push(arr[i]);
}
}
return result; // return the result array
}

}/<code>

36. 手動實現Array.prototype.reduce方法

reduce() 方法對數組中的每個元素執行一個由您提供的reducer函數(升序執行),將其結果彙總為單個返回值。

<code>function reduce(arr, reduceCallback, initialValue) {
// 首先,檢查傳遞的參數是否正確。
if (!Array.isArray(arr) || !arr.length || typeof reduceCallback !== 'function')
{
return [];
} else {
// 如果沒有將initialValue傳遞給該函數,我們將使用第一個數組項作為initialValue
let hasInitialValue = initialValue !== undefined;
let value = hasInitialValue ? initialValue : arr[0];


// 如果有傳遞 initialValue,則索引從 1 開始,否則從 0 開始
for (let i = hasInitialValue ? 0 : 1, len = arr.length; i < len; i++) {
value = reduceCallback(value, arr[i], i, arr);
}
return value;
}
}/<code>

37. arguments 的對象是什麼?

arguments對象是函數中傳遞的參數值的集合。它是一個類似數組的對象,因為它有一個length屬性,我們可以使用數組索引表示法arguments[1]來訪問單個值,但它沒有數組中的內置方法,如:forEach、reduce、filter和map。

我們可以使用Array.prototype.slice將arguments對象轉換成一個數組。

<code>function one() {
return Array.prototype.slice.call(arguments);
}/<code>

注意:箭頭函數中沒有arguments對象。

<code>function one() {
return arguments;
}
const two = function () {
return arguments;
}
const three = function three() {
return arguments;
}

const four = () => arguments;

four(); // Throws an error - arguments is not defined/<code>

當我們調用函數four時,它會拋出一個ReferenceError: arguments is not defined error。使用rest語法,可以解決這個問題。

<code>const four = (...args) => args;/<code>

這會自動將所有參數值放入數組中。

38. 如何創建一個沒有 prototype(原型)的對象?

我們可以使用Object.create方法創建沒有原型的對象。

<code>const o1 = {};
console.log(o1.toString()); // [object Object]

const o2 = Object.create(null);
console.log(o2.toString());
// throws an error o2.toString is not a function/<code>

39. 為什麼在調用這個函數時,代碼中的b會變成一個全局變量?

<code>function myFunc() {
let a = b = 0;
}

myFunc();/<code>

原因是賦值運算符是從右到左的求值的。這意味著當多個賦值運算符出現在一個表達式中時,它們是從右向左求值的。所以上面代碼變成了這樣:

<code>function filter(arr, filterCallback) {
// 首先,檢查傳遞的參數是否正確。
if (!Array.isArray(arr) || !arr.length || typeof filterCallback !== 'function')
{
return [];
} else {
let result = [];
// 每次調用此函數時,我們都會創建一個 result 數組
// 因為我們不想改變原始數組。
for (let i = 0, len = arr.length; i < len; i++) {
// 檢查 filterCallback 的返回值是否是真值
if (filterCallback(arr[i], i, arr)) {
// 如果條件為真,則將數組元素 push 到 result 中
result.push(arr[i]);
}
}
return result; // return the result array
}
}/<code>

首先,表達式b = 0求值,在本例中b沒有聲明。因此,JS引擎在這個函數外創建了一個全局變量b,之後表達式b = 0的返回值為0,並賦給新的局部變量a。

我們可以通過在賦值之前先聲明變量來解決這個問題。

<code>function myFunc() {
let a,b;
a = b = 0;
}
myFunc();/<code>

40. ECMAScript 是什麼?


70個JavaScript知識點詳細總結(下)【實踐】


ECMAScript 是編寫腳本語言的標準,這意味著JavaScript遵循ECMAScript標準中的規範變化,因為它是JavaScript的藍圖。

ECMAScript 和 Javascript,本質上都跟一門語言有關,一個是語言本身的名字,一個是語言的約束條件只不過發明JavaScript的那個人(Netscape公司),把東西交給了ECMA(European Computer Manufacturers Association),這個人規定一下他的標準,因為當時有java語言了,又想強調這個東西是讓ECMA這個人定的規則,所以就這樣一個神奇的東西誕生了,這個東西的名稱就叫做ECMAScript。

javaScript = ECMAScript + DOM + BOM(自認為是一種廣義的JavaScript)

ECMAScript說什麼JavaScript就得做什麼!

JavaScript(狹義的JavaScript)做什麼都要問問ECMAScript我能不能這樣幹!如果不能我就錯了!能我就是對的!

——突然感覺JavaScript好沒有尊嚴,為啥要搞個人出來約束自己,

那個人被創造出來也好委屈,自己被創造出來完全是因為要約束JavaScript。

41. ES6或ECMAScript 2015有哪些新特性?

  • 箭頭函數
  • 模板字符串
  • 加強的對象字面量
  • 對象解構
  • Promise
  • 生成器
  • 模塊
  • Symbol
  • 代理
  • Set
  • 函數默認參數
  • rest 和展開
  • 塊作用域

42. var,let和const的區別是什麼?

var聲明的變量會掛載在window上,而let和const聲明的變量不會:

<code>function reduce(arr, reduceCallback, initialValue) {
// 首先,檢查傳遞的參數是否正確。
if (!Array.isArray(arr) || !arr.length || typeof reduceCallback !== 'function')
{
return [];
} else {
// 如果沒有將initialValue傳遞給該函數,我們將使用第一個數組項作為initialValue
let hasInitialValue = initialValue !== undefined;

let value = hasInitialValue ? initialValue : arr[0];


// 如果有傳遞 initialValue,則索引從 1 開始,否則從 0 開始
for (let i = hasInitialValue ? 0 : 1, len = arr.length; i < len; i++) {
value = reduceCallback(value, arr[i], i, arr);
}
return value;
}
}/<code>

var聲明變量存在變量提升,let和const不存在變量提升:

<code>console.log(a); // undefined  ===>  a已聲明還沒賦值,默認得到undefined值
var a = 100;

console.log(b); // 報錯:b is not defined ===> 找不到b這個變量
let b = 10;

console.log(c); // 報錯:c is not defined ===> 找不到c這個變量
const c = 10;/<code>
<code>if(1){
var a = 100;
let b = 10;
}

console.log(a); // 100
console.log(b) // 報錯:b is not defined ===> 找不到b這個變量

-------------------------------------------------------------

if(1){
var a = 100;
const c = 1;
}
console.log(a); // 100
console.log(c) // 報錯:c is not defined ===> 找不到c這個變量/<code>

同一作用域下let和const不能聲明同名變量,而var可以

<code>var a = 100;
console.log(a); // 100

var a = 10;
console.log(a); // 10
-------------------------------------
let a = 100;
let a = 10;

// 控制檯報錯:Identifier 'a' has already been declared ===> 標識符a已經被聲明瞭。/<code>

暫存死區

<code>var a = 100;

if(1){
a = 10;
//在當前塊作用域中存在a使用let/const聲明的情況下,給a賦值10時,只會在當前作用域找變量a,
// 而這時,還未到聲明時候,所以控制檯Error:a is not defined
let a = 1;
}/<code>

const

<code>/*
*   1、一旦聲明必須賦值,不能使用null佔位。
*
*   2、聲明後不能再修改
*
*   3、如果聲明的是複合類型數據,可以修改其屬性
*
* */

const a = 100;

const list = [];
list[0] = 10;
console.log(list);  // [10]


const obj = {a:100};
obj.name = 'apple';
obj.a = 10000;
console.log(obj);  // {a:10000,name:'apple'}/<code>

43. 什麼是箭頭函數?

箭頭函數表達式的語法比函數表達式更簡潔,並且沒有自己的this,arguments,super或new.target。箭頭函數表達式更適用於那些本來需要匿名函數的地方,並且它不能用作構造函數。

<code>//ES5 Version
var getCurrentDate = function (){
return new Date();
}

//ES6 Version
const getCurrentDate = () => new Date();/<code>

在本例中,ES5 版本中有function(){}聲明和return關鍵字,這兩個關鍵字分別是創建函數和返回值所需要的。在箭頭函數版本中,我們只需要()括號,不需要 return 語句,因為如果我們只有一個表達式或值需要返回,箭頭函數就會有一個隱式的返回。

<code>function one() {
return Array.prototype.slice.call(arguments);
}/<code>

我們還可以在箭頭函數中使用與函數表達式和函數聲明相同的參數。如果我們在一個箭頭函數中有一個參數,則可以省略括號。

<code>function one() {
return arguments;

}
const two = function () {
return arguments;
}
const three = function three() {
return arguments;
}

const four = () => arguments;

four(); // Throws an error - arguments is not defined/<code>

箭頭函數不能訪問arguments對象。所以調用第一個getArgs函數會拋出一個錯誤。相反,我們可以使用rest參數來獲得在箭頭函數中傳遞的所有參數。

<code>const four = (...args) => args;/<code>

箭頭函數沒有自己的this值。它捕獲詞法作用域函數的this值,在此示例中,addAll函數將複製computeResult 方法中的this值,如果我們在全局作用域聲明箭頭函數,則this值為 window 對象。

44. 什麼是類?

類(class)是在 JS 中編寫構造函數的新方法。它是使用構造函數的語法糖,在底層中使用仍然是原型和基於原型的繼承。

<code>const o1 = {};
console.log(o1.toString()); // [object Object]

const o2 = Object.create(null);
console.log(o2.toString());
// throws an error o2.toString is not a function/<code>

重寫方法並從另一個類繼承。

<code>//ES5 Version 

Employee.prototype = Object.create(Person.prototype);

function Employee(firstName, lastName, age, address, jobTitle, yearStarted) {
Person.call(this, firstName, lastName, age, address);
this.jobTitle = jobTitle;
this.yearStarted = yearStarted;
}

Employee.prototype.describe = function () {
return `I am ${this.getFullName()} and I have a position of ${this.jobTitle}
and I started at ${this.yearStarted}`;
}

Employee.prototype.toString = function () {
return "[object Employee]";
}

//ES6 Version
class Employee extends Person { //Inherits from "Person" class
constructor(firstName, lastName, age, address, jobTitle, yearStarted) {
super(firstName, lastName, age, address);
this.jobTitle = jobTitle;
this.yearStarted = yearStarted;
}

describe() {
return `I am ${this.getFullName()} and I have a position of ${this.jobTitle}
and I started at ${this.yearStarted}`;
}

toString() { // Overriding the "toString" method of "Person"
return "[object Employee]";
}
}/<code>

所以我們要怎麼知道它在內部使用原型?

<code>class Something {

}

function AnotherSomething(){

}
const as = new AnotherSomething();
const s = new Something();

console.log(typeof Something); // "function"
console.log(typeof AnotherSomething); // "function"

console.log(as.toString()); // "[object Object]"
console.log(as.toString()); // "[object Object]"
console.log(as.toString === Object.prototype.toString); // true
console.log(s.toString === Object.prototype.toString); // true/<code>

45. 什麼是模板字符串?

模板字符串是在 JS 中創建字符串的一種新方法。我們可以通過使用反引號使模板字符串化。

<code>function myFunc() {
let a = b = 0;
}

myFunc();/<code>

在 ES5 中我們需要使用一些轉義字符來達到多行的效果,在模板字符串不需要這麼麻煩:

<code>function myFunc() {
let a = (b = 0);
}

myFunc();/<code>

在ES5版本中,我們需要添加\\n以在字符串中添加新行。在模板字符串中,我們不需要這樣做。

<code>//ES5 Version
function greet(name) {
return 'Hello ' + name + '!';
}


//ES6 Version
function greet(name) {
return `Hello ${name} !`;
}/<code>

在 ES5 版本中,如果需要在字符串中添加表達式或值,則需要使用+運算符。在模板字符串s中,我們可以使用${expr}嵌入一個表達式,這使其比 ES5 版本更整潔。

46. 什麼是對象解構?

對象析構是從對象或數組中獲取或提取值的一種新的、更簡潔的方法。假設有如下的對象:

<code>function myFunc() {
let a,b;
a = b = 0;
}
myFunc();/<code>

從對象獲取屬性,早期方法是創建一個與對象屬性同名的變量。這種方法很麻煩,因為我們要為每個屬性創建一個新變量。假設我們有一個大對象,它有很多屬性和方法,用這種方法提取屬性會很麻煩。

<code>var firstName = employee.firstName;
var lastName = employee.lastName;
var position = employee.position;
var yearHired = employee.yearHired;/<code>

使用解構方式語法就變得簡潔多了:

<code>{ firstName, lastName, position, yearHired } = employee;/<code>

我們還可以為屬性取別名:

<code>let { firstName: fName, lastName: lName, position, yearHired } = employee;/<code>

當然如果屬性值為 undefined 時,我們還可以指定默認值:

<code>let { firstName = "Mark", lastName: lName, position, yearHired } = employee;/<code>

47. 什麼是 ES6 模塊?

模塊使我們能夠將代碼基礎分割成多個文件,以獲得更高的可維護性,並且避免將所有代碼放在一個大文件中。在 ES6 支持模塊之前,有兩個流行的模塊。

  • CommonJS-Node.js
  • AMD(異步模塊定義)-瀏覽器

基本上,使用模塊的方式很簡單,import用於從另一個文件中獲取功能或幾個功能或值,同時export用於從文件中公開功能或幾個功能或值。

導出

使用 ES5 (CommonJS)

<code>// 使用 ES5 CommonJS - helpers.js
exports.isNull = function (val) {
return val === null;
}

exports.isUndefined = function (val) {
return val === undefined;
}

exports.isNullOrUndefined = function (val) {
return exports.isNull(val) || exports.isUndefined(val);
}/<code>

使用 ES6 模塊

<code>// 使用 ES6 Modules - helpers.js
export function isNull(val){
return val === null;
}

export function isUndefined(val) {

return val === undefined;
}

export function isNullOrUndefined(val) {
return isNull(val) || isUndefined(val);
}/<code>

在另一個文件中導入函數

<code>// 使用 ES5 (CommonJS) - index.js
const helpers = require('./helpers.js'); // helpers is an object
const isNull = helpers.isNull;
const isUndefined = helpers.isUndefined;
const isNullOrUndefined = helpers.isNullOrUndefined;

// or if your environment supports Destructuring
const { isNull, isUndefined, isNullOrUndefined } = require('./helpers.js');
-------------------------------------------------------

// ES6 Modules - index.js
import * as helpers from './helpers.js'; // helpers is an object

// or

import { isNull, isUndefined, isNullOrUndefined as isValid } from './helpers.js';

// using "as" for renaming named exports/<code>

在文件中導出單個功能或默認導出

使用 ES5 (CommonJS)

<code>// 使用 ES5 (CommonJS) - index.js
class Helpers {
static isNull(val) {
return val === null;
}

static isUndefined(val) {
return val === undefined;
}

static isNullOrUndefined(val) {
return this.isNull(val) || this.isUndefined(val);
}
}


module.exports = Helpers;/<code>

使用ES6 Modules

<code>// 使用 ES6 Modules - helpers.js
class Helpers {
static isNull(val) {
return val === null;
}

static isUndefined(val) {
return val === undefined;
}

static isNullOrUndefined(val) {
return this.isNull(val) || this.isUndefined(val);
}
}

export default Helpers/<code>

從另一個文件導入單個功能

使用ES5 (CommonJS)

<code>// 使用 ES5 (CommonJS) - index.js
const Helpers = require('./helpers.js');
console.log(Helpers.isNull(null));/<code>

使用 ES6 Modules

<code>import Helpers from '.helpers.js'
console.log(Helpers.isNull(null));/<code>

48. 什麼是Set對象,它是如何工作的?

Set 對象允許你存儲任何類型的唯一值,無論是原始值或者是對象引用。

我們可以使用Set構造函數創建Set實例。

<code>const set1 = new Set(); 

const set2 = new Set(["a","b","c","d","d","e"]);/<code>

我們可以使用add方法向Set實例中添加一個新值,因為add方法返回Set對象,所以我們可以以鏈式的方式再次使用add。如果一個值已經存在於Set對象中,那麼它將不再被添加。

<code>set2.add("f");
set2.add("g").add("h").add("i").add("j").add("k").add("k");
// 後一個“k”不會被添加到set對象中,因為它已經存在了/<code>

我們可以使用has方法檢查Set實例中是否存在特定的值。

<code>set2.has("a") // true
set2.has("z") // true/<code>

我們可以使用size屬性獲得Set實例的長度。

<code>set2.size // returns 10/<code>

可以使用clear方法刪除 Set 中的數據。

<code>set2.clear();/<code>

我們可以使用Set對象來刪除數組中重複的元素。

<code>const numbers = [1, 2, 3, 4, 5, 6, 6, 7, 8, 8, 5];
const uniqueNums = [...new Set(numbers)]; // [1,2,3,4,5,6,7,8]/<code>

49. 什麼是回調函數?

回調函數是一段可執行的代碼段,它作為一個參數傳遞給其他的代碼,其作用是在需要的時候方便調用這段(回調函數)代碼。

在JavaScript中函數也是對象的一種,同樣對象可以作為參數傳遞給函數,因此函數也可以作為參數傳遞給另外一個函數,這個作為參數的函數就是回調函數。

<code>const btnAdd = document.getElementById('btnAdd');

btnAdd.addEventListener('click', function clickCallback(e) {
// do something useless
});/<code>

在本例中,我們等待id為btnAdd的元素中的click事件,如果它被單擊,則執行clickCallback函數。回調函數向某些數據或事件添加一些功能。

數組中的reduce、filter和map方法需要一個回調作為參數。回調的一個很好的類比是,當你打電話給某人,如果他們不接,你留下一條消息,你期待他們回調。調用某人或留下消息的行為是事件或數據,回調是你希望稍後發生的操作。

70個JavaScript知識點詳細總結(下)【實踐】

50. Promise 是什麼?

Promise 是異步編程的一種解決方案:從語法上講,promise是一個對象,從它可以獲取異步操作的消息;從本意上講,它是承諾,承諾它過一段時間會給你一個結果。promise有三種狀態:pending(等待態),fulfiled(成功態),rejected(失敗態);狀態一旦改變,就不會再變。創造promise實例後,它會立即執行。

<code>fs.readFile('somefile.txt', function (e, data) {
if (e) {
console.log(e);
}
console.log(data);
});/<code>

如果我們在回調內部有另一個異步操作,則此方法存在問題。我們將有一個混亂且不可讀的代碼。此代碼稱為“回調地獄”。

<code>// 回調地獄
fs.readFile('somefile.txt', function (e, data) {
//your code here
fs.readdir('directory', function (e, files) {
//your code here
fs.mkdir('directory', function (e) {
//your code here
})
})
})/<code>

如果我們在這段代碼中使用promise,它將更易於閱讀、理解和維護。

<code>promReadFile('file/path')
.then(data => {

return promReaddir('directory');
})
.then(data => {
return promMkdir('directory');
})
.catch(e => {
console.log(e);
})/<code>

promise有三種不同的狀態:

  • pending:初始狀態,完成或失敗狀態的前一個狀態
  • fulfilled:操作成功完成
  • rejected:操作失敗

pending 狀態的 Promise 對象會觸發 fulfilled/rejected 狀態,在其狀態處理方法中可以傳入參數/失敗信息。當操作成功完成時,Promise 對象的 then 方法就會被調用;否則就會觸發 catch。如:

<code>const myFirstPromise = new Promise((resolve, reject) => {
setTimeout(function(){
resolve("成功!");
}, 250);
});

myFirstPromise.then((data) => {
console.log("Yay! " + data);
}).catch((e) => {...});/<code>

51. 什麼是 async/await 及其如何工作?

async/await是 JS 中編寫異步或非阻塞代碼的新方法。它建立在Promises之上,讓異步代碼的可讀性和簡潔度都更高。

async/await是 JS 中編寫異步或非阻塞代碼的新方法。它建立在Promises之上,相對於 Promise 和回調,它的可讀性和簡潔度都更高。但是,在使用此功能之前,我們必須先學習Promises的基礎知識,因為正如我之前所說,它是基於Promise構建的,這意味著幕後使用仍然是Promise。

使用 Promise

<code>var a = 100;
console.log(a,window.a); // 100 100

let b = 10;
console.log(b,window.b); // 10 undefined

const c = 1;
console.log(c,window.c); // 1 undefined/<code>

使用async/await

在async/await,我們使用 tru/catch 語法來捕獲異常。

<code>console.log(a); // undefined  ===>  a已聲明還沒賦值,默認得到undefined值
var a = 100;

console.log(b); // 報錯:b is not defined ===> 找不到b這個變量
let b = 10;

console.log(c); // 報錯:c is not defined ===> 找不到c這個變量
const c = 10;/<code>

注意:使用 async關鍵聲明函數會隱式返回一個Promise。

<code>if(1){
var a = 100;
let b = 10;
}

console.log(a); // 100

console.log(b) // 報錯:b is not defined ===> 找不到b這個變量

-------------------------------------------------------------

if(1){
var a = 100;
const c = 1;
}
console.log(a); // 100
console.log(c) // 報錯:c is not defined ===> 找不到c這個變量/<code>

注意:await關鍵字只能在async function中使用。在任何非async function的函數中使用await關鍵字都會拋出錯誤。await關鍵字在執行下一行代碼之前等待右側表達式(可能是一個Promise)返回。

<code>var a = 100;
console.log(a); // 100

var a = 10;
console.log(a); // 10
-------------------------------------
let a = 100;
let a = 10;

// 控制檯報錯:Identifier 'a' has already been declared ===> 標識符a已經被聲明瞭。/<code>

52. 展開(spread )運算符和 剩餘(Rest) 運算符有什麼區別?

展開運算符(spread)是三個點(...),可以將一個數組轉為用逗號分隔的參數序列。說的通俗易懂點,有點像化骨綿掌,把一個大元素給打散成一個個單獨的小元素。

剩餘運算符也是用三個點(...)表示,它的樣子看起來和展開操作符一樣,但是它是用於解構數組和對象。在某種程度上,剩餘元素和展開元素相反,展開元素會“展開”數組變成多個元素,剩餘元素會收集多個元素和“壓縮”成一個單一的元素。

<code>var a = 100;

if(1){
a = 10;
//在當前塊作用域中存在a使用let/const聲明的情況下,給a賦值10時,只會在當前作用域找變量a,
// 而這時,還未到聲明時候,所以控制檯Error:a is not defined
let a = 1;
}/<code>

在本例中,我們在調用add函數時使用了展開操作符,對nums數組進行展開。所以參數a的值是5 ,b的值是6,所以sum 是11。

<code>/*
*   1、一旦聲明必須賦值,不能使用null佔位。
*
*   2、聲明後不能再修改
*
*   3、如果聲明的是複合類型數據,可以修改其屬性
*
* */

const a = 100;

const list = [];
list[0] = 10;
console.log(list);  // [10]

const obj = {a:100};
obj.name = 'apple';
obj.a = 10000;
console.log(obj);  // {a:10000,name:'apple'}/<code>

在本例中,我們有一個add函數,它接受任意數量的參數,並將它們全部相加,然後返回總數。

<code>const [first, ...others] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(others); // [2,3,4,5]/<code>

這裡,我們使用剩餘操作符提取所有剩餘的數組值,並將它們放入除第一項之外的其他數組中。

53. 什麼是默認參數?

默認參數是在 JS 中定義默認變量的一種新方法,它在ES6或ECMAScript 2015版本中可用。

<code>//ES5 Version
var getCurrentDate = function (){
return new Date();
}

//ES6 Version
const getCurrentDate = () => new Date();/<code>

我們還可以在默認參數中使用解構。

<code>//ES5 Version
function greet(name) {
return 'Hello ' + name + '!';
}

//ES6 Version
const greet = (name) => `Hello ${name}`;
const greet2 = name => `Hello ${name}`;/<code>

我們還可以使用先定義的參數再定義它們之後的參數。

<code>const getArgs = () => arguments

const getArgs2 = (...rest) => rest/<code>

54. 什麼是包裝對象(wrapper object)?

我們現在複習一下JS的數據類型,JS數據類型被分為兩大類,基本類型和引用類型。

基本類型:Undefined,Null,Boolean,Number,String,Symbol,BigInt

引用類型:Object,Array,Date,RegExp等,說白了就是對象。

其中引用類型有方法和屬性,但是基本類型是沒有的,但我們經常會看到下面的代碼:

<code>const data = {
result: 0,
nums: [1, 2, 3, 4, 5],
computeResult() {
// 這裡的“this”指的是“data”對象
const addAll = () => {
return this.nums.reduce((total, cur) => total + cur, 0)
};
this.result = addAll();
}
};/<code>

name類型是 string,屬於基本類型,所以它沒有屬性和方法,但是在這個例子中,我們調用了一個toUpperCase()方法,它不會拋出錯誤,還返回了對象的變量值。

原因是基本類型的值被臨時轉換或強制轉換為對象,因此name變量的行為類似於對象。除null和undefined之外的每個基本類型都有自己包裝對象。也就是:String,Number,Boolean,Symbol和BigInt。在這種情況下,name.toUpperCase()在幕後看起來如下:

<code>console.log(new String(name).toUpperCase()); // "MARKO"/<code>

在完成訪問屬性或調用方法之後,新創建的對象將立即被丟棄。

55. 隱式和顯式轉換有什麼區別)?

隱式強制轉換是一種將值轉換為另一種類型的方法,這個過程是自動完成的,無需我們手動操作。

假設我們下面有一個例子。

<code>   //ES5 Version
function Person(firstName, lastName, age, address){
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.address = address;
}

Person.self = function(){
return this;
}

Person.prototype.toString = function(){
return "[object Person]";
}

Person.prototype.getFullName = function (){
return this.firstName + " " + this.lastName;
}

//ES6 Version
class Person {
constructor(firstName, lastName, age, address){
this.lastName = lastName;
this.firstName = firstName;
this.age = age;
this.address = address;
}

static self() {
return this;
}

toString(){
return "[object Person]";
}

getFullName(){
return `${this.firstName} ${this.lastName}`;
}
}/<code>

第一個console.log語句結果為16。在其他語言中,這會拋出編譯時錯誤,但在 JS 中,1被轉換成字符串,然後與+運算符連接。我們沒有做任何事情,它是由 JS 自動完成。

第二個console.log語句結果為1,JS 將false轉換為boolean 值為 0,,true為1,因此結果為1。

第三個console.log語句結果12,它將'2'轉換為一個數字,然後乘以6 * 2,結果是12。

而顯式強制是將值轉換為另一種類型的方法,我們需要手動轉換。

<code>//ES5 Version
Employee.prototype = Object.create(Person.prototype);

function Employee(firstName, lastName, age, address, jobTitle, yearStarted) {
Person.call(this, firstName, lastName, age, address);
this.jobTitle = jobTitle;
this.yearStarted = yearStarted;
}

Employee.prototype.describe = function () {
return `I am ${this.getFullName()} and I have a position of ${this.jobTitle}
and I started at ${this.yearStarted}`;
}

Employee.prototype.toString = function () {
return "[object Employee]";
}

//ES6 Version

class Employee extends Person { //Inherits from "Person" class
constructor(firstName, lastName, age, address, jobTitle, yearStarted) {
super(firstName, lastName, age, address);
this.jobTitle = jobTitle;
this.yearStarted = yearStarted;
}

describe() {
return `I am ${this.getFullName()} and I have a position of ${this.jobTitle}
and I started at ${this.yearStarted}`;
}

toString() { // Overriding the "toString" method of "Person"
return "[object Employee]";
}
}/<code>

在本例中,我們使用parseInt函數將'6'轉換為number ,然後使用+運算符將1和6相加。

56. 什麼是NaN?以及如何檢查值是否為NaN?

NaN表示“非數字”是 JS 中的一個值,該值是將數字轉換或執行為非數字值的運算結果,因此結果為NaN。

<code>class Something {

}

function AnotherSomething(){

}
const as = new AnotherSomething();
const s = new Something();

console.log(typeof Something); // "function"
console.log(typeof AnotherSomething); // "function"
console.log(as.toString()); // "[object Object]"
console.log(as.toString()); // "[object Object]"
console.log(as.toString === Object.prototype.toString); // true
console.log(s.toString === Object.prototype.toString); // true/<code>

JS 有一個內置的isNaN方法,用於測試值是否為isNaN值,但是這個函數有一個奇怪的行為。

<code>console.log(isNaN()); // true
console.log(isNaN(undefined)); // true
console.log(isNaN({})); // true
console.log(isNaN(String('a'))); // true
console.log(isNaN(() => { })); // true/<code>

所有這些console.log語句都返回true,即使我們傳遞的值不是NaN。

在ES6中,建議使用Number.isNaN方法,因為它確實會檢查該值(如果確實是NaN),或者我們可以使自己的輔助函數檢查此問題,因為在 JS 中,NaN是唯一的值,它不等於自己。

<code>function checkIfNaN(value) {
return value !== value;
}/<code>

57. 如何判斷值是否為數組?

我們可以使用Array.isArray方法來檢查值是否為數組。當傳遞給它的參數是數組時,它返回true,否則返回false。

<code>//ES5 Version
var greet = 'Hi I\\'m Mark';

//ES6 Version
let greet = `Hi I'm Mark`;/<code>

如果環境不支持此方法,則可以使用polyfill實現。

<code>//ES5 Version
var lastWords = '\\n'
+ ' I \\n'
+ ' Am \\n'
+ 'Iron Man \\n';


//ES6 Version

let lastWords = `
I
Am
Iron Man
`;/<code>

當然還可以使用傳統的方法:

<code>//ES5 Version
function greet(name) {
return 'Hello ' + name + '!';
}


//ES6 Version
function greet(name) {
return `Hello ${name} !`;
}/<code>

58. 如何在不使用%模運算符的情況下檢查一個數字是否是偶數?

我們可以對這個問題使用按位&運算符,&對其操作數進行運算,並將其視為二進制值,然後執行與運算。

<code>function isEven(num) {
if (num & 1) {
return false
} else {
return true
}
}/<code>

以此類推...

與運算的規則如下:

70個JavaScript知識點詳細總結(下)【實踐】

aba & b000010111

因此,當我們執行console.log(5&1)這個表達式時,結果為1。首先,&運算符將兩個數字都轉換為二進制,因此5變為101,1變為001。

然後,它使用按位懷運算符比較每個位(0和1)。 101&001,從表中可以看出,如果a & b為1,所以5&1結果為1。


70個JavaScript知識點詳細總結(下)【實踐】


  • 首先我們比較最左邊的1&0,結果是0。
  • 然後我們比較中間的0&0,結果是0。
  • 然後我們比較最後1&1,結果是1。
  • 最後,得到一個二進制數001,對應的十進制數,即1。

由此我們也可以算出console.log(4 & 1) 結果為0。知道4的最後一位是0,而0 & 1 將是0。如果你很難理解這一點,我們可以使用遞歸函數來解決此問題。

<code>function isEven(num) {if (num < 0 || num === 1) return false;if (num == 0) return true;
return isEven(num - 2);}/<code>

59. 如何檢查對象中是否存在某個屬性?

檢查對象中是否存在屬性有三種方法。

第一種使用 in 操作符號:

<code>const employee = {
firstName: "Marko",
lastName: "Polo",
position: "Software Developer",
yearHired: 2017
};/<code>

第二種使用 hasOwnProperty 方法,hasOwnProperty() 方法會返回一個布爾值,指示對象自身屬性中是否具有指定的屬性(也就是,是否有指定的鍵)。

<code>var firstName = employee.firstName;
var lastName = employee.lastName;
var position = employee.position;
var yearHired = employee.yearHired;/<code>

第三種使用括號符號obj["prop"]。如果屬性存在,它將返回該屬性的值,否則將返回undefined。

<code>{ firstName, lastName, position, yearHired } = employee;/<code>

60. AJAX 是什麼?

即異步的 JavaScript 和 XML,是一種用於創建快速動態網頁的技術,傳統的網頁(不使用 AJAX)如果需要更新內容,必需重載整個網頁面。使用AJAX則不需要加載更新整個網頁,實現部分內容更新

用到AJAX的技術:

  • HTML - 網頁結構
  • CSS - 網頁的樣式
  • JavaScript - 操作網頁的行為和更新DOM
  • XMLHttpRequest API - 用於從服務器發送和獲取數據
  • PHP,Python,Nodejs - 某些服務器端語言

61. 如何在 JS 中創建對象?

使用對象字面量:

<code>let { firstName: fName, lastName: lName, position, yearHired } = employee;/<code> 

使用構造函數:

<code>let { firstName = "Mark", lastName: lName, position, yearHired } = employee;/<code>

使用 Object.create 方法:

<code>const n = {
greeting() {
return `Hi, I'm ${this.name}`;
}
};

const o = Object.create(n); // sets the prototype of "o" to be "n"

o.name = "Mark";

console.log(o.greeting()); // logs "Hi, I'm Mark"/<code>

62. Object.seal 和 Object.freeze 方法之間有什麼區別?

這兩種方法之間的區別在於,當我們對一個對象使用Object.freeze方法時,該對象的屬性是不可變的,這意味著我們不能更改或編輯這些屬性的值。而在Obj.Engor方法中,我們可以改變現有的屬性。

Object.freeze()

Object.freeze() 方法可以凍結一個對象。一個被凍結的對象再也不能被修改;凍結了一個對象則不能向這個對象添加新的屬性,不能刪除已有屬性,不能修改該對象已有屬性的可枚舉性、可配置性、可寫性,以及不能修改已有屬性的值。此外,凍結一個對象後該對象的原型也不能被修改。freeze() 返回和傳入的參數相同的對象。

Object.seal()

Object.seal()方法封閉一個對象,阻止添加新屬性並將所有現有屬性標記為不可配置。當前屬性的值只要可寫就可以改變。

方法的相同點:

  1. ES5新增。
  2. 對象不可能擴展,也就是不能再添加新的屬性或者方法。
  3. 對象已有屬性不允許被刪除。
  4. 對象屬性特性不可以重新配置。


方法不同點:

  • Object.seal方法生成的密封對象,如果屬性是可寫的,那麼可以修改屬性值。
  • Object.freeze方法生成的凍結對象,屬性都是不可寫的,也就是屬性值無法更改。

63. in 運算符和 Object.hasOwnProperty 方法有什麼區別?

如你所知,這兩個特性都檢查對象中是否存在屬性,它將返回truefalse。它們之間的區別在於,in操作符還會檢查對象的原型鏈,如果屬性在當前對象中沒有找到,而hasOwnProperty方法只檢查屬性是否存在於當前對象中,而忽略原型鏈。

hasOwnPropert方法

hasOwnPropert()方法返回值是一個布爾值,指示對象自身屬性中是否具有指定的屬性,因此這個方法會忽略掉那些從原型鏈上繼承到的屬性。

看下面的例子:

<code>// 使用 ES5 CommonJS - helpers.js
exports.isNull = function (val) {
return val === null;
}

exports.isUndefined = function (val) {
return val === undefined;
}

exports.isNullOrUndefined = function (val) {
return exports.isNull(val) || exports.isUndefined(val);
}/<code>

可以看到,如果在函數原型上定義一個變量phone,hasOwnProperty方法會直接忽略掉。

in 運算符

如果指定的屬性在指定的對象或其原型鏈中,則in 運算符返回true。

還是用上面的例子來演示:

<code>// 使用 ES6 Modules - helpers.js
export function isNull(val){
return val === null;
}

export function isUndefined(val) {
return val === undefined;
}

export function isNullOrUndefined(val) {
return isNull(val) || isUndefined(val);
}/<code>

可以看到in運算符會檢查它或者其原型鏈是否包含具有指定名稱的屬性。

64. 有哪些方法可以處理 JS 中的異步代碼?

  • 回調
  • Promise
  • async/await
  • 還有一些庫:async.js, bluebird, q, co

65. 函數表達式和函數聲明之間有什麼區別?

看下面的例子:

<code>// 使用 ES5 (CommonJS) - index.js
const helpers = require('./helpers.js'); // helpers is an object
const isNull = helpers.isNull;
const isUndefined = helpers.isUndefined;
const isNullOrUndefined = helpers.isNullOrUndefined;

// or if your environment supports Destructuring
const { isNull, isUndefined, isNullOrUndefined } = require('./helpers.js');
-------------------------------------------------------

// ES6 Modules - index.js
import * as helpers from './helpers.js'; // helpers is an object

// or

import { isNull, isUndefined, isNullOrUndefined as isValid } from './helpers.js';

// using "as" for renaming named exports/<code>

notHoistedFunc調用拋出異常:Uncaught TypeError: notHoistedFunc is not a function,而hoistedFunc調用不會,因為hoistedFunc會被提升到作用域的頂部,而notHoistedFunc 不會。

66. 調用函數,可以使用哪些方法?

在 JS 中有4種方法可以調用函數。

  • 作為函數調用——如果一個函數沒有作為方法、構造函數、apply、call 調用時,此時 this 指向的是 window 對象(非嚴格模式)
<code>// 使用 ES5 (CommonJS) - index.js
class Helpers {
static isNull(val) {
return val === null;
}

static isUndefined(val) {
return val === undefined;
}

static isNullOrUndefined(val) {
return this.isNull(val) || this.isUndefined(val);
}
}


module.exports = Helpers;/<code>
  • 作為方法調用——如果一個對象的屬性有一個函數的值,我們就稱它為方法。調用該方法時,該方法的this值指向該對象。
<code>const details = {
name : "Marko",
getName(){
return this.name;
}
}

details.getName(); // Marko
// the "this" value inside "getName" method will be the "details" object/<code>
  • 作為構造函數的調用-如果在函數之前使用new關鍵字調用了函數,則該函數稱為構造函數。構造函數里面會默認創建一個空對象,並將this指向該對象。
<code>function Employee(name, position, yearHired) {
// creates an empty object {}
// then assigns the empty object to the "this" keyword
// this = {};
this.name = name;
this.position = position;
this.yearHired = yearHired;
// inherits from Employee.prototype
// returns the "this" value implicitly if no
// explicit return statement is specified
};

const emp = new Employee("Marko Polo", "Software Developer", 2017);/<code>
  • 使用apply和call方法調用——如果我們想顯式地指定一個函數的this值,我們可以使用這些方法,這些方法對所有函數都可用。
<code>const obj1 = {
result:0
};

const obj2 = {
result:0
};


function reduceAdd(){
let result = 0;
for(let i = 0, len = arguments.length; i < len; i++){
result += arguments[i];
}

this.result = result;
}


reduceAdd.apply(obj1, [1, 2, 3, 4, 5]); // reduceAdd 函數中的 this 對象將是 obj1
reduceAdd.call(obj2, 1, 2, 3, 4, 5); // reduceAdd 函數中的 this 對象將是 obj2/<code>

67. 什麼是緩存及它有什麼作用?

緩存是建立一個函數的過程,這個函數能夠記住之前計算的結果或值。使用緩存函數是為了避免在最後一次使用相同參數的計算中已經執行的函數的計算。這節省了時間,但也有不利的一面,即我們將消耗更多的內存來保存以前的結果。

68. 手動實現緩存方法

<code>// 使用 ES6 Modules - helpers.js
class Helpers {
static isNull(val) {
return val === null;
}

static isUndefined(val) {
return val === undefined;
}

static isNullOrUndefined(val) {
return this.isNull(val) || this.isUndefined(val);
}
}

export default Helpers/<code>

這個緩存函數適用於接受一個參數。我們需要改變下,讓它接受多個參數。

<code>const slice = Array.prototype.slice; 

function memoize(fn) {
const cache = {};
return (...args) => {
const params = slice.call(args);
console.log(params);
if (cache[params]) {
console.log('cached');
return cache[params];
} else {
let result = fn(...args);
cache[params] = result;
console.log(`not cached`);
return result;
}
}
}
const makeFullName = (fName, lName) => `${fName} ${lName}`;
const reduceAdd = (numbers, startingValue = 0) =>
numbers.reduce((total, cur) => total + cur, startingValue);

const memoizedMakeFullName = memoize(makeFullName);
const memoizedReduceAdd = memoize(reduceAdd);

memoizedMakeFullName("Marko", "Polo");
memoizedMakeFullName("Marko", "Polo");

memoizedReduceAdd([1, 2, 3, 4, 5], 5);
memoizedReduceAdd([1, 2, 3, 4, 5], 5);/<code>

69. 為什麼typeof null 返回 object?如何檢查一個值是否為 null?

typeof null == 'object'總是返回true,因為這是自 JS 誕生以來null的實現。曾經有人提出將typeof null == 'object'修改為typeof null == 'null',但是被拒絕了,因為這將導致更多的bug。

我們可以使用嚴格相等運算符===來檢查值是否為null。

<code>// 使用 ES5 (CommonJS) - index.js
const Helpers = require('./helpers.js');
console.log(Helpers.isNull(null));/<code>

70. new 關鍵字有什麼作用?

new關鍵字與構造函數一起使用以創建對象在JavaScript中。

下面看看例子:

<code>import Helpers from '.helpers.js'
console.log(Helpers.isNull(null));/<code>

new關鍵字做了4件事:

  • 創建空對象 {}
  • 將空對象分配給 this 值
  • 將空對象的__proto__指向構造函數的prototype
  • 如果沒有使用顯式return語句,則返回this

根據上面描述的,它將首先創建一個空對象{},然後它將this值賦給這個空對象this={},並向這個對象添加屬性。因為我們沒有顯式的return語句,所以它會自動為我們返回this。

70個JS知識點總結,已完結!

推薦JS相關文章

另外,前面一段時間,小編也陸陸續續整理了不少的JavaSxript 知識點的相關文章,不知小夥們都學的怎麼樣了?咱們一起來回顧一下:

《 》

《 》

《 》

《 》

《 》

《 》

《 》

《 》


英文原文地址:https://dev.to/macmacky/70-javascript-interview-questions-5gfi#61-what-are-the-ways-of-making-objects-in-javascript


分享到:


相關文章: