12月的北京,寒氣逼人。網際網路行業的人才越來越多,競爭壓力越來越大。在從業人員越來越優秀的情況下,你的不進步就意味著退步。所以,只有多學習,多總結,才能在網際網路的寒冬中生存下來。以下是手撕代碼系列的第二篇,謹獻給大家,望和大家共同進步。
在寫map之前,我們可以先來看一個經典的面試題。
不賣關子,這個面試題的答案是[1, NaN, NaN]。為什麼呢?
我們先來看mdn上對map參數的介紹:
callback
生成新數組元素的函數,使用三個參數:
thisArg可選
執行 callback 函數時值被用作this。
在這道題中,map的參數是parseInt,我們再來看看mdn上parseInt的參數,
這個時候,我們結合map和parseInt各自的參數,可以知道map的返回值為
[parseInt(1, 0), parseInt(2, 1), parseInt(3, 2)],radix為0時,且string參數不以「0x」和「0」開頭時,按照10為基數處理。這個時候返回1。基數為1(1進位)表示的數中,最大值小於2,所以無法解析,返回NaN。基數為2的時候同理。
言歸正傳,我們的目標是實現一個Array.prototype.map,以下便是實現的代碼:
'use strict' Array.prototype.map = function (fn, context) { if (this === null || this === undefined) { throw new Error('Array.prototype.map called on null or undefined'); } if (typeof fn !== 'function') { throw new Error(`Uncaught typeError: ${fn} is not a function`); } const results = []; const list = this; for (let i = 0; i < list.length; i++) { const ret = fn.call(context, list[i], i, list); results.push(ret); } return results; }
console.log([1].map()); console.log(Array.prototype.map.call(undefined)); console.log([1, 2, 3, undefined].map(item => item)); console.log([1, 2, 3, undefined].map(item => item * 2)); 十一 call'use strict' Function.prototype.call = function(context, ...args){ const fn = Symbol('fn'); if(context === null || context === undefined){ fn(context); } if(typeof context !== 'object' && context !== undefined){ context = {}; } context[fn] = this; const results = context[fn](...args) delete context[fn]; return results; } function Test(){ this.name = 'breakair'; } Test.prototype.sayName = function(){ return `Hello, ${this.name}`; } const obj = {name: 'Bob'}; const test = new Test(); console.log(test.sayName()); console.log(test.sayName.call(obj)); console.log(test.sayName.call(undefined)); 十二 bind實現bind的過程中,我們需要注意在構造函數的情況下,bind的構造出來的實例的指向問題。
Function.prototype.bind = function(context, ...args){ const fn = this; function Fn(...inner){ return fn.call(this instanceof Fn? this: context, ...args, ...inner); } Fn.prototype = Object.create(fn.prototype); return Fn; } const obj = { name: 'breakair', sayName: function(){ return `Hello, ${this.name}`; } }; var name = 'Bob'; console.log(obj.sayName()); const sayName = obj.sayName; console.log(sayName()); const sayName1 = obj.sayName.bind(obj); console.log(sayName1()); function LateBloomer() { this.petalCount = Math.ceil(Math.random() * 12) + 1; }
LateBloomer.prototype.bloom = function() { window.setTimeout(this.declare.bind(this), 1000); };
LateBloomer.prototype.declare = function() { console.log('I am a beautiful flower with ' + this.petalCount + ' petals!'); };
var flower = new LateBloomer(); flower.bloom(); function Point(x, y) { this.x = x; this.y = y; }
Point.prototype.toString = function() { return this.x + ',' + this.y; };
var p = new Point(1, 2);
console.log(p.toString());
var YAxisPoint = Point.bind(null, 0);
var axisPoint = new YAxisPoint(5); console.log(axisPoint.toString());
console.log(axisPoint instanceof Point); console.log(axisPoint instanceof YAxisPoint); console.log(new YAxisPoint(17, 42) instanceof Point); 十三 對象的深拷貝function deepCopy(obj){ if(typeof obj !== 'object' || obj === null){ return obj } if(Array.isArray(obj)){ const results = []; for(let i = 0; i < obj.length; i++){ results[i] = deepCopy(obj[i]); } return results; } else { const results = {}; for(const key in obj){ if(obj.hasOwnProperty(key)){ results[key] = deepCopy(obj[key]); } } return results; } } const oriObj = { val: 1, children: { fn: () => { console.log('hello, this is copy') }, names: [1, 2, 3], } } const newObj = deepCopy(oriObj); console.log(newObj);十四 實現一個簡單的事件監聽class EventEmitter{ constructor(){ this.events = new Map(); } on(type, fn){ this.events.set(type, fn); } emit(type, ...args){ const handler = this.events.get(type); handler.call(this, ...args); } } let emitter = new EventEmitter() emitter.on('ages', age => { console.log(age) }) emitter.emit('ages', 18) 十五 雙向數據綁定let obj = {}let input = document.getElementById('input')let span = document.getElementById('span')Object.defineProperty(obj, 'text', { configurable: true, enumerable: true, get() { console.log('獲取數據了') }, set(newVal) { console.log('數據更新了') input.value = newVal span.innerHTML = newVal }})input.addEventListener('keyup', function(e) { obj.text = e.target.value})十六 簡單路由的實現class Route{ constructor(){ this.routes = {} this.currentHash = '' this.freshRoute = this.freshRoute.bind(this) window.addEventListener('load', this.freshRoute, false) window.addEventListener('hashchange', this.freshRoute, false) } storeRoute (path, cb) { this.routes[path] = cb || function () {} } freshRoute () { this.currentHash = location.hash.slice(1) || '/' this.routes[this.currentHash]() }}十七 實現懶加載<ul> <li><img src="./imgs/default.png" data="./imgs/1.png" alt=""></li> <li><img src="./imgs/default.png" data="./imgs/2.png" alt=""></li> <li><img src="./imgs/default.png" data="./imgs/3.png" alt=""></li> <li><img src="./imgs/default.png" data="./imgs/4.png" alt=""></li> <li><img src="./imgs/default.png" data="./imgs/5.png" alt=""></li> <li><img src="./imgs/default.png" data="./imgs/6.png" alt=""></li> <li><img src="./imgs/default.png" data="./imgs/7.png" alt=""></li> <li><img src="./imgs/default.png" data="./imgs/8.png" alt=""></li> <li><img src="./imgs/default.png" data="./imgs/9.png" alt=""></li> <li><img src="./imgs/default.png" data="./imgs/10.png" alt=""></li></ul>let imgs = document.querySelectorAll('img')let clientHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeightfunction lazyLoad () { let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop for (let i = 0; i < imgs.length; i ++) { let x = clientHeight + scrollTop - imgs[i].offsetTop if (x > 0 && x < clientHeight+imgs[i].height) { imgs[i].src = imgs[i].getAttribute('data') } }}
十八 rem的實現setRem()function setRem () { let doc = document.documentElement let width = doc.getBoundingClientRect().width let rem = width / 75 doc.style.fontSize = rem + 'px'}addEventListener("resize", setRem)