前端基本功-示例代碼 (一) 點這裏
前端基本功-示例代碼 (二) 點這裏javascript
僞類 + transform 實現
對於老項目,有沒有什麼辦法能兼容1px的尷尬問題了,我的認爲僞類+transform是比較完美的方法了。前端
原理是把原先元素的 border 去掉,而後利用 :before 或者 :after 重作 border ,並 transform 的 scale 縮小一半,原先的元素相對定位,新作的 border 絕對定位。
單條border樣式設置:vue
.scale-1px{ position: relative; border:none; } .scale-1px:after{ content: ''; position: absolute; bottom: 0; background: #000; width: 100%; height: 1px; -webkit-transform: scaleY(0.5); transform: scaleY(0.5); -webkit-transform-origin: 0 0; transform-origin: 0 0; }
四條boder樣式設置:java
.scale-1px{ position: relative; margin-bottom: 20px; border:none; } .scale-1px:after{ content: ''; position: absolute; top: 0; left: 0; border: 1px solid #000; -webkit-box-sizing: border-box; box-sizing: border-box; width: 200%; height: 200%; -webkit-transform: scale(0.5); transform: scale(0.5); -webkit-transform-origin: left top; transform-origin: left top; }
最好在使用前也判斷一下,結合 JS 代碼,判斷是否 Retina 屏:web
if(window.devicePixelRatio && devicePixelRatio >= 2){ document.querySelector('ul').className = 'scale-1px'; }
方法二面試
/*移動端正常展現1px的問題 start*/ %border-1px{ display: block; position:absolute; left: 0; width: 100%; content: ' '; } .border-1px{ position: relative; &::after{ @extend %border-1px; bottom: 0; border-top: 1px solid #ccc; } &::before{ @extend %border-1px; top: 0; border-bottom: 1px solid #ccc; } } @media (-webkit-min-device-pixel-ratio:1.5),(min-device-pixel-ratio:1.5){ .border-1px{ &::after{ -webkit-transform: scaleY(0.7); transform: scaleY(0.7); } } } @media (-webkit-min-device-pixel-ratio:2),(min-device-pixel-ratio:2){ .border-1px{ &::after{ -webkit-transform: scaleY(0.5); transform: scaleY(0.5); } } } /*移動端正常展現1px的問題 end*/
方法三算法
.hairline-border { box-shadow: 0 0 0 1px; } @media (min-resolution: 2dppx) { .hairline-border { box-shadow: 0 0 0 0.5px red; } } @media (min-resolution: 3dppx) { .hairline-border { box-shadow: 0 0 0 0.33333333px; } } @media (min-resolution: 4dppx) { .hairline-border { box-shadow: 0 0 0 0.25px; } }
animation:mymove 5s infinite; @keyframes mymove { from {top:0px;} to {top:200px;} }
js實現一個持續的動畫效果segmentfault
//兼容性處理 window.requestAnimFrame = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback){ window.setTimeout(callback, 1000 / 60); }; })(); var e = document.getElementById("e"); var flag = true; var left = 0; function render() { left == 0 ? flag = true : left == 100 ? flag = false : ''; flag ? e.style.left = ` ${left++}px` : e.style.left = ` ${left--}px`; } (function animloop() { render(); requestAnimFrame(animloop); })();
// 寫一個 function 讓下面兩行代碼輸出的結果都爲 5 console.log(sum(2, 3)); console.log(sum(2)(3));
實現設計模式
function sum() { var cache; if (arguments.length === 1) { cache = arguments[0]; return function ( number ) {return cache + number;}; } else return arguments[0] + arguments[1]; };
函數柯里化指的是將可以接收多個參數的函數轉化爲接收單一參數的函數,而且返回接收餘下參數或結果的新函數的技術。
函數柯里化的主要做用和特色就是參數複用、提早返回和延遲計算/執行。數組
引導
// 普通函數 function add(x,y){ return x + y; } add(3,4); //5 // 實現了柯里化的函數 // 接收參數,返回新函數,把參數傳給新函數使用,最後求值 let add = function(x){ return function(y){ return x + y; } }; add(3)(4); // 7
通用的柯里化函數
感受currying就是返回函數的函數,在此函數閉包中定義了私有域變量。
function curry(fn) { let slice = Array.prototype.slice, // 將slice緩存起來 args = slice.call(arguments, 1); // 這裏將arguments轉成數組並保存 return function() { // 將新舊的參數拼接起來 let newArgs = args.concat(slice.call(arguments)); return fn.apply(null, newArgs); // 返回執行的fn並傳遞最新的參數 } }
if (typeof Function.prototype.bind === "undefined"){ Function.prototype.bind = function (thisArgs){ var fn = this, slice = Array.prototype.slice, args = slice.call(arguments, 1); return function (){ let newArgs = args.concat(slice.call(arguments)) return fn.apply(thisArgs, newArgs); } } }
ES6版的柯里化函數
function curry(fn, ...allArgs) { const g = (...allArgs) => allArgs.length >= fn.length ? fn(...allArgs) : (...args) => g(...allArgs, ...args) return g; } // 測試用例 const foo = curry((a, b, c, d) => { console.log(a, b, c, d); }); foo(1)(2)(3)(4); // 1 2 3 4 const f = foo(1)(2)(3); f(5); // 1 2 3 5
function trueCurrying(fn, ...args) { if (args.length >= fn.length) { return fn(...args) } return function (...args2) { return trueCurrying(fn, ...args, ...args2) } } // 比較屢次接受的參數總數與函數定義時的入參數量, //當接受參數的數量大於或等於被 Currying 函數的傳入參數數量時, //就返回計算結果,不然返回一個繼續接受參數的函數。 //注意這點和上邊的區別
題目:須要寫一個函數,知足
curry(fn)(1)(2)(3) //6
var fn = function(a,b,c) { return a+b+c; } function curry(fn) { var arr = [], mySlice = arr.slice fnLen = fn.length; function curring() { arr = arr.concat(mySlice.call(arguments)); if(arr.length < fnLen) { return curring; } return fn.apply(this, arr); } return curring; } curry(fn)(1)(2)(3);//6
本小題來自:幾個讓我印象深入的面試題(一)
var addEvent = function(el, type, fn, capture) { if (window.addEventListener) { el.addEventListener(type, function(e) { fn.call(el, e); }, capture); } else if (window.attachEvent) { el.attachEvent("on" + type, function(e) { fn.call(el, e); }); } };
上面的方法有什麼問題呢?很顯然,咱們每次使用addEvent爲元素添加事件的時候,(eg. IE6/IE7)都會走一遍if...else if ...,其實只要一次斷定就能夠了,怎麼作?–柯里化。改成下面這樣子的代碼:
var addEvent = (function(){ if (window.addEventListener) { return function(el, sType, fn, capture) { el.addEventListener(sType, function(e) { fn.call(el, e); }, (capture)); }; } else if (window.attachEvent) { return function(el, sType, fn, capture) { el.attachEvent("on" + sType, function(e) { fn.call(el, e); }); }; } })();
初始addEvent的執行其實值實現了部分的應用(只有一次的if...else if...斷定),而剩餘的參數應用都是其返回函數實現的,典型的柯里化。
對比:惰性加載
let addEvent = function(ele, type, fn) { if (window.addEventListener) { addEvent = function(ele, type, fn) { ele.addEventListener(type, fn, false); } } else if (window.attachEvent) { addEvent = function(ele, type, fn) { ele.attachEvent('on' + type, function() { fn.call(ele) }); } } addEvent(ele, type, fn);
ES5中的bind方法
if (!Function.prototype.bind) { Function.prototype.bind = function(context) { var self = this, args = Array.prototype.slice.call(arguments); return function() { return self.apply(context, args.slice(1)); } }; }
推薦閱讀:從一道面試題認識函數柯里化
參考文章:ES6版的柯里化函數、JS中的柯里化(currying)
帶一個參數:
Function.prototype.bind = function(context) { let self = this, slice = Array.prototype.slice, args = slice.call(arguments); return function() { return self.apply(context, args.slice(1)); } };
帶多個參數:
//ES3實現 if(!Function.prototype.bind){ Function.prototype.bind = function(o, args){ var self = this, boundArgs = arguments;//注:arguments是指sum.bind(null,1)中的參數null和1 return function(){ //此時返回的只是一個函數 var args = [], i; for(var i=1; i< boundArgs.length; i++){ args.push(boundArgs[i]); } for(var i =0; i< arguments.length; i++){ args.push(arguments[i]);//注:這裏的arguments是指result(2)中的參數2 } return self.apply(o, args); } } }
或者
// 代碼來自書籍 《javaScript 模式》 if (typeof Function.prototype.bind === "undefined"){ Function.prototype.bind = function (thisArgs){ var fn = this, slice = Array.prototype.slice, args = slice.call(arguments, 1); return function (){ return fn.apply(thisArgs, args.concat(slice.call(arguments))); } } } //注:先後arguments不是一回事哦~ //調用 var sum = function(x,y){ return x+y }; var result = sum.bind(null,1); result(2); // 3
或者
Function.prototype.bind = function(){ var fn = this; var args = Array.prototye.slice.call(arguments); var context = args.shift(); return function(){ return fn.apply(context, args.concat(Array.prototype.slice.call(arguments))); };
本節參考文章:js中的bind
其餘文章:JavaScirpt 的 bind 函數究竟作了哪些事
首先來看一下,函數聲明的過程
// 實際代碼 function fn1() {} // JavaScript 自動執行 fn1.protptype = { constructor: fn1, __proto__: Object.prototype } fn1.__proto__ = Function.prototype
var a = new myFunction("Li","Cherry"); //僞代碼 new myFunction{ var obj = {}; obj.__proto__ = myFunction.prototype; var result = myFunction.call(obj,"Li","Cherry"); return typeof result === 'object'? result : obj; }
因此咱們能夠看到,在 new 的過程當中,咱們是使用 call 改變了 this 的指向。
var NEW = function(func) { var o = Object.create(func.prototype) var k = func.call(o) if (typeof k === 'object') { return k } else { return o } }
什麼是原型鏈
當一個引用類型繼承另外一個引用類型的屬性和方法時候就會產生一個原型連。
ES5:寄生組合式繼承:經過借用構造函數來繼承屬性和原型鏈來實現子繼承父。
function ParentClass(name) { this.name = name; } ParentClass.prototype.sayHello = function () { console.log("I'm parent!" + this.name); } function SubClass(name, age) { //如果要多個參數能夠用apply 結合 ...解構 ParentClass.call(this, name); this.age = age; } SubClass.prototype.sayChildHello = function (name) { console.log("I'm child " + this.name) } SubClass.prototype = Object.create(ParentClass.prototype); SubClass.prototype.constructor = SubClass; let testA = new SubClass('CRPER') // Object.create()的polyfill /* function pureObject(obj){ //定義了一個臨時構造函數 function F() {} //將這個臨時構造函數的原型指向了傳入進來的對象。 F.prototype = obj; //返回這個構造函數的一個實例。該實例擁有obj的全部屬性和方法。 //由於該實例的原型是obj對象。 return new F(); } */ 或 function subClass() { superClass.apply(this, arguments); this.abc = 1; } function inherits(subClass, superClass) { function Inner() {} Inner.prototype = superClass.prototype; subClass.prototype = new Inner(); subClass.prototype.constructor = subClass; } inherits(subClass, superClass); subClass.prototype.getTest = function() { console.log("hello") };
ES6: 其實就是ES5的語法糖,不過可讀性很強..
class ParentClass { constructor(name) { this.name = name; } sayHello() { console.log("I'm parent!" + this.name); } } class SubClass extends ParentClass { constructor(name) { super(name); } sayChildHello() { console.log("I'm child " + this.name) } // 從新聲明父類同名方法會覆寫,ES5的話就是直接操做本身的原型鏈上 sayHello(){ console.log("override parent method !,I'm sayHello Method") } } let testA = new SubClass('CRPER')
寫在前面,本節只記錄了如何繼承Date對象...的解決方案,具體問題和解析過程請看原文
ES5
// 須要考慮polyfill狀況 Object.setPrototypeOf = Object.setPrototypeOf || function(obj, proto) { obj.__proto__ = proto; return obj; }; /** * 用了點技巧的繼承,實際上返回的是Date對象 */ function MyDate() { // bind屬於Function.prototype,接收的參數是:object, param1, params2... var dateInst = new(Function.prototype.bind.apply(Date, [Date].concat(Array.prototype.slice.call(arguments))))(); // 更改原型指向,不然沒法調用MyDate原型上的方法 // ES6方案中,這裏就是[[prototype]]這個隱式原型對象,在沒有標準之前就是__proto__ Object.setPrototypeOf(dateInst, MyDate.prototype); dateInst.abc = 1; return dateInst; } // 原型從新指回Date,不然根本沒法算是繼承 Object.setPrototypeOf(MyDate.prototype, Date.prototype); MyDate.prototype.getTest = function getTest() { return this.getTime(); }; let date = new MyDate(); // 正常輸出,譬如1515638988725 console.log(date.getTest());
ES6
class MyDate extends Date { constructor() { super(); this.abc = 1; } getTest() { return this.getTime(); } } let date = new MyDate(); // 正常輸出,譬如1515638988725 console.log(date.getTest());
注意:這裏的正常輸出環境是直接用ES6運行,不通過babel打包,打包後實質上是轉化成ES5的,因此效果徹底不同,會報錯的
<body> <input type="text" id="foo"> <p id="test"></p> <script> var user = {} Object.defineProperty(user, 'inputValue', { configurable: true, get: function() { return document.getElementById('foo').value }, set: function(value) { document.getElementById('foo').value = value document.getElementById('test').innerHTML = value } }) document.getElementById('foo').addEventListener('keyup', function() { document.getElementById('test').innerHTML = user.inputValue }) </script> </body>
發佈-訂閱模式又叫觀察者模式,它定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴於它的對象都將獲得通知。JavaScript開發中咱們通常用事件模型來代替傳統的發佈-訂閱模式
示例1
function Dep() {//發佈者 this.subs = []; } Dep.prototype.addSub = function (sub) { this.subs.push(sub); } Dep.prototype.notify = function () { this.subs.forEach(sub=>sub.update()); } function Watcher(fn) {//訂閱者 this.fn = fn; } Watcher.prototype.update = function () { this.fn(); } var dep = new Dep(); dep.addSub(new Watcher(function () { console.log('okokok'); })) dep.notify();
示例2
function Event(){ this.list={}, this.on=function(key,cb){//訂閱事件 if(!this.list[key]){ this.list[key] = [] } this.list[key].push(cb) }, this.emit = function(){//觸發事件 var key = Array.prototype.shift.call(arguments) var e = this.list[key] if(!e){ return } var args = Array.prototype.slice.call(arguments) for(var i = 0;i<e.length;i++){ e[i].apply(null,args) } } }
嘗試一下:
var a = new Event() a.on('a',function(x){console.log(x)}) a.emit('a',1)//1
推薦閱讀:從單向到雙向數據綁定
示例3
var myBus = (function() { var clienlist = {}, addlisten, trigger, remove; /** * 增長訂閱者 * @key {String} 類型 * @fn {Function} 回掉函數 * */ addlisten = function(key, fn) { if(!clienlist[key]) { clienlist[key] = []; } clienlist[key].push(fn); }; /** * 發佈消息 * */ trigger = function() { var key = [].shift.call(arguments), //取出消息類型 fns = clienlist[key]; //取出該類型的對應的消息集合 if(!fns || fns.length === 0) { return false; } for(var i = 0, fn; fn = fns[i++];) { fn.apply(this, arguments); } }; /** * 刪除訂閱 * @key {String} 類型 * @fn {Function} 回掉函數 * */ remove = function(key, fn) { var fns = clienlist[key]; //取出該類型的對應的消息集合 if(!fns) { //若是對應的key沒有訂閱直接返回 return false; } if(!fn) { //若是沒有傳入具體的回掉,則表示須要取消全部訂閱 fns && (fns.length = 0); } else { for(var l = fns.length - 1; l >= 0; l--) { //遍歷回掉函數列表 if(fn === fns[l]) { fns.splice(l, 1); //刪除訂閱者的回掉 } } } }; return { $on: addlisten, $emit: trigger, $off: remove } })();
示例4
這個示例更像示例2、示例3的總結,我也放這裏吧,多看幾種寫法也多少開闊一下思路或全當複習
賣燒餅的店主能夠把小明、小龍的電話記錄下來,等店裏有燒餅了在通知小龍小明來拿這就是所謂的發佈-訂閱模式,代碼以下:
/*燒餅店*/ var Sesamecakeshop={ clienlist:[],//緩存列表 addlisten:function(fn){//增長訂閱者 this.clienlist.push(fn); }, trigger:function(){//發佈消息 for(var i=0,fn;fn=this.clienlist[i++];){ fn.apply(this,arguments); } } } /*小明發布訂閱*/ Sesamecakeshop.addlisten(function(price,taste){ console.log("小明發布的"+price+"元,"+taste+"味道的"); }); /*小龍發佈訂閱*/ Sesamecakeshop.addlisten(function(price,taste){ console.log("小龍發佈的"+price+"元,"+taste+"味道的"); }); Sesamecakeshop.trigger(10,"椒鹽");
從代碼中能夠看出,只有小明,小龍預約了燒餅,燒餅店就能夠發佈消息告訴小龍與小明。可是有個問題不知道你們發現了沒有。小明只喜歡椒鹽味道的。而小龍只喜歡焦糖味道的。上面的代碼就知足不了客戶的需求,給客戶一種感受就是,無論我喜歡不喜歡,你都會發給我。若是發佈比較多,客戶就會感到厭煩,甚至會想刪除訂閱。下邊是對代碼進行改良你們能夠看看。
/*燒餅店*/ var Sesamecakeshop={ clienlist:{},/*緩存列表*/ /** * 增長訂閱者 * @key {String} 類型 * @fn {Function} 回掉函數 * */ addlisten:function(key,fn){ if(!this.clienlist[key]){ this.clienlist[key]=[]; } this.clienlist[key].push(fn); }, /** * 發佈消息 * */ trigger:function(){ var key=[].shift.call(arguments),//取出消息類型 fns=this.clienlist[key];//取出該類型的對應的消息集合 if(!fns || fns.length===0){ return false; } for(var i=0,fn;fn=fns[i++];){ fn.apply(this,arguments); } }, /** * 刪除訂閱 * @key {String} 類型 * @fn {Function} 回掉函數 * */ remove:function(key,fn){ var fns=this.clienlist[key];//取出該類型的對應的消息集合 if(!fns){//若是對應的key沒有訂閱直接返回 return false; } if(!fn){//若是沒有傳入具體的回掉,則表示須要取消全部訂閱 fns && (fns.length=0); }else{ for(var l=fns.length-1;l>=0;l--){//遍歷回掉函數列表 if(fn===fns[l]){ fns.splice(l,1);//刪除訂閱者的回掉 } } } } } /*小明發布訂閱*/ Sesamecakeshop.addlisten("焦糖",fn1=function(price,taste){ console.log("小明發布的"+price+"元,"+taste+"味道的"); }); /*小龍發佈訂閱*/ Sesamecakeshop.addlisten("椒鹽",function(price,taste){ console.log("小龍發佈的"+price+"元,"+taste+"味道的"); }); Sesamecakeshop.trigger("椒鹽",10,"椒鹽"); Sesamecakeshop.remove("焦糖",fn1);//注意這裏是按照地址引用的。若是傳入匿名函數則刪除不了 Sesamecakeshop.trigger("焦糖",40,"焦糖");
推薦必讀:發佈-訂閱模式
如:[1, [2, [ [3, 4], 5], 6]] => [1, 2, 3, 4, 5, 6]
var data = [1, [2, [ [3, 4], 5], 6]]; function flat(data, result) { var i, d, len; for (i = 0, len = data.length; i < len; ++i) { d = data[i]; if (typeof d === 'number') { result.push(d); } else { flat(d, result); } } } var result = []; flat(data, result); console.log(result);
解析:
js代碼實現
function bubble_sort(arr){ for(var i = 0;i < arr.length - 1; i++){ for(var j = 0;j < arr.length - i - 1;j++){ if(arr[j] > arr[j+1]){ [arr[j], arr[j+1]] = [arr[j + 1], arr[j]] } } } } var arr = [3,1,5,7,2,4,9,6,10,8]; bubble_sort(arr); console.log(arr);
快速排序是對冒泡排序的一種改進
解析:
js代碼實現
function quick_sort(arr){ if(arr.length <= 1){ return arr; } var pivotIndex = Math.floor(arr.length / 2); var pivot = arr.splice(pivotIndex, 1)[0]; var left = []; var right = []; for (var i = 0;i < arr.length; i++) { if(arr[i] < pivot){ left.push(arr[i]); } else { right.push(arr[i]); } } return quick_sort(left).concat([pivot],quick_sort(right)); } var arr=[5,6,2,1,3,8,7,1,2,3,4,7]; console.log(quick_sort(arr));
// 選擇排序:大概思路是找到最小的放在第一位,找到第二小的放在第二位,以此類推 算法複雜度O(n^2) 選擇demo: function selectionSort(arr) { let len = arr.length; let minIndex; for (let i = 0; i < len - 1; i++) { minIndex = i; for (let j = i + 1; j < len; j++) { if (arr[j] < arr[minIndex]) { //尋找最小的數 minIndex = j; //將最小數的索引保存 } } [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]; } return arr; }
本節參考文章:2018前端面試總結...
解析:
js代碼實現
function insert_sort(arr){ var i=1, j,key,len=arr.length; for(;i<len;i++){ var j=i; var key=arr[j]; while(--j>-1){ if(arr[j]>key){ arr[j+1]=arr[j]; }else{ break; } } arr[j+1]=key; } return arr; } 或 function insert_sort(arr) { let len = arr.length; let preIndex, current; for (let i = 1; i < len; i++) { preIndex = i - 1; current = arr[i]; while (preIndex >= 0 && arr[preIndex] > current) { arr[preIndex + 1] = arr[preIndex]; preIndex--; } arr[preIndex + 1] = current; } return arr; } insert_sort([2,34,54,2,5,1,7]);
聯繫: 於夢中(wx:tsw0618) 內推,備註來意,簡歷請甩 weihongjie@huami.com