摘要:復雜數據類型變量,賦值給之后,只讀引用與關聯,和中存儲的是同一個對象的堆內存地址,當這個對象的值發生改變時,此時的值也會發生變化。
不積跬步無以至千里。
關于【Step-By-Step】Step-By-Step (點擊進入項目) 是我于 2019-05-20 開始的一個項目,每個工作日發布一道面試題。每個周末我會仔細閱讀大家的答案,整理最一份較優答案出來,因本人水平有限,有誤的地方,大家及時指正。
如果想 加群 學習,可以通過文末的公眾號,添加我為好友。
更多優質文章可戳: https://github.com/YvetteLau/...
__
本周面試題一覽:
實現一個 JSON.stringify
實現一個 JSON.parse
實現一個觀察者模式
使用CSS讓一個元素水平垂直居中有哪些方式
ES6模塊和CommonJS模塊有哪些差異?
31. 實現一個 JSON.stringifyJSON.stringify([, replacer [, space]) 方法是將一個JavaScript值(對象或者數組)轉換為一個 JSON字符串。此處模擬實現,不考慮可選的第二個參數 replacer 和第三個參數 space,如果對這兩個參數的作用還不了解,建議閱讀 MDN 文檔。
JSON.stringify() 將值轉換成對應的 JSON 格式:
基本數據類型:
undefined 轉換之后仍是 undefined(類型也是 undefined)
boolean 值轉換之后是字符串 "false"/"true"
number 類型(除了 NaN 和 Infinity)轉換之后是字符串類型的數值
symbol 轉換之后是 undefined
null 轉換之后是字符串 "null"
string 轉換之后仍是string
NaN 和 Infinity 轉換之后是字符串 "null"
如果是函數類型
轉換之后是 undefined
如果是對象類型(非函數)
如果有 toJSON() 方法,那么序列化 toJSON() 的返回值。
如果是一個數組
- 如果屬性值中出現了 `undefined`、任意的函數以及 `symbol`,轉換成字符串 `"null"`
如果是 RegExp 對象。
返回 `{}` (類型是 string)
如果是 Date 對象,返回 Date 的 toJSON 字符串值
如果是普通對象;
- 如果屬性值中出現了 `undefined`、任意的函數以及 symbol 值,忽略。 - 所有以 `symbol` 為屬性鍵的屬性都會被完全忽略掉。
對包含循環引用的對象(對象之間相互引用,形成無限循環)執行此方法,會拋出錯誤。
模擬實現
function jsonStringify(data) { let dataType = typeof data; if (dataType !== "object") { let result = data; //data 可能是 string/number/null/undefined/boolean if (Number.isNaN(data) || data === Infinity) { //NaN 和 Infinity 序列化返回 "null" result = "null"; } else if (dataType === "function" || dataType === "undefined" || dataType === "symbol") { //function 、undefined 、symbol 序列化返回 undefined return undefined; } else if (dataType === "string") { result = """ + data + """; } //boolean 返回 String() return String(result); } else if (dataType === "object") { if (data === null) { return "null"; } else if (data.toJSON && typeof data.toJSON === "function") { return jsonStringify(data.toJSON()); } else if (data instanceof Array) { let result = []; //如果是數組 //toJSON 方法可以存在于原型鏈中 data.forEach((item, index) => { if (typeof item === "undefined" || typeof item === "function" || typeof item === "symbol") { result[index] = "null"; } else { result[index] = jsonStringify(item); } }); result = "[" + result + "]"; return result.replace(/"/g, """); } else { //普通對象 /** * 循環引用拋錯(暫未檢測,循環引用時,堆棧溢出) * symbol key 忽略 * undefined、函數、symbol 為屬性值,被忽略 */ let result = []; Object.keys(data).forEach((item, index) => { if (typeof item !== "symbol") { //key 如果是symbol對象,忽略 if (data[item] !== undefined && typeof data[item] !== "function" && typeof data[item] !== "symbol") { //鍵值如果是 undefined、函數、symbol 為屬性值,忽略 result.push(""" + item + """ + ":" + jsonStringify(data[item])); } } }); return ("{" + result + "}").replace(/"/g, """); } } }
測試代碼:
let sym = Symbol(10); console.log(jsonStringify(sym) === JSON.stringify(sym)); let nul = null; console.log(jsonStringify(nul) === JSON.stringify(nul)); let und = undefined; console.log(jsonStringify(undefined) === JSON.stringify(undefined)); let boo = false; console.log(jsonStringify(boo) === JSON.stringify(boo)); let nan = NaN; console.log(jsonStringify(nan) === JSON.stringify(nan)); let inf = Infinity; console.log(jsonStringify(Infinity) === JSON.stringify(Infinity)); let str = "hello"; console.log(jsonStringify(str) === JSON.stringify(str)); let reg = new RegExp("w"); console.log(jsonStringify(reg) === JSON.stringify(reg)); let date = new Date(); console.log(jsonStringify(date) === JSON.stringify(date)); let obj = { name: "劉小夕", age: 22, hobbie: ["coding", "writing"], date: new Date(), unq: Symbol(10), sayHello: function () { console.log("hello") }, more: { brother: "Star", age: 20, hobbie: [null], info: { money: undefined, job: null, others: [] } } } console.log(jsonStringify(obj) === JSON.stringify(obj)); function SuperType(name, age) { this.name = name; this.age = age; } let per = new SuperType("小姐姐", 20); console.log(jsonStringify(per) === JSON.stringify(per)); function SubType(info) { this.info = info; } SubType.prototype.toJSON = function () { return { name: "錢錢錢", mount: "many", say: function () { console.log("我偏不說!"); }, more: null, reg: new RegExp("w") } } let sub = new SubType("hi"); console.log(jsonStringify(sub) === JSON.stringify(sub)); let map = new Map(); map.set("name", "小姐姐"); console.log(jsonStringify(map) === JSON.stringify(map)); let set = new Set([1, 2, 3, 4, 5, 1, 2, 3]); console.log(jsonStringify(set) === JSON.stringify(set));32. 實現一個 JSON.parse
JSON.parse(JSON.parse(text[, reviver]) 方法用來解析JSON字符串,構造由字符串描述的JavaScript值或對象。提供可選的reviver函數用以在返回之前對所得到的對象執行變換。此處模擬實現,不考慮可選的第二個參數 reviver ,如果對這個參數的作用還不了解,建議閱讀 MDN 文檔。
第一種方式 eval最簡單,最直觀的方式就是調用 eval
var json = "{"name":"小姐姐", "age":20}"; var obj = eval("(" + json + ")"); // obj 就是 json 反序列化之后得到的對象
直接調用 eval 存在 XSS 漏洞,數據中可能不是 json 數據,而是可執行的 JavaScript 代碼。因此,在調用 eval 之前,需要對數據進行校驗。
var rx_one = /^[],:{}s]*$/; var rx_two = /(?:["/bfnrt]|u[0-9a-fA-F]{4})/g; var rx_three = /"[^" ]*"|true|false|null|-?d+(?:.d*)?(?:[eE][+-]?d+)?/g; var rx_four = /(?:^|:|,)(?:s*[)+/g; if ( rx_one.test( json .replace(rx_two, "@") .replace(rx_three, "]") .replace(rx_four, "") ) ) { var obj = eval("(" +json + ")"); }
JSON 是 JS 的子集,可以直接交給 eval 運行。
第二種方式 new FunctionFunction 與 eval 有相同的字符串參數特性。
var json = "{"name":"小姐姐", "age":20}"; var obj = (new Function("return " + json))();33. 實現一個觀察者模式
觀察者模式定義了對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴于它的對象都將得到通知,并自動更新。觀察者模式屬于行為型模式,行為型模式關注的是對象之間的通訊,觀察者模式就是觀察者和被觀察者之間的通訊。
觀察者(Observer)直接訂閱(Subscribe)主題(Subject),而當主題被激活的時候,會觸發(Fire Event)觀察者里的事件。
//有一家獵人工會,其中每個獵人都具有發布任務(publish),訂閱任務(subscribe)的功能 //他們都有一個訂閱列表來記錄誰訂閱了自己 //定義一個獵人類 //包括姓名,級別,訂閱列表 function Hunter(name, level){ this.name = name this.level = level this.list = [] } Hunter.prototype.publish = function (money){ console.log(this.level + "獵人" + this.name + "尋求幫助") this.list.forEach(function(item, index){ item(money) }) } Hunter.prototype.subscribe = function (targrt, fn){ console.log(this.level + "獵人" + this.name + "訂閱了" + targrt.name) targrt.list.push(fn) } //獵人工會走來了幾個獵人 let hunterMing = new Hunter("小明", "黃金") let hunterJin = new Hunter("小金", "白銀") let hunterZhang = new Hunter("小張", "黃金") let hunterPeter = new Hunter("Peter", "青銅") //Peter等級較低,可能需要幫助,所以小明,小金,小張都訂閱了Peter hunterMing.subscribe(hunterPeter, function(money){ console.log("小明表示:" + (money > 200 ? "" : "暫時很忙,不能") + "給予幫助") }); hunterJin.subscribe(hunterPeter, function(){ console.log("小金表示:給予幫助") }); hunterZhang.subscribe(hunterPeter, function(){ console.log("小張表示:給予幫助") }); //Peter遇到困難,賞金198尋求幫助 hunterPeter.publish(198); //獵人們(觀察者)關聯他們感興趣的獵人(目標對象),如Peter,當Peter有困難時,會自動通知給他們(觀察者)34. 使用 CSS 讓一個元素水平垂直居中
父元素 .container
子元素 .box
利用 flex 布局/* 無需知道被居中元素的寬高 */ .container { display: flex; align-items: center; justify-content: center; }子元素是單行文本
設置父元素的 text-align 和 line-height = height
.container { height: 100px; line-height: 100px; text-align: center; }利用 absolute + transform
/* 無需知道被居中元素的寬高 */ /* 設置父元素非 `static` 定位 */ .container { position: relative; } /* 子元素絕對定位,使用 translate的好處是無需知道子元素的寬高 */ /* 如果知道寬高,也可以使用 margin 設置 */ .box { position: absolute; left: -50%; top: -50%; transform: translate(-50%, -50%); }利用 grid 布局
/* 無需知道被居中元素的寬高 */ .container { display: grid; } .box { justify-self: center; align-self: center; }利用絕對定位和 margin:auto
/* 無需知道被居中元素的寬高 */ .box { position: absolute; left: 0; top: 0; right: 0; bottom: 0; margin: auto; } .container { position: relative; }35. ES6模塊和 CommonJS 模塊有哪些差異?
ES6模塊在編譯時,就能確定模塊的依賴關系,以及輸入和輸出的變量。ES6 模塊不是對象,它的對外接口只是一種靜態定義,在代碼靜態解析階段就會生成。
CommonJS 加載的是一個對象,該對象只有在腳本運行完才會生成。
- `CommonJS` 輸出的是一個值的拷貝(注意基本數據類型/復雜數據類型) - ES6 模塊是動態引用,并且不會緩存值,模塊里面的變量綁定其所在的模塊。
CommonJS 模塊輸出的是值的拷貝。
模塊輸出的值是基本數據類型,模塊內部的變化就影響不到這個值。
//name.js let name = "William"; setTimeout(() => { name = "Yvette"; }, 300); module.exports = name; //index.js const name = require("./name"); console.log(name); //William //name.js 模塊加載后,它的內部變化就影響不到 name //name 是一個基本數據類型。將其復制出一份之后,二者之間互不影響。 setTimeout(() => console.log(name), 500); //William
模塊輸出的值是復雜數據類型
模塊輸出的是對象,屬性值是簡單數據類型時:
//name.js let name = "William"; setTimeout(() => { name = "Yvette"; }, 300); module.exports = { name }; //index.js const { name } = require("./name"); console.log(name); //William //name 是一個原始類型的值,會被緩存。 setTimeout(() => console.log(name), 500); //William
模塊輸出的是對象:
//name.js let name = "William"; let hobbies = ["coding"]; setTimeout(() => { name = "Yvette"; hobbies.push("reading"); }, 300); module.exports = { name, hobbies }; //index.js const { name, hobbies } = require("./name"); console.log(name); //William console.log(hobbies); //["coding"] /* * name 的值沒有受到影響,因為 {name: name} 屬性值 name 存的是個字符串 * 300ms后 name 變量重新賦值,但是不會影響 {name: name} * * hobbies 的值會被影響,因為 {hobbies: hobbies} 屬性值 hobbies 中存的是 * 數組的堆內存地址,因此當 hobbies 對象的值被改變時,存在棧內存中的地址并 沒有發生變化,因此 hoobies 對象值的改變會影響 {hobbies: hobbies} * xx = { name, hobbies } 也因此改變 (復雜數據類型,拷貝的棧內存中存的地址) */ setTimeout(() => { console.log(name);//William console.log(hobbies);//["coding", "reading"] }, 500);
ES6 模塊的運行機制與 CommonJS 不一樣。JS 引擎對腳本靜態分析的時候,遇到模塊加載命令 import ,就會生成一個只讀引用。等到腳本真正執行時,再根據這個只讀引用,到被加載的那個模塊里面去取值。
//name.js let name = "William"; setTimeout(() => { name = "Yvette"; hobbies.push("writing"); }, 300); export { name }; export var hobbies = ["coding"]; //index.js import { name, hobbies } from "./name"; console.log(name, hobbies); //William ["coding"] //name 和 hobbie 都會被模塊內部的變化所影響 setTimeout(() => { console.log(name, hobbies); //Yvette ["coding", "writing"] }, 500); //Yvette
ES6 模塊是動態引用,并且不會緩存值,模塊里面的變量綁定其所在的模塊。因此上面的例子也很容易理解。
那么 export default 導出是什么情況呢?
//name.js let name = "William"; let hobbies = ["coding"] setTimeout(() => { name = "Yvette"; hobbies.push("writing"); }, 300); export default { name, hobbies }; //index.js import info from "./name"; console.log(info.name, info.hobbies); //William ["coding"] //name 不會被模塊內部的變化所影響 //hobbie 會被模塊內部的變化所影響 setTimeout(() => { console.log(info.name, info.hobbies); //William ["coding", "writing"] }, 500); //Yvette
一起看一下為什么。
export default 可以理解為將變量賦值給 default,最后導出 default (僅是方便理解,不代表最終的實現,如果對這塊感興趣,可以閱讀 webpack 編譯出來的代碼)。
基礎類型變量 name, 賦值給 default 之后,只讀引用與 default 關聯,此時原變量 name 的任何修改都與 default 無關。
復雜數據類型變量 hobbies,賦值給 default之后,只讀引用與 default 關聯,default 和 hobbies 中存儲的是同一個對象的堆內存地址,當這個對象的值發生改變時,此時 default 的值也會發生變化。
import name from "./name"; name = "Star"; //拋錯
[1] 珠峰架構課(墻裂推薦)
[2] JSON.parse三種實現方式
[3] ES6 文檔
[4] JSON-js
[5] CommonJS模塊和ES6模塊的區別
[6] 發布訂閱模式與觀察者模式
謝謝各位小伙伴愿意花費寶貴的時間閱讀本文,如果本文給了您一點幫助或者是啟發,請不要吝嗇你的贊和Star,您的肯定是我前進的最大動力。 https://github.com/YvetteLau/...
關注公眾號,加入技術交流群。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/105501.html
摘要:關于點擊進入項目是我于開始的一個項目,每個工作日發布一道面試題。那個率先改變的實例的返回值,就傳遞給的回調函數。通過插入標簽的方式來實現跨域,參數只能通過傳入,僅能支持請求。因此清除浮動,只需要觸發一個即可。 關于【Step-By-Step】 Step-By-Step (點擊進入項目) 是我于 2019-05-20 開始的一個項目,每個工作日發布一道面試題。每個周末我會仔細閱讀大家的...
摘要:關于點擊進入項目是我于開始的一個項目,每個工作日發布一道面試題。的狀態由決定,分成以下兩種情況只有的狀態都變成,的狀態才會變成,此時的返回值組成一個數組,傳遞給的回調函數。 關于【Step-By-Step】 Step-By-Step (點擊進入項目) 是我于 2019-05-20 開始的一個項目,每個工作日發布一道面試題。每個周末我會仔細閱讀大家的答案,整理最一份較優答案出來,因本人...
閱讀 3581·2023-04-26 02:55
閱讀 2848·2021-11-02 14:38
閱讀 4134·2021-10-21 09:39
閱讀 2841·2021-09-27 13:36
閱讀 3942·2021-09-22 15:08
閱讀 2642·2021-09-08 10:42
閱讀 2802·2019-08-29 12:21
閱讀 666·2019-08-29 11:22