您好,登錄后才能下訂單哦!
本篇內容介紹了“JS繼承類型有哪些”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
typeof 可以正確識別:Undefined、Boolean、Number、String、Symbol、Function 等類型的數據,但是對于其他的都會認為是 object,比如 Null、Date 等,所以通過 typeof 來判斷數據類型會不準確。但是可以使用 Object.prototype.toString 實現。
function typeOf(obj) { let res = Object.prototype.toString.call(obj).split(' ')[1] res = res.substring(0, res.length - 1).toLowerCase() return res } typeOf([]) // 'array' typeOf({}) // 'object' typeOf(new Date) // 'date'
function Animal() { this.colors = ['black', 'white'] } Animal.prototype.getColor = function() { return this.colors } function Dog() {} Dog.prototype = new Animal() let dog1 = new Dog() dog1.colors.push('brown') let dog2 = new Dog() console.log(dog2.colors) // ['black', 'white', 'brown']
原型鏈繼承存在的問題:
問題1:原型中包含的引用類型屬性將被所有實例共享;
問題2:子類在實例化的時候不能給父類構造函數傳參;
function Animal(name) { this.name = name this.getName = function() { return this.name } } function Dog(name) { Animal.call(this, name) } Dog.prototype = new Animal()
借用構造函數實現繼承解決了原型鏈繼承的 2 個問題:引用類型共享問題以及傳參問題。但是由于方法必須定義在構造函數中,所以會導致每次創建子類實例都會創建一遍方法。
組合繼承結合了原型鏈和盜用構造函數,將兩者的優點集中了起來。基本的思路是使用原型鏈繼承原型上的屬性和方法,而通過盜用構造函數繼承實例屬性。這樣既可以把方法定義在原型上以實現重用,又可以讓每個實例都有自己的屬性。
function Animal(name) { this.name = name this.colors = ['black', 'white'] } Animal.prototype.getName = function() { return this.name } function Dog(name, age) { Animal.call(this, name) this.age = age } Dog.prototype = new Animal() Dog.prototype.constructor = Dog let dog1 = new Dog('奶昔', 2) dog1.colors.push('brown') let dog2 = new Dog('哈赤', 1) console.log(dog2) // { name: "哈赤", colors: ["black", "white"], age: 1 }
組合繼承已經相對完善了,但還是存在問題,它的問題就是調用了 2 次父類構造函數,第一次是在 new Animal(),第二次是在 Animal.call() 這里。
所以解決方案就是不直接調用父類構造函數給子類原型賦值,而是通過創建空函數 F 獲取父類原型的副本。
寄生式組合繼承寫法上和組合繼承基本類似,區別是如下這里:
- Dog.prototype = new Animal() - Dog.prototype.constructor = Dog + function F() {} + F.prototype = Animal.prototype + let f = new F() + f.constructor = Dog + Dog.prototype = f
稍微封裝下上面添加的代碼后:
function object(o) { function F() {} F.prototype = o return new F() } function inheritPrototype(child, parent) { let prototype = object(parent.prototype) prototype.constructor = child child.prototype = prototype } inheritPrototype(Dog, Animal)
如果你嫌棄上面的代碼太多了,還可以基于組合繼承的代碼改成最簡單的寄生式組合繼承:
- Dog.prototype = new Animal() - Dog.prototype.constructor = Dog + Dog.prototype = Object.create(Animal.prototype) + Dog.prototype.constructor = Dog
class 實現繼承
class Animal { constructor(name) { this.name = name } getName() { return this.name } } class Dog extends Animal { constructor(name, age) { super(name) this.age = age } }
ES5 實現:
function unique(arr) { var res = arr.filter(function(item, index, array) { return array.indexOf(item) === index }) return res }
ES6 實現:
var unique = arr => [...new Set(arr)]
數組扁平化就是將 [1, [2, [3]]] 這種多層的數組拍平成一層 [1, 2, 3]。使用 Array.prototype.flat 可以直接將多層數組拍平成一層:
[1, [2, [3]]].flat(2) // [1, 2, 3]
現在就是要實現 flat 這種效果。
ES5 實現:遞歸。
function flatten(arr) { var result = []; for (var i = 0, len = arr.length; i < len; i++) { if (Array.isArray(arr[i])) { result = result.concat(flatten(arr[i])) } else { result.push(arr[i]) } } return result; }
ES6 實現:
function flatten(arr) { while (arr.some(item => Array.isArray(item))) { arr = [].concat(...arr); } return arr; }
淺拷貝:只考慮對象類型。
function shallowCopy(obj) { if (typeof obj !== 'object') return let newObj = obj instanceof Array ? [] : {} for (let key in obj) { if (obj.hasOwnProperty(key)) { newObj[key] = obj[key] } } return newObj }
簡單版深拷貝:只考慮普通對象屬性,不考慮內置對象和函數。
function deepClone(obj) { if (typeof obj !== 'object') return; var newObj = obj instanceof Array ? [] : {}; for (var key in obj) { if (obj.hasOwnProperty(key)) { newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key]; } } return newObj; }
復雜版深克隆:基于簡單版的基礎上,還考慮了內置對象比如 Date、RegExp 等對象和函數以及解決了循環引用的問題。
const isObject = (target) => (typeof target === "object" || typeof target === "function") && target !== null; function deepClone(target, map = new WeakMap()) { if (map.get(target)) { return target; } // 獲取當前值的構造函數:獲取它的類型 let constructor = target.constructor; // 檢測當前對象target是否與正則、日期格式對象匹配 if (/^(RegExp|Date)$/i.test(constructor.name)) { // 創建一個新的特殊對象(正則類/日期類)的實例 return new constructor(target); } if (isObject(target)) { map.set(target, true); // 為循環引用的對象做標記 const cloneTarget = Array.isArray(target) ? [] : {}; for (let prop in target) { if (target.hasOwnProperty(prop)) { cloneTarget[prop] = deepClone(target[prop], map); } } return cloneTarget; } else { return target; } }
class EventEmitter { constructor() { this.cache = {} } on(name, fn) { if (this.cache[name]) { this.cache[name].push(fn) } else { this.cache[name] = [fn] } } off(name, fn) { let tasks = this.cache[name] if (tasks) { const index = tasks.findIndex(f => f === fn || f.callback === fn) if (index >= 0) { tasks.splice(index, 1) } } } emit(name, once = false, ...args) { if (this.cache[name]) { // 創建副本,如果回調函數內繼續注冊相同事件,會造成死循環 let tasks = this.cache[name].slice() for (let fn of tasks) { fn(...args) } if (once) { delete this.cache[name] } } } } // 測試 let eventBus = new EventEmitter() let fn1 = function(name, age) { console.log(`${name} ${age}`) } let fn2 = function(name, age) { console.log(`hello, ${name} ${age}`) } eventBus.on('aaa', fn1) eventBus.on('aaa', fn2) eventBus.emit('aaa', false, '布蘭', 12) // '布蘭 12' // 'hello, 布蘭 12'
function parseParam(url) { const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 將 ? 后面的字符串取出來 const paramsArr = paramsStr.split('&'); // 將字符串以 & 分割后存到數組中 let paramsObj = {}; // 將 params 存到對象中 paramsArr.forEach(param => { if (/=/.test(param)) { // 處理有 value 的參數 let [key, val] = param.split('='); // 分割 key 和 value val = decodeURIComponent(val); // 解碼 val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判斷是否轉為數字 if (paramsObj.hasOwnProperty(key)) { // 如果對象有 key,則添加一個值 paramsObj[key] = [].concat(paramsObj[key], val); } else { // 如果對象沒有這個 key,創建 key 并設置值 paramsObj[key] = val; } } else { // 處理沒有 value 的參數 paramsObj[param] = true; } }) return paramsObj; }
function render(template, data) { const reg = /\{\{(\w+)\}\}/; // 模板字符串正則 if (reg.test(template)) { // 判斷模板里是否有模板字符串 const name = reg.exec(template)[1]; // 查找當前模板里第一個模板字符串的字段 template = template.replace(reg, data[name]); // 將第一個模板字符串渲染 return render(template, data); // 遞歸的渲染并返回渲染后的結構 } return template; // 如果模板沒有模板字符串直接返回 }
測試:
let template = '我是{{name}},年齡{{age}},性別{{sex}}'; let person = { name: '布蘭', age: 12 } render(template, person); // 我是布蘭,年齡12,性別undefined
與普通的圖片懶加載不同,如下這個多做了 2 個精心處理:
圖片全部加載完成后移除事件監聽;
加載完的圖片,從 imgList 移除;
let imgList = [...document.querySelectorAll('img')] let length = imgList.length const imgLazyLoad = function() { let count = 0 return function() { let deleteIndexList = [] imgList.forEach((img, index) => { let rect = img.getBoundingClientRect() if (rect.top < window.innerHeight) { img.src = img.dataset.src deleteIndexList.push(index) count++ if (count === length) { document.removeEventListener('scroll', imgLazyLoad) } } }) imgList = imgList.filter((img, index) => !deleteIndexList.includes(index)) } } // 這里最好加上防抖處理 document.addEventListener('scroll', imgLazyLoad)
觸發高頻事件 N 秒后只會執行一次,如果 N 秒內事件再次觸發,則會重新計時。
簡單版:函數內部支持使用 this 和 event 對象;
function debounce(func, wait) { var timeout; return function () { var context = this; var args = arguments; clearTimeout(timeout) timeout = setTimeout(function(){ func.apply(context, args) }, wait); } }
使用:
var node = document.getElementById('layout') function getUserAction(e) { console.log(this, e) // 分別打印:node 這個節點 和 MouseEvent node.innerHTML = count++; }; node.onmousemove = debounce(getUserAction, 1000)
最終版:除了支持 this 和 event 外,還支持以下功能:
支持立即執行;
函數可能有返回值;
支持取消功能;
function debounce(func, wait, immediate) { var timeout, result; var debounced = function () { var context = this; var args = arguments; if (timeout) clearTimeout(timeout); if (immediate) { // 如果已經執行過,不再執行 var callNow = !timeout; timeout = setTimeout(function(){ timeout = null; }, wait) if (callNow) result = func.apply(context, args) } else { timeout = setTimeout(function(){ func.apply(context, args) }, wait); } return result; }; debounced.cancel = function() { clearTimeout(timeout); timeout = null; }; return debounced; }
使用:
var setUseAction = debounce(getUserAction, 10000, true); // 使用防抖 node.onmousemove = setUseAction // 取消防抖 setUseAction.cancel()
參考:JavaScript專題之跟著underscore學防抖
觸發高頻事件,且 N 秒內只執行一次。
簡單版:使用時間戳來實現,立即執行一次,然后每 N 秒執行一次。
function throttle(func, wait) { var context, args; var previous = 0; return function() { var now = +new Date(); context = this; args = arguments; if (now - previous > wait) { func.apply(context, args); previous = now; } } }
最終版:支持取消節流;另外通過傳入第三個參數,options.leading 來表示是否可以立即執行一次,opitons.trailing 表示結束調用的時候是否還要執行一次,默認都是 true。注意設置的時候不能同時將 leading 或 trailing 設置為 false。
function throttle(func, wait, options) { var timeout, context, args, result; var previous = 0; if (!options) options = {}; var later = function() { previous = options.leading === false ? 0 : new Date().getTime(); timeout = null; func.apply(context, args); if (!timeout) context = args = null; }; var throttled = function() { var now = new Date().getTime(); if (!previous && options.leading === false) previous = now; var remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } }; throttled.cancel = function() { clearTimeout(timeout); previous = 0; timeout = null; } return throttled; }
節流的使用就不拿代碼舉例了,參考防抖的寫就行。
什么叫函數柯里化?其實就是將使用多個參數的函數轉換成一系列使用一個參數的函數的技術。還不懂?來舉個例子。
function add(a, b, c) { return a + b + c } add(1, 2, 3) let addCurry = curry(add) addCurry(1)(2)(3)
現在就是要實現 curry 這個函數,使函數從一次調用傳入多個參數變成多次調用每次傳一個參數。
function curry(fn) { let judge = (...args) => { if (args.length == fn.length) return fn(...args) return (...arg) => judge(...args, ...arg) } return judge }
什么是偏函數?偏函數就是將一個 n 參的函數轉換成固定 x 參的函數,剩余參數(n - x)將在下次調用全部傳入。舉個例子:
function add(a, b, c) { return a + b + c } let partialAdd = partial(add, 1) partialAdd(2, 3)
發現沒有,其實偏函數和函數柯里化有點像,所以根據函數柯里化的實現,能夠能很快寫出偏函數的實現:
function partial(fn, ...args) { return (...arg) => { return fn(...args, ...arg) } }
如上這個功能比較簡單,現在我們希望偏函數能和柯里化一樣能實現占位功能,比如:
function clg(a, b, c) { console.log(a, b, c) } let partialClg = partial(clg, '_', 2) partialClg(1, 3) // 依次打印:1, 2, 3
_ 占的位其實就是 1 的位置。相當于:partial(clg, 1, 2),然后 partialClg(3)。明白了原理,我們就來寫實現:
function partial(fn, ...args) { return (...arg) => { args[index] = return fn(...args, ...arg) } }
JSONP 核心原理:script 標簽不受同源策略約束,所以可以用來進行跨域請求,優點是兼容性好,但是只能用于 GET 請求;
const jsonp = ({ url, params, callbackName }) => { const generateUrl = () => { let dataSrc = '' for (let key in params) { if (params.hasOwnProperty(key)) { dataSrc += `${key}=${params[key]}&` } } dataSrc += `callback=${callbackName}` return `${url}?${dataSrc}` } return new Promise((resolve, reject) => { const scriptEle = document.createElement('script') scriptEle.src = generateUrl() document.body.appendChild(scriptEle) window[callbackName] = data => { resolve(data) document.removeChild(scriptEle) } }) }
const getJSON = function(url) { return new Promise((resolve, reject) => { const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Mscrosoft.XMLHttp'); xhr.open('GET', url, false); xhr.setRequestHeader('Accept', 'application/json'); xhr.onreadystatechange = function() { if (xhr.readyState !== 4) return; if (xhr.status === 200 || xhr.status === 304) { resolve(xhr.responseText); } else { reject(new Error(xhr.responseText)); } } xhr.send(); }) }
forEach
Array.prototype.forEach3 = function(callback, thisArg) { if (this == null) { throw new TypeError('this is null or not defined') } if (typeof callback !== "function") { throw new TypeError(callback + ' is not a function') } const O = Object(this) // this 就是當前的數組 const len = O.length >>> 0 // 后面有解釋 let k = 0 while (k < len) { if (k in O) { callback.call(thisArg, O[k], k, O); } k++; } }
O.length >>> 0 是什么操作?就是無符號右移 0 位,那有什么意義嘛?就是為了保證轉換后的值為正整數。其實底層做了 2 層轉換,第一是非 number 轉成 number 類型,第二是將 number 轉成 Uint32 類型。感興趣可以閱讀 something >>> 0是什么意思?[3]。
基于 forEach 的實現能夠很容易寫出 map 的實現:
- Array.prototype.forEach3 = function(callback, thisArg) { + Array.prototype.map2 = function(callback, thisArg) { if (this == null) { throw new TypeError('this is null or not defined') } if (typeof callback !== "function") { throw new TypeError(callback + ' is not a function') } const O = Object(this) const len = O.length >>> 0 - let k = 0 + let k = 0, res = [] while (k < len) { if (k in O) { - callback.call(thisArg, O[k], k, O); + res[k] = callback.call(thisArg, O[k], k, O); } k++; } + return res }
同樣,基于 forEach 的實現能夠很容易寫出 filter 的實現:
- Array.prototype.forEach3 = function(callback, thisArg) { + Array.prototype.filter2 = function(callback, thisArg) { if (this == null) { throw new TypeError('this is null or not defined') } if (typeof callback !== "function") { throw new TypeError(callback + ' is not a function') } const O = Object(this) const len = O.length >>> 0 - let k = 0 + let k = 0, res = [] while (k < len) { if (k in O) { - callback.call(thisArg, O[k], k, O); + if (callback.call(thisArg, O[k], k, O)) { + res.push(O[k]) + } } k++; } + return res }
同樣,基于 forEach 的實現能夠很容易寫出 some 的實現:
- Array.prototype.forEach3 = function(callback, thisArg) { + Array.prototype.some2 = function(callback, thisArg) { if (this == null) { throw new TypeError('this is null or not defined') } if (typeof callback !== "function") { throw new TypeError(callback + ' is not a function') } const O = Object(this) const len = O.length >>> 0 let k = 0 while (k < len) { if (k in O) { - callback.call(thisArg, O[k], k, O); + if (callback.call(thisArg, O[k], k, O)) { + return true + } } k++; } + return false }
Array.prototype.reduce2 = function(callback, initialValue) { if (this == null) { throw new TypeError('this is null or not defined') } if (typeof callback !== "function") { throw new TypeError(callback + ' is not a function') } const O = Object(this) const len = O.length >>> 0 let k = 0, acc if (arguments.length > 1) { acc = initialValue } else { // 沒傳入初始值的時候,取數組中第一個非 empty 的值為初始值 while (k < len && !(k in O)) { k++ } if (k > len) { throw new TypeError( 'Reduce of empty array with no initial value' ); } acc = O[k++] } while (k < len) { if (k in O) { acc = callback(acc, O[k], k, O) } k++ } return acc }
使用一個指定的 this 值和一個或多個參數來調用一個函數。
實現要點:
this 可能傳入 null;
傳入不固定個數的參數;
函數可能有返回值;
Function.prototype.call2 = function (context) { var context = context || window; context.fn = this; var args = []; for(var i = 1, len = arguments.length; i < len; i++) { args.push('arguments[' + i + ']'); } var result = eval('context.fn(' + args +')'); delete context.fn return result; }
apply 和 call 一樣,唯一的區別就是 call 是傳入不固定個數的參數,而 apply 是傳入一個數組。
實現要點:
this 可能傳入 null;
傳入一個數組;
函數可能有返回值;
Function.prototype.apply2 = function (context, arr) { var context = context || window; context.fn = this; var result; if (!arr) { result = context.fn(); } else { var args = []; for (var i = 0, len = arr.length; i < len; i++) { args.push('arr[' + i + ']'); } result = eval('context.fn(' + args + ')') } delete context.fn return result; }
bind 方法會創建一個新的函數,在 bind() 被調用時,這個新函數的 this 被指定為 bind() 的第一個參數,而其余參數將作為新函數的參數,供調用時使用。
實現要點:
bind() 除了 this 外,還可傳入多個參數;
bing 創建的新函數可能傳入多個參數;
新函數可能被當做構造函數調用;
函數可能有返回值;
Function.prototype.bind2 = function (context) { var self = this; var args = Array.prototype.slice.call(arguments, 1); var fNOP = function () {}; var fBound = function () { var bindArgs = Array.prototype.slice.call(arguments); return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs)); } fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; }
new 運算符用來創建用戶自定義的對象類型的實例或者具有構造函數的內置對象的實例。
實現要點:
new 會產生一個新對象;
新對象需要能夠訪問到構造函數的屬性,所以需要重新指定它的原型;
構造函數可能會顯示返回;
function objectFactory() { var obj = new Object() Constructor = [].shift.call(arguments); obj.__proto__ = Constructor.prototype; var ret = Constructor.apply(obj, arguments); // ret || obj 這里這么寫考慮了構造函數顯示返回 null 的情況 return typeof ret === 'object' ? ret || obj : obj; };
使用:
function person(name, age) { this.name = name this.age = age } let p = objectFactory(person, '布蘭', 12) console.log(p) // { name: '布蘭', age: 12 }
instanceof 就是判斷構造函數的 prototype 屬性是否出現在實例的原型鏈上。
function instanceOf(left, right) { let proto = left.__proto__ while (true) { if (proto === null) return false if (proto === right.prototype) { return true } proto = proto.__proto__ } }
上面的 left.proto 這種寫法可以換成 Object.getPrototypeOf(left)。
Object.create()方法創建一個新對象,使用現有的對象來提供新創建的對象的__proto__。
Object.create2 = function(proto, propertyObject = undefined) { if (typeof proto !== 'object' && typeof proto !== 'function') { throw new TypeError('Object prototype may only be an Object or null.') if (propertyObject == null) { new TypeError('Cannot convert undefined or null to object') } function F() {} F.prototype = proto const obj = new F() if (propertyObject != undefined) { Object.defineProperties(obj, propertyObject) } if (proto === null) { // 創建一個沒有原型對象的對象,Object.create(null) obj.__proto__ = null } return obj }
Object.assign2 = function(target, ...source) { if (target == null) { throw new TypeError('Cannot convert undefined or null to object') } let ret = Object(target) source.forEach(function(obj) { if (obj != null) { for (let key in obj) { if (obj.hasOwnProperty(key)) { ret[key] = obj[key] } } } }) return ret }
JSON.stringify([, replacer [, space]) 方法是將一個 JavaScript 值(對象或者數組)轉換為一個 JSON 字符串。此處模擬實現,不考慮可選的第二個參數 replacer 和第三個參數 space,如果對這兩個參數的作用還不了解,建議閱讀 MDN[4] 文檔。
1.基本數據類型:
undefined 轉換之后仍是 undefined(類型也是 undefined)
boolean 值轉換之后是字符串 "false"/"true"
number 類型(除了 NaN 和 Infinity)轉換之后是字符串類型的數值
symbol 轉換之后是 undefined
null 轉換之后是字符串 "null"
string 轉換之后仍是string
NaN 和 Infinity 轉換之后是字符串 "null"
2.函數類型:轉換之后是 undefined
3.如果是對象類型(非函數)
如果有 toJSON() 方法,那么序列化 toJSON() 的返回值。
如果屬性值中出現了 undefined、任意的函數以及 symbol 值,忽略。
所有以 symbol 為屬性鍵的屬性都會被完全忽略掉。
如果是一個數組:如果屬性值中出現了 undefined、任意的函數以及 symbol,轉換成字符串 "null" ;
如果是 RegExp 對象:返回 {} (類型是 string);
如果是 Date 對象,返回 Date 的 toJSON 字符串值;
如果是普通對象;
4.對包含循環引用的對象(對象之間相互引用,形成無限循環)執行此方法,會拋出錯誤。
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, '"'); } } }
介紹 2 種方法實現:
eval 實現;
new Function 實現;
第一種方式最簡單,也最直觀,就是直接調用 eval,代碼如下:
var json = '{"a":"1", "b":2}'; var obj = eval("(" + json + ")"); // obj 就是 json 反序列化之后得到的對象
但是直接調用 eval 會存在安全問題,如果數據中可能不是 json 數據,而是可執行的 JavaScript 代碼,那很可能會造成 XSS 攻擊。因此,在調用 eval 之前,需要對數據進行校驗。
var rx_one = /^[\],:{}\s]*$/; var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g; var rx_three = /"[^"\\\n\r]*"|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 + ")"); }
Function 與 eval 有相同的字符串參數特性。
var json = '{"name":"小姐姐", "age":20}'; var obj = (new Function('return ' + json))();
Promise 需要完全讀懂 Promise A+ 規范[7],不過從總體的實現上看,有如下幾個點需要考慮到:
then 需要支持鏈式調用,所以得返回一個新的 Promise;
處理異步問題,所以得先用 onResolvedCallbacks 和 onRejectedCallbacks 分別把成功和失敗的回調存起來;
為了讓鏈式調用正常進行下去,需要判斷 onFulfilled 和 onRejected 的類型;
onFulfilled 和 onRejected 需要被異步調用,這里用 setTimeout 模擬異步;
處理 Promise 的 resolve;
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; class Promise { constructor(executor) { this.status = PENDING; this.value = undefined; this.reason = undefined; this.onResolvedCallbacks = []; this.onRejectedCallbacks = []; let resolve = (value) = > { if (this.status === PENDING) { this.status = FULFILLED; this.value = value; this.onResolvedCallbacks.forEach((fn) = > fn()); } }; let reject = (reason) = > { if (this.status === PENDING) { this.status = REJECTED; this.reason = reason; this.onRejectedCallbacks.forEach((fn) = > fn()); } }; try { executor(resolve, reject); } catch (error) { reject(error); } } then(onFulfilled, onRejected) { // 解決 onFufilled,onRejected 沒有傳值的問題 onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) = > v; // 因為錯誤的值要讓后面訪問到,所以這里也要拋出錯誤,不然會在之后 then 的 resolve 中捕獲 onRejected = typeof onRejected === "function" ? onRejected : (err) = > { throw err; }; // 每次調用 then 都返回一個新的 promise let promise2 = new Promise((resolve, reject) = > { if (this.status === FULFILLED) { //Promise/A+ 2.2.4 --- setTimeout setTimeout(() = > { try { let x = onFulfilled(this.value); // x可能是一個proimise resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); } if (this.status === REJECTED) { //Promise/A+ 2.2.3 setTimeout(() = > { try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); } if (this.status === PENDING) { this.onResolvedCallbacks.push(() = > { setTimeout(() = > { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); }); this.onRejectedCallbacks.push(() = > { setTimeout(() = > { try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); }); } }); return promise2; } } const resolvePromise = (promise2, x, resolve, reject) = > { // 自己等待自己完成是錯誤的實現,用一個類型錯誤,結束掉 promise Promise/A+ 2.3.1 if (promise2 === x) { return reject( new TypeError("Chaining cycle detected for promise #<Promise>")); } // Promise/A+ 2.3.3.3.3 只能調用一次 let called; // 后續的條件要嚴格判斷 保證代碼能和別的庫一起使用 if ((typeof x === "object" && x != null) || typeof x === "function") { try { // 為了判斷 resolve 過的就不用再 reject 了(比如 reject 和 resolve 同時調用的時候) Promise/A+ 2.3.3.1 let then = x.then; if (typeof then === "function") { // 不要寫成 x.then,直接 then.call 就可以了 因為 x.then 會再次取值,Object.defineProperty Promise/A+ 2.3.3.3 then.call( x, (y) = > { // 根據 promise 的狀態決定是成功還是失敗 if (called) return; called = true; // 遞歸解析的過程(因為可能 promise 中還有 promise) Promise/A+ 2.3.3.3.1 resolvePromise(promise2, y, resolve, reject); }, (r) = > { // 只要失敗就失敗 Promise/A+ 2.3.3.3.2 if (called) return; called = true; reject(r); }); } else { // 如果 x.then 是個普通值就直接返回 resolve 作為結果 Promise/A+ 2.3.3.4 resolve(x); } } catch (e) { // Promise/A+ 2.3.3.2 if (called) return; called = true; reject(e); } } else { // 如果 x 是個普通值就直接返回 resolve 作為結果 Promise/A+ 2.3.4 resolve(x); } };
Promise 寫完之后可以通過 promises-aplus-tests 這個包對我們寫的代碼進行測試,看是否符合 A+ 規范。不過測試前還得加一段代碼:
// promise.js // 這里是上面寫的 Promise 全部代碼 Promise.defer = Promise.deferred = function () { let dfd = {} dfd.promise = new Promise((resolve,reject)=>{ dfd.resolve = resolve; dfd.reject = reject; }); return dfd; } module.exports = Promise;
全局安裝:
npm i promises-aplus-tests -g
終端下執行驗證命令:
promises-aplus-tests promise.js
上面寫的代碼可以順利通過全部 872 個測試用例。
參考:
BAT前端經典面試問題:史上最最最詳細的手寫Promise教程[8]
100 行代碼實現 Promises/A+ 規范[9]
Promsie.resolve(value) 可以將任何值轉成值為 value 狀態是 fulfilled 的 Promise,但如果傳入的值本身是 Promise 則會原樣返回它。
Promise.resolve = function(value) { // 如果是 Promsie,則直接輸出它 if(value instanceof Promise){ return value } return new Promise(resolve => resolve(value)) }
參考:深入理解 Promise[10]
和 Promise.resolve() 類似,Promise.reject() 會實例化一個 rejected 狀態的 Promise。但與 Promise.resolve() 不同的是,如果給 Promise.reject() 傳遞一個 Promise 對象,則這個對象會成為新 Promise 的值。
Promise.reject = function(reason) { return new Promise((resolve, reject) => reject(reason)) }
Promise.all 的規則是這樣的:
傳入的所有 Promsie 都是 fulfilled,則返回由他們的值組成的,狀態為 fulfilled 的新 Promise;
只要有一個 Promise 是 rejected,則返回 rejected 狀態的新 Promsie,且它的值是第一個 rejected 的 Promise 的值;
只要有一個 Promise 是 pending,則返回一個 pending 狀態的新 Promise;
Promise.all = function(promiseArr) { let index = 0, result = [] return new Promise((resolve, reject) => { promiseArr.forEach((p, i) => { Promise.resolve(p).then(val => { index++ result[i] = val if (index === promiseArr.length) { resolve(result) } }, err => { reject(err) }) }) }) }
Promise.race 會返回一個由所有可迭代實例中第一個 fulfilled 或 rejected 的實例包裝后的新實例。
Promise.race = function(promiseArr) { return new Promise((resolve, reject) => { promiseArr.forEach(p => { Promise.resolve(p).then(val => { resolve(val) }, err => { rejecte(err) }) }) }) }
Promise.allSettled 的規則是這樣:
所有 Promise 的狀態都變化了,那么新返回一個狀態是 fulfilled 的 Promise,且它的值是一個數組,數組的每項由所有 Promise 的值和狀態組成的對象;
如果有一個是 pending 的 Promise,則返回一個狀態是 pending 的新實例;
Promise.allSettled = function(promiseArr) { let result = [] return new Promise((resolve, reject) => { promiseArr.forEach((p, i) => { Promise.resolve(p).then(val => { result.push({ status: 'fulfilled', value: val }) if (result.length === promiseArr.length) { resolve(result) } }, err => { result.push({ status: 'rejected', reason: err }) if (result.length === promiseArr.length) { resolve(result) } }) }) }) }
Promise.any 的規則是這樣:
空數組或者所有 Promise 都是 rejected,則返回狀態是 rejected 的新 Promsie,且值為 AggregateError 的錯誤;
只要有一個是 fulfilled 狀態的,則返回第一個是 fulfilled 的新實例;
其他情況都會返回一個 pending 的新實例;
Promise.any = function(promiseArr) { let index = 0 return new Promise((resolve, reject) => { if (promiseArr.length === 0) return promiseArr.forEach((p, i) => { Promise.resolve(p).then(val => { resolve(val) }, err => { index++ if (index === promiseArr.length) { reject(new AggregateError('All promises were rejected')) } }) }) }) }
“JS繼承類型有哪些”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。