摘要:复杂数据类型变量,赋值给之后,只读引用与关联,和中存储的是同一个对象的堆内存地址,当这个对象的值发生改变时,此时的值也会发生变化。
不积跬步无以至千里。
关于【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/...
关注公众号,加入技术交流群。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/105501.html
摘要:关于点击进入项目是我于开始的一个项目,每个工作日发布一道面试题。那个率先改变的实例的返回值,就传递给的回调函数。通过插入标签的方式来实现跨域,参数只能通过传入,仅能支持请求。因此清除浮动,只需要触发一个即可。 关于【Step-By-Step】 Step-By-Step (点击进入项目) 是我于 2019-05-20 开始的一个项目,每个工作日发布一道面试题。每个周末我会仔细阅读大家的...
摘要:关于点击进入项目是我于开始的一个项目,每个工作日发布一道面试题。的状态由决定,分成以下两种情况只有的状态都变成,的状态才会变成,此时的返回值组成一个数组,传递给的回调函数。 关于【Step-By-Step】 Step-By-Step (点击进入项目) 是我于 2019-05-20 开始的一个项目,每个工作日发布一道面试题。每个周末我会仔细阅读大家的答案,整理最一份较优答案出来,因本人...
阅读 3560·2023-04-26 02:55
阅读 2825·2021-11-02 14:38
阅读 4113·2021-10-21 09:39
阅读 2789·2021-09-27 13:36
阅读 3880·2021-09-22 15:08
阅读 2605·2021-09-08 10:42
阅读 2783·2019-08-29 12:21
阅读 640·2019-08-29 11:22