摘要:前端基本功示例代碼一點這里前端基本功示例代碼二點這里一像素偽類實現對于老項目,有沒有什么辦法能兼容的尷尬問題了,個人認為偽類是比較完美的方法了。
前端基本功-示例代碼 (一) 點這里
前端基本功-示例代碼 (二) 點這里
偽類 + transform 實現
對于老項目,有沒有什么辦法能兼容1px的尷尬問題了,個人認為偽類+transform是比較完美的方法了。
原理是把原先元素的 border 去掉,然后利用 :before 或者 :after 重做 border ,并 transform 的 scale 縮小一半,原先的元素相對定位,新做的 border 絕對定位。
單條border樣式設置:
.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樣式設置:
.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 屏:
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; } }2.動畫
animation:mymove 5s infinite; @keyframes mymove { from {top:0px;} to {top:200px;} }
js實現一個持續的動畫效果
//兼容性處理 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); })();3. 實現sum(2)(3)
// 寫一個 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]; };4. 函數柯里化
函數柯里化指的是將能夠接收多個參數的函數轉化為接收單一參數的函數,并且返回接收余下參數或結果的新函數的技術。
函數柯里化的主要作用和特點就是參數復用、提前返回和延遲計算/執行。
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
本小題來自:幾個讓我印象深刻的面試題(一)
2. 提前返回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);3. 延遲計算/運行
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)
5.手寫一個 bind 方法帶一個參數:
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 函數究竟做了哪些事
6.經典面試問題:new 的過程首先來看一下,函數聲明的過程
// 實際代碼 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; }
創建一個空對象 obj;
將新創建的空對象的隱式原型指向其構造函數的顯示原型。
使用 call 改變 this 的指向
如果無返回值或者返回一個非對象值,則將 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 } }7.javascript里面的繼承怎么實現,如何避免原型鏈上面的對象共享
什么是原型鏈
當一個引用類型繼承另一個引用類型的屬性和方法時候就會產生一個原型連。
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")8.繼承 JS 內置對象(Date)
寫在前面,本節只記錄了如何繼承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的,所以效果完全不一樣,會報錯的
9.簡易雙向數據綁定10.JavaScript實現發布-訂閱模式
發布-訂閱模式又叫觀察者模式,它定義對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴于它的對象都將得到通知。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();
推薦閱讀:Javascript設計模式之發布-訂閱模式
示例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嘗試一下:
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 } })();推薦閱讀:寫一個簡單vue 中間件,$emit、$on
示例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,"焦糖");推薦必讀:發布-訂閱模式
11.扁平化后的數組如:[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);12.冒泡排序解析:比較相鄰的兩個元素,如果前一個比后一個大,則交換位置。
第一輪的時候最后一個元素應該是最大的一個。
按照步驟一的方法進行相鄰兩個元素的比較,這個時候由于最后一個元素已經是最大的了,所以最后一個元素不用比較。
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);13.快速排序快速排序是對冒泡排序的一種改進
解析:第一趟排序時將數據分成兩部分,一部分比另一部分的所有數據都要小。
然后遞歸調用,在兩邊都實行快速排序。
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));14.選擇排序// 選擇排序:大概思路是找到最小的放在第一位,找到第二小的放在第二位,以此類推 算法復雜度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前端面試總結...
15.插入排序解析:從第一個元素開始,該元素可以認為已經被排序
取出下一個元素,在已經排序的元素序列中從后向前掃描
如果該元素(已排序)大于新元素,將該元素移到下一位置
重復步驟3,直到找到已排序的元素小于或者等于新元素的位置
將新元素插入到下一位置中
重復步驟2
js代碼實現
function insert_sort(arr){ var i=1, j,key,len=arr.length; for(;i華米科技 招前端-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
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/101083.html
摘要:前端基本功示例代碼一點這里前端基本功示例代碼二點這里一像素偽類實現對于老項目,有沒有什么辦法能兼容的尷尬問題了,個人認為偽類是比較完美的方法了。 前端基本功-示例代碼 (一) 點這里前端基本功-示例代碼 (二) 點這里 1.一像素 偽類 + transform 實現對于老項目,有沒有什么辦法能兼容1px的尷尬問題了,個人認為偽類+transform是比較完美的方法了。 原理是把原先元素...
摘要:后代選擇器后代選擇器又稱包含選擇器,用于選擇指定元素的后代元素。這些選擇器既可以是基本選擇器,也可以是一個復合選擇器。注意元素選擇器及和屬性選擇器之間沒有空格。 showImg(https://segmentfault.com/img/bVblJEJ?w=900&h=383); 復合選擇器是通過基本選擇器進行組合后構成的,常用的復合選擇器有: 交集選擇器 并集選持器 后代選擇器 子元...
摘要:介紹是許多流行文本編輯器的插件,它極大地改進了和工作流程為大部分流行的編輯器都提供了安裝插件,核心是縮寫語法鍵不同編輯器可自行設置,以下是我整理的常用知識點。 介紹:Emmet是許多流行文本編輯器的插件,它極大地改進了HTML和CSS工作流程 、為大部分流行的編輯器都提供了安裝插件,核心是縮寫語法+tab鍵(不同編輯器可自行設置),以下是我整理的常用知識點。 一、特性 1、支持定制: ...
摘要:介紹是許多流行文本編輯器的插件,它極大地改進了和工作流程為大部分流行的編輯器都提供了安裝插件,核心是縮寫語法鍵不同編輯器可自行設置,以下是我整理的常用知識點。 介紹:Emmet是許多流行文本編輯器的插件,它極大地改進了HTML和CSS工作流程 、為大部分流行的編輯器都提供了安裝插件,核心是縮寫語法+tab鍵(不同編輯器可自行設置),以下是我整理的常用知識點。 一、特性 1、支持定制: ...
摘要:在接觸前端開發起,跨域這個詞就一直以很高的頻率在我們學習工作中重復出現,最近在工作中遇到了跨域的相關問題,這里我把它總結記錄一下。 在接觸前端開發起,跨域這個詞就一直以很高的頻率在我們學習工作中重復出現,最近在工作中遇到了跨域的相關問題,這里我把它總結記錄一下。關于跨域,有N種類型,現在我只專注于ajax請求跨域(ajax跨域只是屬于瀏覽器同源策略中的一部分,其它的這里不做介紹),內容...
閱讀 2269·2021-11-23 09:51
閱讀 5657·2021-09-22 15:39
閱讀 3343·2021-09-02 15:15
閱讀 3494·2019-08-30 15:54
閱讀 2355·2019-08-30 15:53
閱讀 1397·2019-08-30 14:04
閱讀 2446·2019-08-29 18:33
閱讀 2364·2019-08-29 13:08