-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
89 lines (89 loc) · 79.5 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[Javascript异步编程(一)]]></title>
<url>%2Fposts%2F68bb3b82%2F</url>
<content type="text"><![CDATA[我们知道Javascript是单线程的,同一时间只能执行一个任务,这就意味着即使一个任务耗时很长,下一个任务也只能等下去,整个程序就像卡死了一样。为了解决这个问题,Javascript将任务分为同步任务与异步任务:同步任务会阻塞程序,只有一个任务执行完才会开始下一个任务;异步任务则是非阻塞的,异步任务没有完成的时候下一个任务就可以开始执行,常见的异步任务有setTimeout,Ajax操作等。 由于异步不会阻塞程序,因此从设计上说更有优势。然而异步会使得程序的流程控制变得麻烦,如何编写出优雅的异步代码一直困扰着人们,下面介绍几种常见的异步编程方法。 callback这是大家最熟悉的异步编程方式了,例如我们在setTimeout里面就会指定一个回调函数,在时间到的时候执行;又或者在发起Ajax请求的时候也会指定一个回调函数,在请求返回的时候执行。 1234567891011121314151617function getSomething(url,callback){ var xhr = new XMLHttpRequest(); xhr.open("GET",url,true); xhr.onreadystatechange = function(){ if (xhr.readyState === 4 ) { if (xhr.status === 200) { callback.call(this,xhr.responseText) // 执行回调 } } }}var print = function(text){ console.log(text);}getSomething('http://m.test.com/api',print); // 执行异步操作 优点 简单、容易理解 非常容易部署到自己的库/函数中 缺点 容易导致嵌套过深,造成所谓回调地狱,导致代码高度耦合,可读性差 每次只能传递一个回调函数,有很大局限 不容易进行错误处理,一是因为异步函数是立即返回的,异步事物中发生的错误无法通过try-catch捕获;二是回调函数执行时异步函数的上下文已经不存在了 generatorgenerator是es6新提出的一个东西,可以用于异步编程。不过目前看来这只是一个过渡方案,了解即可,未来还是属于下面会提到的promise。 generator跟普通函数很像,区别有两点: 声明的时候需要在function后面加一个*。就像这样:function* generator(){} 内部可以使用yield语句,这个语句只能用于generator 调用generator的时候并不会立即执行函数,而是返回一个迭代器,调用这个迭代的next方法可以遍历到函数内部的下一个状态,即运行到下一个yield。 1234567891011121314function* generator(){ yield 'hello'; yield 'ecmascript'; return 'end';}var gen = generator();gen.next();// Object { value: "hello", done: false }gen.next()// Object { value: "ecmascript", done: false }gen.next()// Object { value: "end", done: true }gen.next()// Object { value: undefined, done: true } 显然yield和return出来的值会出现在value字段中,可以通过done字段知道是否已经遍历完。 另外我们也可以使用for..of循环来遍历,不过此时不会遍历到return的值。12345for(let x of generator()){ console.log(x);}// hello// ecmascript 虽然yield只能用在generator里面,不过generator不一定要有yield。没有yield的generator相当于一个暂缓执行函数。 另外要注意一点:,yield只能用在generator函数中,因此要注意不要讲yield放在generator里面的回调函数中。 Generator 函数可以暂停执行和恢复执行,这是它能封装异步任务的根本原因。除此之外,它还有两个特性,使它可以作为异步编程的完整解决方案:函数体内外的数据交换和错误处理机制。 所谓函数体内外的数据交换就是指既可以通过next方法返回的对象的value字段将数据传递出来,也可以通过给next方法传参把数据传递到函数内部。next方法的参数会作为该实例内部上一个yield语句的返回值,如不通过next方法传值,yield语句的返回值总是undefined。 generator函数内部也能捕获函数体外抛出的错误。Generator实例的throw方法可以在函数体外抛出错误,并在函数体内捕获,但是同时只能捕获一条错误。如果函数体内没有使用try catch,没有捕获外部throw方法抛出的错误,那么该错误也能在外部被捕获。 这意味着,出错的代码与处理错误的代码,实现了时间和空间上的分离,这对于异步编程无疑是很重要的。 说了这么多,下面谈谈如何使用generator来做异步编程。下面的例子来自阮一峰的Generator 函数的含义与用法。 12345678910111213141516var fetch = require('node-fetch');function* gen(){ var url = 'https://api.github.com/users/github'; var result = yield fetch(url); console.log(result.bio);}var g = gen();var result = g.next();result.value.then(function(data){ return data.json();}).then(function(data){ g.next(data);}); 代码中首先『实例化』了一个generator函数,然后调用next方法,即执行函数内部的fetch,由于fetch返回的是一个promise,因此要用then方法执行下一个next方法。 可以看到使用了generator函数的确能让异步操作变得简洁,然而流程控制依然不方便。generator用于异步编程终究是一个过渡方案,下面要介绍的promise+async/await才是『终极』的解决方案。 promise+async/awaitPromise是什么这里就不介绍了,不了解的可以看阮一峰的es6入门。这里具体讲讲async/await。 先看一个例子 12345678910111213141516var sleep = function (time) { return new Promise(function (resolve, reject) { setTimeout(function () { resolve(); }, time); })};var start = async function () { // 在这里使用起来就像同步代码那样直观 console.log('start'); await sleep(3000); console.log('end');};start();// 输出start,3秒后输出end async放在function前面用于修饰,代表这是一个async函数,await只能用在async修饰的函数里面。 await代表这里等待一个promise对象返回,函数的执行会暂停在这里,知道promise返回了结果。 明显这样的写法很像同步写法,十分优雅。 另外还有一些要注意的: 通过await可以获得promise resolve出来的值,而不必通过then 1234var start = async function () { let result = await sleep(3000); console.log(result); // 收到 ‘ok’}; 通过await也能直接通过try catch捕获错误 1234567891011var start = async function () { try { console.log('start'); await sleep(3000); // 这里得到了一个返回错误 // 所以以下代码不会被执行了 console.log('end'); } catch (err) { console.log(err); // 这里捕捉到错误 `error` }}; await也能写在循环里,并且因为类似同步代码,使用await不必担心出现以往需要用闭包解决的问题。使用循环时需要注意,await只能在async修饰的函数里面执行,不要在forEach里面的函数使用await。 123456var start = async function () { for (var i = 1; i <= 10; i++) { console.log(`当前是第${i}次等待..`); await sleep(1000); // 不需要用闭包 }}; 总结拥抱promise+async/await吧,相信这个就是终极的异步编程方法。 ReferenceJavascript异步编程的4种方法 ES6 - Note7:Generator函数 Generator 函数的含义与用法 体验异步的终极解决方案-ES7的Async/Await 快速理解和使用 ES7 await/async]]></content>
<tags>
<tag>Javascript</tag>
</tags>
</entry>
<entry>
<title><![CDATA[webpack(一)]]></title>
<url>%2Fposts%2Ff1c99717%2F</url>
<content type="text"><![CDATA[webpack是用来做什么的就不赘述了,我们直接进入主题吧。 webpack中的核心概念webpack中有四个核心概念: Entry 给webpack指定入口文件,webpack会从入口文件出发,处理各种依赖,最终打包出叫bundles的东西 可以指定一个文件,也可以指定多个 entry的值可以是一个字符串,一个数组,一个对象 值是一个字符串时,字符串就是那个入口文件的路径 123module.exports = { entry: './path/to/my/entry/file.js'}; 值是一个数组用于多入口文件的情景,数组里面的值都是字符串,并且每一个值代表一个入口文件。 123module.exports = { entry: ['./user', './account']}; 将一个对象作为entry的值似乎有点过于麻烦,不过这种方法更加灵活,也能构建出几个相互独立的文件。注意,下面的例子表明这几种形式是可以组合在一起的。 123456module.exports = { entry: { home: './home', user: ['./user', './account'] },} Output 用来告诉webpack打包后生成的bundles文件应该叫什么和应该去哪里 123456789const path = require('path');// 例子里面用到了path,详情可以查阅相关文档module.exports = { entry: './path/to/my/entry/file.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'my-first-webpack.bundle.js' }}; filename也可以使用变量,不必写死:filename: '[name].bundle.[hash].js' Loaders loader和plugin算是webpack里面最核心的最有用的东西了 webpack本身是只能处理js文件的,为了能处理其他文件(如css)我们需要借助loader loader有两个重要的属性: test属性表明哪一些文件需要被处理,一般是一个正则表达式 use属性表明处理这些文件要用哪个loader,一般是一个字符串,也可以是一个数组。 值是一个数组的情况一般用于链式调用loader,指定类型的文件会先被数组中最后一个loader处理,处理完之后的产出交给倒数第二个loader处理,直到被第一个loader处理完。12345678910111213141516const path = require('path');const config = { entry: './path/to/my/entry/file.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'my-first-webpack.bundle.js' }, module: { rules: [ { test: /\.txt$/, use: 'raw-loader' } ] }};module.exports = config; 留意到上面说到的test和use属性时放在rules数组里面的一个对象里的,并且这个rules必须作为module对象的属性 Plugins 用于拓展webpack的功能,能让webpack实现压缩文件、优化bundle、设置环境变量等诸多功能 使用plugin前需要先require进去,然后把它添加到config下一个叫plugins的数组中。 plugin的设置是通过options来实现的 另外由于你可以将同一个plugin使用多次,你需要通过new操作符新建一个实例12345678910111213141516171819202122const HtmlWebpackPlugin = require('html-webpack-plugin'); //installed via npmconst webpack = require('webpack'); //to access built-in pluginsconst path = require('path');const config = { entry: './path/to/my/entry/file.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'my-first-webpack.bundle.js' }, module: { rules: [ { test: /\.txt$/, use: 'raw-loader' } ] }, plugins: [ new webpack.optimize.UglifyJsPlugin(), new HtmlWebpackPlugin({template: './src/index.html'}) ]};module.exports = config; 几个例子下面通过几个例子来讲解一下 例子一:12345678910111213const path = require('path')module.exports = { context: path.join(__dirname, './src'), entry: { home: './home', user: ['./user', './account'] }, output: { path: path.join(__dirname, './dist'), filename: '[name].bundle.[hash].js' }} 在这例子中我们没有用到任何loader以及plugin,webpack只是通过入口文件,找到文件之间的依赖关系,并根据依赖关系最后打包出一个文件。 有几点需要解释一下: context: path.join(__dirname, './src'),用于指明当前是在src目录下,因此后面描述类似./src/home这样的路径时直接写./home就可以了 entry里面是一个对象,里面有两个属性,因此我们将会打包出两个chunk,也就是会有两个文件。其中user属性的值是一个数组,表明他有两个入口文件 path: path.join(__dirname, './dist'),表明文件会输出在项目目录的dist文件夹下 filename: '[name].bundle.[hash].js'表明我们没有写死输出文件的名字,而是使用映射变量[name]和[hash]组合一个名字 例子二:1234567891011121314151617module.exports = { context: __dirname + '/src', entry: './', module: { rules: [ { test: /\.css$/, include: [ __dirname + '/src'], use: ['style-loader', 'css-loader'] } ] }, output: { path: __dirname + '/dist', filename: '[name].bundle.js' }} 这里面我们使用了style-loader以及css-loader来处理css文件。 解释一下: 我们之前提到使用loader时最关键的是test和use属性,并且要把拥有这两个属性的对象放在一个叫rules的数组里面,而这个rules就是module的一个属性 include: [ __dirname + '/src'],代表只对src目录下的css文件进行处理 例子三:12345678910111213141516171819202122232425var ExtractTextPlugin = require('extract-text-webpack-plugin')module.exports = { context: __dirname + '/src', entry: './', module: { rules: [{ test: /\.css$/, include: [ __dirname + '/src' ], use: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader' }) }] }, output: { path: __dirname + '/dist', filename: '[name].bundle.[hash].js' }, plugins: [ new ExtractTextPlugin('[name].css') ]} 这个例子介绍一个叫extract-text-webpack-plugin的插件。我们注意到之前打包生成的文件只有一个js文件并且里面含有css代码,而这个插件主要用于将css从js文件中提取出来,生成单独的css文件。 这里主要解释一下用法: 使用插件前必须先require:var ExtractTextPlugin = require('extract-text-webpack-plugin') 然后再plugin数组中新建一个实例:new ExtractTextPlugin('[name].css') 新建的时候可以传递一些配置,具体可以设置什么看这里 然后还要修改loaders里面有关css文件的use:1234use: ExtractTextPlugin.extract({ fallback: "style-loader", // 编译后用什么loader来提取css文件 use: "css-loader" // 指需要什么样的loader去编译文件,这里由于源文件是.css所以选择css-loader}) 例子四:这个例子是一个综合运用上面说到的知识的例子,在这个例子中,不同的命令会产生不同的结果: npm run build时会产生css文件并且js被压缩 npm run dev时则不会产生css文件,js也不会被压缩12345678910111213141516171819202122232425262728293031323334353637383940414243var path = require('path')var webpack = require('webpack')var ExtractTextPlugin = require('extract-text-webpack-plugin')// 这个在 npm run dev 和 npm run build 时候是不同的var TARGET = process.env.npm_lifecycle_eventvar APP_PATH = path.join(__dirname, '/src')// 根据TARGET不同设置plugin设置var uglify = new webpack.optimize.UglifyJsPlugin({compress: {warnings: false}});var setPlugins = [];if (TARGET === 'build') { setPlugins = [ new ExtractTextPlugin('./[name].css'), uglify ]}else if (TARGET === 'dev'){ setPlugins = [ new ExtractTextPlugin({disable: true}) ]}const config = { entry: path.join(APP_PATH, '/index.js'), output:{ path: path.resolve(__dirname, 'dist'), filename: '[name].js' }, module: { rules: [ { test: /\.css$/, use: ExtractTextPlugin.extract({ fallback: "style-loader", use: "css-loader" }) } ] }, plugins: setPlugins}module.exports = config; 其他一些东西除了提到的这些以外,webpack还有其他一些loader和plugin很有用: autoprefixer–用于自动给css属性添加前缀,实现兼容 commonChunk–用于自动识别项目中有哪些文件是共用的,会只保留一份 webpack还具有异步加载能力,通过异步加载可以提高网站的首屏加载速度。 Referencewebpack concept 详解前端模块化工具-webpack]]></content>
<tags>
<tag>Javascript</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Javascript设计模式简介]]></title>
<url>%2Fposts%2F7e963259%2F</url>
<content type="text"><![CDATA[设计模式能帮组我们解决很多软件设计上常见的问题。下面介绍两种Javascript中常用的设计模式,他们十分类似但也有不同。注意,设计模式并不限于某种语言。 观察者模式在这个模式中,有一个叫目标的东西,它维护这一个依赖于它的观察者列表,当特定的事情发生时,目标会通知在它的观察者列表中的所有观察者。如果某个观察者想接收某个目标的通知,该目标就讲这个观察者添加到自己的观察者列表,如果不再想接收该目标的通知,该目标就可以从自己的观察者列表中移除这个通知。下面展示一个示意图: 解释一下里面的名词: Subject-目标 具体目标的基类 维护一个观察者列表 维护一些用于添加、删除、通知观察者的方法 Observer-观察者 具体观察者的基类 提供更新接口,供目标调用 ConcreteSubject-具体目标 继承目标类 当状态改变时通知自己的观察者(即调度观察者的更新方法) ConcreteObserver-具体观察者 继承观察者类 把自己注册到具体目标中 实现自己的更新接口(更新方法) 这本书中讲到了一个例子,我觉得很好的展示了观察者模式,下面主要讲讲这个例子: 这个例子大概就是,有一个按钮用于添加checkbox,有一个container用于装添加的checkbox,有一个checkbox(旧checkbox)用来控制其他新添加checkbox的状态。 新添加的checkbox们就是具体观察者,他们的更新接口(方法)就是一个update函数,函数所做的事就是将具体观察者们的checked属性置为跟旧checkbox的一样。 旧checkbox就是具体目标,点击它会修改自己的checked属性(默认行为),同时会notify他的具体观察者,即调用他们的更新接口 点击按钮就会创建一个新的checkbox并添加到container内,同时会调用具体目标的添加观察者方法 步骤如下: 首先有一个观察者列表的类,方便目标维护一个观察者列表 构建目标类,主要就是添加一些用于添加、删除、通知观察者(即调用观察者更新接口)的方法 构建观察者类,实现一个默认更新接口,这个接口可以被具体观察者的实现覆盖 写html 获取dom 创建具体目标:旧checkbox的dom对象继承目标类 给具体目标添加点击事件,点击的时候调用具体目标的notify方法 给按钮添加点击事件,点击的时候新建一个checkbox的dom对象,给对象继承观察者类(构建具体观察者),实现具体观察者的update方法,调用具体目标的添加方法,将具体观察者添加进去,将新checkbox dom对象添加到container 在这个例子中首先定义了一个ObserverList类,用于维护一个观察者列表。 为什么观察者模式里面要额外维护一个观察者列表? 答:实际开发中一般在观察者列表和目标之间会有一个中间层负责调度,例如控制一下谁能够添加到观察者列表中,可以做权限管理和控制 而观察者列表可以做得十分通用,十分简单 12345678910111213141516171819202122232425262728293031323334function ObserverList(){ this.observerList = [];} ObserverList.prototype.add = function( obj ){ return this.observerList.push( obj );}; ObserverList.prototype.count = function(){ return this.observerList.length;}; ObserverList.prototype.get = function( index ){ if( index > -1 && index < this.observerList.length ){ return this.observerList[ index ]; }}; ObserverList.prototype.indexOf = function( obj, startIndex ){ var i = startIndex; while( i < this.observerList.length ){ if( this.observerList[i] === obj ){ return i; } i++; } return -1;}; ObserverList.prototype.removeAt = function( index ){ this.observerList.splice( index, 1 );}; 可以思考一下为什么要有get、indexOf等貌似多次一举的方法,为什么不直接用数组方法? 答:因为有可能不是一个数组,这时候是没法直接用下标或者别的什么方式获取的。通过实现get接口,使用者可以完全不需要关心内部实现,不需要知道内部是数组还是对象什么的。其实这就是一个高内聚的特点,就是一个彻底地封装 接着我们定义一个目标(subject)类,里面维护一个刚刚定义的ObserverList的实例123456789101112131415161718function Subject(){ this.observers = new ObserverList();} Subject.prototype.addObserver = function( observer ){ this.observers.add( observer );}; Subject.prototype.removeObserver = function( observer ){ this.observers.removeAt( this.observers.indexOf( observer, 0 ) );}; Subject.prototype.notify = function( context ){ var observerCount = this.observers.count(); for(var i=0; i < observerCount; i++){ this.observers.get(i).update( context ); }}; 可以看到目标类的作用主要就是三个: 添加观察者 移除观察者 通知观察者(即调用观察者列表里面所有观察者的更新(update)方法 接下来定义观察者类,注意这只是一个框架,里面的update方法可以在后来被覆盖。123456// The Observerfunction Observer(){ this.update = function(){ // ... };} 三个类写好之后,下面我们来看看如何使用。首先我们的html是这样的:123<div class="observerContainer"></div><input type="checkbox" name="subjectCheckbox" class="subjectCheckbox"><button class="addObserverBtn">click here to add a observer</button> 然后是我们的js代码,我们额外定义了一个extend函数方便我们合并实例:12345678910111213141516171819202122232425262728function extend(target, extension){ for(var key in extension){ target[key] = extension[key]; }}var addBtn = document.querySelector(".addObserverBtn"), container = document.querySelector(".observerContainer"), mainCheckbox= document.querySelector(".subjectCheckbox");extend(mainCheckbox, new Subject());mainCheckbox.addEventListener("click",function(){ mainCheckbox.notify(mainCheckbox.checked);})addBtn.addEventListener('click',function(){ let checkbox = document.createElement("input"); checkbox.type = "checkbox"; extend(checkbox, new Observer()); checkbox.update = function(value){ this.checked = value; } mainCheckbox.addObserver(checkbox); container.appendChild(checkbox);}) subjectCheckbox是我们的具体目标,点击subjectCheckbox会改变自己的checked属性,同时会调用具体目标的通知方法,即调用调用具体目标的观察者列表中的所有观察者的update方法,并把checked属性的值作为参数传进去。 而在这里checkbox就是我们的具体观察者,他的update方法就是根据传进来的参数修改自己的checked属性。 点击addBtn会创建一个checkbox,并调用具体目标的添加观察者方法将checkbox添加进具体目标的观察者列表里。 在这个例子中我们没有使用移除观察者的方法。 发布/订阅模式(Publish/Subscribe Pattern) 比较概念的解释是,订阅者把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者发布该事件到调度中心(顺带上下文),由调度中心统一调度订阅者注册到调度中心的处理代码。 我的理解中,其实比较类似js中的事件。在js中引擎就是调度中心,我们给某个对象添加事件就是将事件处理程序注册到调度中心,这里对象就是订阅者。事件发生的时候其实就是发起事件者给调度中心发布,这里的发起事件者就是发布者。调度中心指导之后就统一掉用注册到调度各个事件处理程序。 在这个过程中发布者不需要直达订阅者是谁、长什么样,订阅者也不需要指导发布者是谁、长什么样。这就是发布/订阅模式的特点: 与观察者模式的区别两者最大的区别就是调度方式, 虽然两种模式都存在订阅者和发布者(具体观察者可认为是订阅者、具体目标可认为是发布者),但是观察者模式是由具体目标调度的,而发布/订阅模式是统一由调度中心调的,所以观察者模式的订阅者与发布者之间是存在依赖的,而发布/订阅模式则不会。 这里引用一篇关于二者区别的文章的内容,摘自这本书 The Observer pattern requires that the observer (or object) wishing to receive topic notifications must subscribe this interest to the object firing the event (the subject). The Publish/Subscribe pattern however uses a topic/event channel which sits between the objects wishing to receive notifications (subscribers) and the object firing the event (the publisher). This event system allows code to define application specific events which can pass custom arguments containing values needed by the subscriber. The idea here is to avoid dependencies between the subscriber and publisher. This differs from the Observer pattern as it allows any subscriber implementing an appropriate event handler to register for and receive topic notifications broadcast by the publisher. 这文章里面有一个描述发布/订阅模式的例子,可以参考一下。注意里面的publish、subscribe、unsubscribe方法的实习都没有写出来。 这个例子很好的体现出发布/订阅模式的优点:允许订阅者实现一个特定的适合于自己的事件处理函数123456789101112131415161718192021222324252627282930313233343536373839404142// A very simple new mail handler // A count of the number of messages receivedvar mailCounter = 0; // Initialize subscribers that will listen out for a topic// with the name "inbox/newMessage". // Render a preview of new messagesvar subscriber1 = subscribe( "inbox/newMessage", function( topic, data ) { // Log the topic for debugging purposes console.log( "A new message was received: ", topic ); // Use the data that was passed from our subject // to display a message preview to the user $( ".messageSender" ).html( data.sender ); $( ".messagePreview" ).html( data.body ); }); // Here's another subscriber using the same data to perform// a different task. // Update the counter displaying the number of new// messages received via the publisher var subscriber2 = subscribe( "inbox/newMessage", function( topic, data ) { $('.newMessageCounter').html( ++mailCounter ); }); publish( "inbox/newMessage", [{ sender: "[email protected]", body: "Hey there! How are you doing today?"}]); // We could then at a later point unsubscribe our subscribers// from receiving any new topic notifications as follows:// unsubscribe( subscriber1 );// unsubscribe( subscriber2 ); 参考 Learning JavaScript Design Patterns 设计模式(三):观察者模式与发布/订阅模式区别]]></content>
<tags>
<tag>Javascript</tag>
</tags>
</entry>
<entry>
<title><![CDATA[跨域解决方案汇总]]></title>
<url>%2Fposts%2Ff489ed5e%2F</url>
<content type="text"><![CDATA[什么是跨域首先我们要知道所谓不同域就是协议、域名、端口这三者其中一个不一样。比如说: http://www.a.com 与 https://www.a.com 不同域,因为协议不同 http://m.a.com 与 http://www.a.com 不同域,因为域名不同(子域名不同) http://www.a.com 与 http://www. b.net 不同域,因为域名不同(主域名不同) http://www.a.com:80 与 http://www.a.com:443 不同域,因为端口不同 http://www.a.com 与 http://www.a.com/login 同域,因为除了请求资源地址不同以外,其他都相同 而跨域自然就是指某域下请求另一个域的资源。Javascript为了安全,提出了同源策略,限制了跨域。同源策略最早是Netscape为了cookie而创建的规则,它要求某些操作只能在同域下进行。受到同源策略影响的是下面这三类: 获取cookie、localstorage、IndexDB。 DOM操作 Ajax请求 前两点很容易理解,如果可以跨域获取localstorage岂不是很容易就能盗取信息?如果能跨域操作DOM岂不是能插入广告或者获取密码框的值? 至于Ajax请求为什么不能跨域,我们可以想象一下这个场景:我的电脑能同时访问内网和网外,有一天我点开了一个钓鱼网站,如果没有同源策略的话钓鱼网站就能发起一个跨域Ajax请求,通过我获取到内网的信息。因此我们需要限制跨域Ajax请求。 解决跨域问题的方法在实际开发中我们经常需要发起一些跨域请求,这就需要我们想办法规避同源策略的限制。解决跨域问题的方法有很多,我这里介绍最常见的几种方法。 jsonp跨域jsonp利用了<script>标签可以跨域的特点,我们可以在a域下请求一个b域的脚本文件,而这个脚本文件里面就带上了我们需要的信息。12345678// a域下的脚本window.cb1 = function(res){ console.log(res);}var script = document.createElement("script");script.src = "http://www.b.com/json?callback=cb1";document.body.appendChild(script); 12// 请求回来的脚本内容cb1({val:"hello world"}); // 也就是运行这个callback 通过这个例子可以看出,jsonp最大的缺点就是不支持post请求。 另外,jQuery等都能很方便地实现jsonp,大家可以查阅一下相关api。 跨域资源共享(CROS)这个方法非常简单,在不涉及cookie的情况下只需要服务端设置一个Access-Control-Allow-Origin的响应头。如果需要带上cookie,则前端需要设置xhr.withCredentials = true;。CROS跨域与jsonp相比,他可以发起get和post请求,在信息量大的时候尤其有优势。目前这个方法已经得到主流现代浏览器的支持,没有意外的话会成为将来的主流。 关于Access-Control-Allow-Origin需要补充两点: 他的参数只能是*或者是指定的域名, 有时候我们需要接收主域名为a.com的所有域名的请求,类似实现*.baidu.com这种。我们可以通过后台判断请求头的origin,判断之后动态的往Access-Control-Allow-Origin里写入这个域名。而无需手动将所有域名一个个添加进Access-Control-Allow-Origin。 location.hash + iframe主要是利用了两个特性: 父窗口和子窗口可以相互操作对方的location.hash hash的变化不会引起页面重新加载 这个方法的过程大概是: 让子窗口在b域下,父窗口在a域下 父窗口修改子窗口的hash值,通过hash值将请求信息传给子窗口 子窗口坚挺到hash变化之后,在b域发起请求 子窗口拿到返回之后,修改父窗口的hash值,通过hash值将返回信息传回给父窗口 父窗口监听到hash变化之后获取hash携带的返回信息并作出相应 下面简单展示一个例子,为了简单起见省掉了父窗口修改子窗口的hash值这一步,直接在加载iframe的时候就发起请求。 12345678// 父窗口,在http://www.a.com下var iframe = document.createElement('iframe')iframe.src = 'http://www.b.com/ifram.html'document.body.appendChild(iframe)window.onhashchange = function () { console.log(location.hash)} 12345678910// 子窗口,在http://www.b.com下var xhr = new XMLHttpRequest();xhr.onreadystatechange = function(){ if(xhr.readyState === 4 && xhr.status === 200){ var res = JSON.parse(xhr.responseText); window.parent. parent.location.href = `http://www.a.com#msg=${res.msg}` }}xhr.open('GET', 'http://www.b.com/json', true)xhr.send(null); window.name + iframe这里利用了window.name的两个特性: 切换到不同页面不同域后,window.name依然存在 可以给window.name设置很大的值,(2MB) 利用这两个特性我们可以实现跨域,步骤如下: 先将子窗口切换到b域, 子窗口在b域请求之后把数据写在window.name中,再切换回a域。 a域下的父窗口通过.contentWindow.name获取数据 123456789// 父窗口,http://www.a.com/var iframe = document.creatElement("iframe");iframe.src = "http://www.b.com/iframe.html";document.body.appendChild(iframe);var times = 0;iframe.onload = function(){ if(++times == 2) console.log(JSON.parse(iframe.contentWindow.name));} 12345678910// 子窗口var xhr = new XMLHttpRequest();xhr.onreadystatechange = function(){ if(xhr.readyState == 4 && xhr.status == 200){ window.name = xhr.responseText; location.href = "http://www.a.com/ifram.html";// 回到a域 }}xhr.open("GET","http://www.b.com/json",false);xhr.send(null); postMessagewindow.postMessage这是HTML5 XMLHttpRequest Level 2中的API,允许跨窗口通信,无论两个窗口是不是在同一个域。 他有三个参数: message,需要发送的数据,字符串类型。必选 targetOrigin,来指定哪些窗口能接收到消息事件,可以是*也可以是URL。必选 transfer,是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。可选 另外需要注意的是接收方必须要监听message事件,否则会报错。 12345678// 父窗口var iframe = document.createElement("iframe");iframe.url = "http://www.b.com/iframe.html";document.body.appendChild(iframe);window.addEventListener('message', function(e) { console.log(JSON.parse(e.data))}, false); 123456789// 子窗口var xhr = new XMLHttpRequest();xhr.onreadystatechange = function(){ if(xhr.readyState == 4 && xhr.status == 200){ parent.postMessage(xhr.responseText, '*') }}xhr.open("GET","http://www.b.com/json",false);xhr.send(null); 其他除了上面提到过的五种跨域方法以外,还有一些其他方法: nginx代理跨域 Nodejs中间件代理跨域 WebSocket协议跨域 这些方法涉及了Nginx、NodeJs等知识,我暂时还搞不定,就不赘述了。 先挖个坑,日后再补。— 2018.2.9 Reference AJAX POST&跨域 解决方案 - CORS 前端常见跨域解决方案(全) 跨域问题汇总 浏览器同源政策及其规避方法]]></content>
<tags>
<tag>Javscript</tag>
</tags>
</entry>
<entry>
<title><![CDATA[前端自动化测试入门(should.js+Mocha+Karma+Travis-CI)]]></title>
<url>%2Fposts%2Fd4b1664a%2F</url>
<content type="text"><![CDATA[测试驱动开发能『迫使』我们在开发前设计好api再实现细节,开发过程中尽量做到单元解耦(否则单元不可测),也便于我们日后进行重构。在前端开发中我们一般在核心库和中间件的开发中使用单元测试,写ui界面时则一般只会『做个类似按键精灵的东西,自动点点点』。 在这里我会简单介绍一下怎么使用should.js+Mocha+Karma+Travis.CI做前端自动化测试。 他们是什么?首先来解释一下提到过的那些工具都是些什么。 should.js既然是要做测试,那么显然我们需要判断一下输出结果是否符合我们预期,如果符合就是测试通过。而断言就是帮助我们完成这个判断的。 在程序设计中,断言(assertion)是一种放在程序中的一阶逻辑(如一个结果为真或是假的逻辑判断式),目的是为了标示与验证程序开发者预期的结果-当程序运行到断言的位置时,对应的断言应该为真。若断言不为真时,程序会中止运行,并给出错误消息。–维基百科 在Node.js中内置有一个assert模块用于断言。然而他提供的函数有限,有时候并不能满足需求。 在这里我们使用的是一个叫should.js的第三方断言库。他的API 非常语义,缺点是文档太烂。 MochaMocha(“摩卡”)是一个Javascript测试框架,而所谓测试框架其实就是用来运行一些符合框架要求的测试脚本并给出测试结果以达到测试的目的,我们的测试用例就写在那些测试脚本中。除了Mocha以外,Jasmine(“茉莉花”)也是一个很流行的测试框架。 KarmaKarma是一个Javascript的测试运行器(Test Runner)。 在一个TDD(测试驱动开发)的过程中,我们经常需要做的,也是最关心的就是编写测试用例、编写实现、运行测试。然而在实际开发中,我们很多时候还要经常做一些类似手动刷新页面,手动引入相应文件等等繁琐的工作。而Karma则能帮组我们从这些繁琐工作中解放出来,我们需要关注的只是编写测试用例和编写实现等核心工作,karma会检测文件的变化,自动运行测试。 Travis CI所谓CI(Continuous Integration,简称 CI)就是持续集成的意思。按我的理解,持续集成就是每当项目代码有变更(例如有项目成员push了一份代码上来),就会自动构建、测试并反馈测试结果。而不需要等到代码长得很大的时候再测试,那时候如果出问题也许就只能推倒重来了。 而Travis CI就是提供这样持续集成服务的一个工具。你用github登陆他的网站,选择一个需要服务的仓库(里面需要有相关的配置文件)。那么以后每一次有代码push到这个仓库的时候Travis CI就会自动构建、测试、反馈结果。 怎么用?下面我会通过一个例子来讲一下简单的基于should.js+Mocha+Karma+Travis-CI的测试流程是怎么样的。当然这里并不能将这几个工具讲解得很透彻,更多用法和配置还请到官网查阅。 准备首先当然是要安装node.js,然后编写package.json等文件。这里我们先不管Karma以及Travis-CI,只在依赖中写上should以及mocha。123456789101112131415// package.json{ "name": "testExample", "version": "0.0.1", "description": "an example of testing", "scripts": { "test": "mocha" }, "author": "weibinzhu", "license": "MIT", "devDependencies": { "should": "^11.2.1", "mocha": "^3.5.0" }} 我们约定目录结构如下:12345678└── testExample ├── Readme.md ├── package.json ├── .gitignore └── lib └── add.js └── test └── add.test.js lib 用来存放我们的待测试的代码,test文件夹内存放测试代码,add.test.js是我们的测试脚本,一般与要测试的脚本同名,带上.test后缀。 接下来npm i一下,安装好依赖之后就可以开始了! 编写测试代码首先我们设计一下api以及编写测试脚本了。 这里我们以实现一个大数相加函数add(a,b)为例子。我们让两个参数a和b都是数字组成的字符串,返回一个由a和b相加结果构成的字符串。例如add('1','1')==='2'。 根据这样的api,于是我们写出这样的测试脚本:123456789101112// add.test.jsrequire('should');// var add = require('../lib/add');describe('大数相加测试',function(){ it('"12"加上"22"等于"34"',function(){ add("12","22").should.equal("34"); }); it('"843529812342341234"加上"236124361425345435"等于"1079654173767686669"',function(){ add("843529812342341234","236124361425345435").should.equal("1079654173767686669"); });}) 我来解释一下: 由于我们这里使用了should.js这个第三方断言库,我们需要先引入should:require('should'); 留意脚本里面有一个describe和两个it。一个测试脚本里面应该有一个或更多的describe块,并且每个describe块中应该有一个或更多的it快。 describe叫做测试套件(test suite),表示这是一组测试,第一个参数是这组的名字,第二个参数是一个待执行的函数,在测试的时候会执行这个函数。 it叫做测试用例(test case,表示一个测试,第一个参数是这个测试的名字,第二个参数是一个待执行的函数,当进行到这个测试用例的时候就会执行这个函数。如果这个函数内部抛出了错误,那么代表这个测试用例测试失败。 类似add("12","22").should.equal("34");这样的代码就是一个断言,如果add("12","22")的返回值不等于"34",那么久会抛出一个错误,这个测试用例自然就不会通过。可见should.js的api十分语义,一看就知道是什么意思。更多api请到官网查阅,这里就不赘述了。 现在我们可以回到命令行,输入$ npm test或者$ mocha看一下结果。mocha会自动运行test目录里面的测试脚本,但不会运行里面子目录中的测试脚本。或者你也可以指定运行哪一个测试脚本:$ mocha add.test.js 结果如下:12345678910111213141516171819> mocha 大数相加测试 1) "12"加上"22"等于"34" 2) "843529812342341234"加上"236124361425345435"等于"1079654173767686669" 0 passing (14ms) 2 failing 1) 大数相加测试 "12"加上"22"等于"34": ReferenceError: add is not defined at Context.<anonymous> (test\add.test.js:6:3) 2) 大数相加测试 "843529812342341234"加上"236124361425345435"等于"1079654173767686669": ReferenceError: add is not defined at Context.<anonymous> (test\add.test.js:9:3)npm ERR! Test failed. See above for more details. 显然是不通过的,我们add都没写呢 编写待测试的脚本现在让我们把这个add函数实现了,我们把代码写在lib/add.js里面,然后再add.test.js中引入:var add = require('../lib/add');。 add函数的代码如下,具体为什么这么写可以看看我这篇博客 123456789101112131415161718192021/** * 大数相加 * @param {[String]} a 操作数 * @param {[String]} b 操作数 */function add(a,b){ var res = "", c = 0; a = a.split(""); b = b.split(""); while(a.length>0||b.length>0||c){ c = c + ~~a.pop() + ~~b.pop(); res = c%10 + res; c = c > 9; } return res.replace(/^0+/,'');}module.exports = add; 现在再运行一遍测试应该就通过了,通过的样子长这样:1234567> mocha 大数相加测试 √ "12"加上"22"等于"34" √ "843529812342341234"加上"236124361425345435"等于"1079654173767686669" 2 passing (14ms) 加上Karma现在让我们在dependencies里面加上"karma": "^1.7.0",,然后再执行一次npm install。待安装完之后,我们可以在命令行输入$ karma init,karma会问我们一系列的问题。我们按提示操作即可。具体到本例子,我是这样回答那些问题的:12345678910111213141. Which testing framework do you want to use ? (mocha)2. Do you want to use Require.js ?(no)3. Do you want to capture any browsers automatically ?(Chrome)4. What is the location of your source and test files ? (lib/add.js)(node_modules/should/should.js)(test/add.test.js)5. Should any of the files included by the previous patterns be excluded ? ()6. Do you want Karma to watch all the files and run the tests on change ?(yes) 回答完之后karma就会自动在根目录创建一个karma.config.js文件。没错,其实我们可以不使用$ karma init,自己在根目录创建一个karma.config.js文件也是可以的。 注意到第四个问题了吗?此时我们已经让karma跟踪上所需的文件了,此时我们可以放心的去掉add.js里面的module.exports以及add.test.js里面的require('should');var add = require('../lib/add');。 现在让我们启动一下Karma吧,在命令行输入$ karma start。一切顺利的话你会看到chrome自动打开,命令行里显示着这个:12345602 02 2018 22:14:09.880:WARN [karma]: No captured browser, open http://localhost:9876/02 02 2018 22:14:09.896:INFO [karma]: Karma v1.7.1 server started at http://0.0.0.0:9876/02 02 2018 22:14:09.896:INFO [launcher]: Launching browser Chrome with unlimited concurrency02 02 2018 22:14:09.934:INFO [launcher]: Starting browser Chrome02 02 2018 22:14:11.912:INFO [Chrome 64.0.3282 (Windows 10.0.0)]: Connected on socket Lx8YZORCy1yfAyFAAAAA with id 8475447Chrome 64.0.3282 (Windows 10.0.0): Executed 2 of 2 SUCCESS (0.016 secs / 0.003 secs) 一次通过! 此时如果我们修改一下被跟踪的代码,例如在add.test.js里面将add("12","22").should.equal("34");改成add("12","22").should.equal("4");,保存之后再回到命令行,我们会看到这个:123456Chrome 64.0.3282 (Windows 10.0.0) 大数相加测试 "12"加上"22"等于"34" FAILED AssertionError: expected '34' to be '4' at Assertion.fail (node_modules/should/should.js:1638:17) at Assertion.value (node_modules/should/should.js:1715:19) at Context.<anonymous> (test/add.test.js:6:25)Chrome 64.0.3282 (Windows 10.0.0): Executed 1 of 2 (1 FAILED) (0 secs / 0.002 seChrome 64.0.3282 (Windows 10.0.0): Executed 2 of 2 (1 FAILED) (0 secs / 0.002 seChrome 64.0.3282 (Windows 10.0.0): Executed 2 of 2 (1 FAILED) (0.014 secs / 0.002 secs) 测试失败了!显然Karma检测到文件有改动后自动又运行了一次,当我们把代码改回来之后karma又会执行一次。这就是karma的魅力所在。 接入Travis CITravis CI是配合github使用的,因此我们首先需要将刚才的代码放上github。然后进入Travis CI的官网(可能需要科学上网)并用你的github账户登录。接着根据官网上的提示操作,也就是这个: 先选择需要接入的仓库(也就是我们刚上传的那个)然后在根目录添加.travis.yml文件。完成之后每当我们像github push一次代码,Travis CI就会默默地帮我们测试并在他的网站上给出结果。 显然这里的重点就是.travis.yml这个配置文件。我们可以在这个文件里面设置很多东西,不过这里只会用最简单的情况来介绍一下。更多设置还请到官网慢慢查看。 12345678language: node_jsnode_js: - "lts/*"script: node_modules/karma/bin/karma start karma.conf.js --single-run before_install: - export CHROME_BIN=chromium-browser - export DISPLAY=:99.0 - sh -e /etc/init.d/xvfb start 解释一下: language自然是用来告诉travis我们是node.js node_js是指明我们的node版本,这里是所有lts版本 script就是知名需要执行的脚本,这里就是执行karma。一般karma是会一直执行的,显然在travis中我们不需要这样,因此加上–single-run 修饰 before_install,这里涉及了一个浏览器的坑,下面详细说说。 我们知道karma会自动打开一个chrome(或者其他指定浏览器),因此我们需要告诉travis我们需要打开浏览器,并且由于travis默认不是用chrome的,我们还需要针对chrome分别在 .travis.yml和karma.config.js做一些修改。 .travis.yml的修改已经在上面展示过了,下面说一下karma.config.js的改动。 我们需要在配置里面加上这个:1234567// karma.config.jscustomLaunchers: { Chrome_travis_ci: { base: 'Chrome', flags: ['--no-sandbox'] }}, 其次为了让我们的配置文件既适用于travis也适用于本地,我们需要对环境变量做个判断:1234567891011// karma.config.jsmodule.exports = function(config) { var configuration = { // 原来的各种配置 } if(process.env.TRAVIS){ // 做一个判断 configuration.browsers = ['Chrome_travis_ci']; } config.set(configuration)} 修改完成之后将代码push到github,此时回到travis CI的网站,我们应该会看到travis正在测试我们的代码(可能需要等几分钟travis才会响应到我们刚才的push)。 测试完成之后,如果最下面出现Done. Your build exited with 0.并且仓库名字隔壁的图标变成这个:,证明测试通过了。 最后说几句这只是最最基本的一个入门,显然这里面还有很多东西需要深究。除了官网外我还有几个链接分享给大家: 这是travis官网上有关浏览器的文章 阮一峰大大关于mocha的教程 另一篇关于mocha教程 本文用到的那个例子我放在了这个仓库中]]></content>
<tags>
<tag>Javascript</tag>
<tag>测试</tag>
</tags>
</entry>
<entry>
<title><![CDATA[谈谈JS中的大数运算]]></title>
<url>%2Fposts%2F8541ccde%2F</url>
<content type="text"><![CDATA[什么是大数运算大数运算顾名思义就是一些很大的数之间的运算。之所以要用大数运算算法处理原因有二: 我们知道计算机能表现的数字范围是有限的,超过这个范围会出现溢出。 在js中只有一定范围内的数才能精确到个位,超过这个范围的数是不准确的。有关js中的数字可以参考下图: 算法在这里我以加法为例,介绍三个大数运算的方法。 方法一:常规方法思路就是运用加法的原理,一位一位的加12345678910111213141516171819202122232425function add(a,b) { // 初始化,把两个数字转为数组 // 这里将数组反过来是因为两个数字长短不一,反过来有助于处理边界问题 if (a.length>=b.length) { var lgArr = a.split("").reverse(), smArr = b.split("").reverse(); }else{ var lgArr = b.split("").reverse(), smArr = a.split("").reverse(); } var lgLength = lgArr.length, res = ''; for(var i=0; i<lgLength; i++){ lgArr[i] = +lgArr[i] + +smArr[i];// 使用一元加来将字符串转成数字 if(lgArr[i]>10){// 如果大于10代表要进位 lgArr[i] -= 10; if(i==lgLength-1){ lgArr[i+1] = 1; }else{ lgArr[i+1] = +lgArr[i+1] + 1; } } } res = lgArr.reverse().join(""); return res;} 方法二:利用js的奇淫技巧JS中有很多奇淫技巧,合理运用可以让代码看起来更简洁123456789101112131415function add1(a,b){ // ~取反操作符,~0 === -1,~~0 === 0,~~null === 0,~~'0' === 0 // 用+也能实现字符转成数字,不过~~能处理null的情况 // c>0会返回一个布尔值,布尔值转换成数字的时候true为1,false为0 var res = '', c = 0; a = a.split(''); b = b.split(''); while(a.length||b.length||c){ c = c + ~~a.pop() + ~~b.pop(); res = c % 10 + res; c = c > 9; } return res.replace(/^0+/,'');// 去掉前面可能存在的0} 方法三:快速算法前两种一位一位的加太慢了,我们可以充分利用js的精度,14位14位的算。1234567891011121314151617181920function add2(a,b){ // 主要思想是分开一段段加,这样既能保证精度且不溢出,也能提高速度 // 使用到了substr这个方法,String.substr(idx,length)接收两个参数,第一个参数是索引,第二个参数是长度,第二个参数可省略 // 如果索引超范围,或者长度小于1那么返回空字符串 var res = []; while(a.length||b.length){ res.push(parseInt(a.substr(-14)||0,10)+parseInt(b.substr(-14)||0,10)); a = a.substr(0,a.length-14); b = b.substr(0,b.length-14); } var c = 0, o = ''; console.log(res) while(res.length){ let temp = (res.shift() + c).toString(); o = temp.substr(-14) + o; c = parseInt(temp.substr(0,temp.length-14) || 0,10); } return o;} 实际上只用一个循环就可以了,上面只是方便理解。只用一个循环的话就是这样:1234567891011function add3(a,b){ var res = '', c = 0; while(a.length||b.length){ let temp = (parseInt(a.substr(-14)||0,10)+parseInt(b.substr(-14)||0,10) + c).toString(); res = temp.substr(-14) + res; c = parseInt(temp.substr(0,temp.length-14,10)||0,10); a = a.substr(0,a.length-14); b = b.substr(0,b.length-14); } return res;}]]></content>
<tags>
<tag>Javascript</tag>
</tags>
</entry>
<entry>
<title><![CDATA[谈谈Javascript中的void]]></title>
<url>%2Fposts%2Fdddf7511%2F</url>
<content type="text"><![CDATA[简单版void是一个一元操作符,根据MDN的说法 The void operator evaluates the given expression and then returns undefined. void操作符会对给定表达式求值,然后返回undefined 也就是说无论表达式是什么,void都会返回一个undefined:12345678void 'a' // undefinedvoid 0 // undefined。 同void(0)void false // undefinedvoid new Object() // undefinedvoid Number() // // undefinedvoid 1 + 1 // NaN,因为void 1返回undefinedvoid function re(){console.log('sa')}() // 打印'sa',返回undefined. . . 那么有什么用呢? 第一个用途,用来代替undefined与很多人以为的不一样,undefined实际上并不是js的保留字,尽管ES5中undefined已经变成了全局对象的一个只读属性,但在局部作用域中仍然可以被重写。 这就带来很多隐患。 为了解决这个问题我们可以使用void无论怎么样都返回undefined的特性,在需要undefined的时候使用void 0代替。 至于为什么用void 0而不用void(0),void 'hello,world!',主要是因为void 0短。这意味着尽管未来undefined会变成保留字,void 0也仍然会有用武之地,事实上很多压缩工具就是直接用void 0代替代码中的undefined。 第二个用途,用在Javascript URIs在浏览器处理javascript: URI时,他会对URL那的代码求值然后用返回值替换页面上的内容,除非返回的是undefined。而void就可以帮我们返回undefined。1234567<a href="javascript:void(0);"> Click here to do nothing</a><a href="javascript:void(document.body.style.backgroundColor='green');"> Click here for green background</a> 不过现在javascript:已经不被推荐,虽然我并不知道为何不推荐= = 第三个用途,避免立即执行函数出错通常我们写立即执行函数的时候都会加括号,例如这样:123(function add(){ // do something })() 不过有的人为了省力气往往省略这个括号,省略本来并没有问题,不过这有时候会被js解释器错误的认为这是在函数声明。 通过在function前面加上void可以避免这种情况。 123void function add(){ // do something}() 事实上不只可以加void,也可以用!,+等一元操作符替代。 复杂版其实void在ECMAScript中的解释是这样的: Let expr be the result of evaluatingUnaryExpression. Call GetValue(expr). Return undefined. 由于里面涉及了一些比较深入的js知识,我现在并不太清楚究竟做了什么。待日后更新。 Reference:void关键字有个毛用 从用 void 0 代替 undefined 说起 谈谈Javascript中的void操作符 void operator–MDN]]></content>
<tags>
<tag>Javascript</tag>
</tags>
</entry>
<entry>
<title><![CDATA[几种方法实现自适应的有文字分割线]]></title>
<url>%2Fposts%2F6004dfff%2F</url>
<content type="text"><![CDATA[在写页面的时候我们经常需要写一些带文字的分割线。我们需要横线垂直居中,文字水平居中,并且横线的长度能根据文字长度自适应。类似这样:为此我参考了一些插件的做法,加上自己的一些思考,总结出以下三种写法: 使用table-cell以及背景图片(vux以及Cola-UI的写法)这个方法主要应用了table-cell的自适应特性,再配合使用background-position将北京图片居中。为了减少http请求,此处的图片采用base64。 123<div class="divider"> 自适应小标题</div> 12345678910111213141516171819202122.divider{ text-align: center; display: table; white-space: nowrap;/*不然文字会被挤成一行一个字*/ height: 50px; line-height: 50px; font-size: 16px; color:#353535;}.divider:before,.divider:after{ content: ''; display: table-cell; width: 50%; /*设置50%是为了确保能自适应文字宽度,最小的时候也能占满一行,如果有文字就会挤开*/ background-repeat: no-repeat; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABaAAAAACCAYAAACuTHuKAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyFpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDE0IDc5LjE1MTQ4MSwgMjAxMy8wMy8xMy0xMjowOToxNSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo1OThBRDY4OUNDMTYxMUU0OUE3NUVGOEJDMzMzMjE2NyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo1OThBRDY4QUNDMTYxMUU0OUE3NUVGOEJDMzMzMjE2NyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjU5OEFENjg3Q0MxNjExRTQ5QTc1RUY4QkMzMzMyMTY3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjU5OEFENjg4Q0MxNjExRTQ5QTc1RUY4QkMzMzMyMTY3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+VU513gAAADVJREFUeNrs0DENACAQBDBIWLGBJQby/mUcJn5sJXQmOQMAAAAAAJqt+2prAAAAAACg2xdgANk6BEVuJgyMAAAAAElFTkSuQmCC);}.divider:before{ background-position: right 1em top 50%;}.divider:after{ background-position: left 1em top 50%;} 使用flex-box这个方法主要是利用了flex-box中自动撑满空间的特性,以及align-items:center。优点是代码简洁优雅,缺点是兼容性稍差。 12345<div class="title"> <div class="line"></div> <div class="text">自适应标题</div> <div class="line"></div></div> 123456789101112131415.title{ display: flex; flex-direction: row; align-items: center; /*垂直居中*/ width:100%;}.line{ flex:1; /*自动撑满剩余空间*/ border-top: 1px solid #353535; /*使用border制作横线*/}.text{ font-size: 16px; margin:0 10px; color:#353535;} 利用table-cell的自动撑满特性以及vertical-align特性这个是我自己根据前两个方案总结出来的。其实实现这种分割线的要点在于实现自动撑满空间,以及垂直居中。恰好table-cell都能做到这两点。 我提出的这个方法缺点是html结构有点复杂。 123456789<div class="divider"> <div class="line-container"> <div class="line"></div> </div> <div class="text">自适应小标题</div> <div class="line-container"> <div class="line"></div> </div></div> 123456789101112131415161718192021.divider{ display: table; font-size: 16px; color: #353535; height: 30px; line-height: 30px; width: 100%;}.line-container{ display: table-cell; width: 50%; vertical-align: middle; /*让table-cell内部的元素垂直居中*/ padding: 0 10px;}.line{ width: 100%; /*内部元素的长度撑满父元素即可*/ border-top: 1px solid #353535;/*跟之前一样用border实现横线*/ }.text{ white-space: nowrap;/*跟之前一样避免文字被挤成一个字一行*/} 以上就是我收集、提出的三种实现自适应文字分割线的方法,欢迎大家提出修改意见:)]]></content>
<tags>
<tag>CSS</tag>
</tags>
</entry>
<entry>
<title><![CDATA[30分钟入门正则表达式]]></title>
<url>%2Fposts%2F64378108%2F</url>
<content type="text"><![CDATA[正则表达式既简单又博大精深。半个小时肯定是不能精通,不过入门应该是够了,接下来赶紧把它用到实际编程中,熟能生巧,多用就是走向精通的捷径。 什么是正则表达式?简单来说就是按照某种规则去匹配符合条件的字符串,正则表达式描述的就是这个规则。 相信大家都用过编辑器(如sublime)里的查找功能。你在搜索框输入几个字符,编辑器就会帮你找到含有这些字符的字符串。这里你输入那几个字符其实就是一种规则,编辑器找到的就是符合这个规则的字符串。只不过这样的查找规则过于简单,很多时候不能找出我们想要的东西。正则表达式的出现正是用来解决这个痛点的。通过他我们能够描述出精确得多的规则,也就更容易找到我们想要的东西。 正则表达式就像JSON一样,并不是某个语言独有的。 大部分编程语言都支持正则表达式,本文重点讲讲Javascript中的正则表达式。 在线可视化工具正则表达式的规则虽然简单,不过一长串字符还是很容易让人看晕的,debug也不方便。这里我推荐一个在线的可视化工具,他能够很形象地描述正则表达式。下面我简单说一下怎么看它给出的结果。 我随便写了一个正则表达式\b\d{3}.?\w+\d*\b。什么意思先不用管,我们直接看结果。 第一个word boundary就表示说这是一个单词的边界,digit就表示数字,这些都用英文写的很清楚了我就不细说了(也说不完)。重点看后面那些循环和分支。第一个digit那里可以看到有一个圈,他代表循环,2times表明循环两次,加上原本那个digit就是有三个digit。明显这里就是三个digit的简写。后面any character上面有一条空的分支,表明这里可以有一个任意字符,也可以没有。再后面一个digit有一个空分支,也有一个表示循环的圈,圈上没有写明次数,这里的意思是可以有一个digit,可以有多个(无上限),也可以一个也没有。 有没有觉得很形象?没有这感觉的话可能是因为你还没学正则的那些规则,没关系,学了规则之后再回来看,到时候你会发现这正是学习和debug的利器。 RegExp对象JS通过内置的RegExp对象支持正则表达式,跟其他JS对象一样,实例化RegExp对象也有两种方法: 字面量:var reg = /\bis\b/g;(g是修饰符,表示进行全文搜索匹配) 构造函数:var reg = new RegExp ('\\bis\\b' , 'g' );(字符串中的\是有含义的字符,所以我们需要转义)(其他在后面被介绍的,有特殊含义的字符,当我们需要匹配他们的时候也需要用\转义) 元字符正则表达式由两种基本字符类型组成: 原义文本字符,例如’a’就是指字母’a’ 元字符,类似我们之前用过的\b。下面列举了一些元字符 字符 含义 \t 水平制表符 \v 垂直制表符 \n 换行 \r 回车 \0 空字符 \f 换页 \cX 与X对应的控制字符(ctrl+X) 还有一些具有特殊含义的标点符号也是元字符:* + ? $ ^ . | \ ( ) { } [ ]。 元字符当然不止这些,下面我们将结合他们的用法慢慢介绍。 字符类一般情况下正则表达式的一个字符对应字符串的一个字符,例如ab\t的含义就会一个字母a一个字母b再加一个水平制表符 有时候我们不是想匹配某个字符而是某一类字符,例如不只是a,而是a,b,c都行。这种情况下我们可以使用元字符[]来构建一个简单的类。所谓类是指符合某些特性的对象,一个泛指,而不是特指某个字符。例如:表达式[abc]把字符a或b或c归为一类,意思是’a’,’b’,’c’都行。 取反有时候我们不是要匹配某些字符而是躲开某些字符。这时候我们可以用元字符^创建反向类。意思是不属于某类的内容,如[^abc]表示不是字符a或b或c的内容 范围类有时候我们需要匹配的那一类字符太多,比如说我们想匹配一个任意小写字母,傻乎乎地写上[abcdefg......]既麻烦又不简洁。这时候我们可以使用范围内。就像这样:[a-z],这表明我们要匹配任意一个从a到z的字母。 有几点我们要注意的。 这是一个双闭区间,包含了a和z。 类的内部是可以连写的,例如可以这样:[a-zA-Z],意思是匹配任意一个a到z或者A到Z的字母。 两个字符之间的短横线会被认为是在表示范围,如果我们就是想匹配短横线,我们可以把它加在后面,例如这样:[a-z-],意思是任意一个a到z的字母或者短横线。 预定义类为了进一步简化我们的正则表达式,我们可以使用下面这些预定义类。 字符 等价类 含义 . [^\r\n] 除了回车符和换行符以外的所有字符 \d [0-9] 数字字符 \D [^0-9] 非数字字符 \s [\t\n\x)b\f\r] 任何空白字符,包括空格、制表符、换页符等等 \S [^\t\n\x)b\f\r] 非空白符 \w [a-zA-z_0-9] 包括下划线的任何单词字符 \W [^a-zA-z_0-9] 非单词字符 修饰符前面的例子用到一个修饰符g,除了g以外还有其他修饰符,他们的意思如下: 修饰符 含义 g 全局搜索,而不是搜索到第一个就停止 i 忽略大小写 m 多行搜索 (把换行符后的当新的一行) 使用这些修饰符的话就像上面说过的例子一样,如果是通过字面量实例化,就在最后的/后面加上修饰符,如果是通过构造函数,就作为第二个参数传递给构造函数。 定位符定位符能够使你的正则表达式匹配特定位置的字符。 字符 含义 ^ 匹配输入字符串的开始位置。如果设置了 RegExp 对象的 Multiline 属性,^ 也匹配 ‘\n’ 或 ‘\r’ 之后的位置。(不是在中括号中就是边界的意思) $ 匹配输入字符串的结束位置。如果设置了RegExp 对象的 Multiline 属性,$ 也匹配 ‘\n’ 或 ‘\r’ 之前的位置。 \b 单词边界。例如,’er\b’ 可以匹配”never” 中的 ‘er’,但不能匹配 “verb” 中的 ‘er’。 \B 非单词边界。与上面相反,’er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。 举个例子,有输入字符串"12A1A",我们只想匹配开头的数字,不想匹配中间的。这时候我们可以用这样的正则表达式:^\d。 注意: 在用^匹配开头的时候应将^放在表达式开头,如^\d,而在用$匹配结尾的时候则需要放在表达式后面。 量词有时候我们想匹配多个相同的字符,例如匹配3个数字。我们当然不会这么写:\d\d\d,这时候我们就用到了量词。 字符 含义 ? 出现零次或一次(最多出现一次) + 出现一次或多次(至少出现一次) * 出现零次或多次(任意次) {n} 出现n次 {n,m} 出现n到m次 {n,} 至少出现n次 贪婪模式与非贪婪模式大家有没有想过,如果我们在用上面提到的量词时给出的量词时一个范围,例如\d{3,6}时,究竟会匹配到3到6次的哪一个呢? 其实在这种情况下,正则表达式选择次数的时候会有两种模式: 贪婪模式 这是默认模式。 尽可能多的匹配。例如12345678.replace(\d{3-6},'X')的结果为X78。虽然3,4,5次都符合,不过最终匹配的是6次。 非贪婪模式 让正则表达式尽可能少的匹配,也就是说一旦匹配成功不再继续尝试。 只需要在量词后加上?。 同样举上面的例子12345678.replace(\d{3-6}?,'X')此时的结果为XX78。123和456都被匹配到了。 分组与反向引用首先思考一个例子,假如我们想把形如2017-8-3这样的日期修改成8/3/2017这样的日期,那么需要怎么做呢?本节讲到的分组与反向引用可以帮组你。 使用( ) 可以达到分组的功能,使量词作用于分组。例如(hello){3}表示hello重复三遍。 使用|,可以达到或的效果。他会把表达式分成前后两部分,两部分任意一个匹配成功都行。有时候我们并不想把整个表达式分成两部分,只想其中一段分成两部分。这时候我们可以利用分组。WEf(on|ca)serr 意思是中间是on或者ca都行。 反向引用。使用 $ 能够捕获分组的内容(类似变量的概念)。$1表示的就是第一个分组,以此类推可以表示第n个分组。举个例子:12'2016-2-14'.replace(/(\d{4})-(\d{2})-(\d{2})/g, '$2/$3/$1')// 结果被替换为2/14/2016 忽略分组。有时候我们不想捕获某些分组,只需要在这个分组内加上?:就可以了。例如(?:hello).(world)这样hello这个分组就不会被捕获,此时world算第一个分组。 前瞻与后顾首先说明一下什么叫『前』什么『后』。 正则表达式从文本头部向尾部开始解析,因此文本尾部的方向就叫做『前』。 前瞻就是正则表达式在匹配到规则时,向前检查是否符合断言。后顾就是把反向换过来。 这里所说的断言其实用来限定匹配字符但又不属于匹配字符的东西。举个例子,我们要匹配一个跟在一个字母后面的数字,这里这个『跟在一个字母后面』就是断言。 JS不支持后顾符合和不符合断言称为肯定(正向)匹配和否定(负向)匹配。 名称 正则 备注 正向前瞻 exp(?=assert) exp表示规则,assert表示断言。后同 负向前瞻 exp(?!assert) 正向后顾 exp(?<=assert) JS不支持 负向后顾 exp(?<!assert) JS不支持 举个例子:12'a2*34vv'.replace(/\w(?=\d)/g, 'X')// 结果为X2*X4VV 表明只有a和3符合要求,注意正则表达式中,括号内的部分仅为断言,所以不会被替换。 JS正则表达式对象(RegExp)前面说过虽然很多编程语言都支持正则表达式,不过这里主要讲的是Javascript下的。因此我们在这节讲讲JS内建的RegExp对象。 对象属性 属性 类型 含义 默认值 global 布尔值 是否全文搜索 false ignoreCase 布尔值 是否忽略大小写 false multiline 布尔值 是否多好检索 false source 字符串 正则表达式的文本 lastIndex 整数 若匹配模式中含有g,这个属性储存整个字符串中下一次检索的开始位置 0 注意这些属性时只读的。 对象方法 RegExp.prototype.test(str) 测试一下在给定字符串中能否匹配到,能就返回true。 如果正则表达式有修饰符g,即全局搜索。那么每一次调用test方法,都会从上一次的lastIndex开始搜索,直到匹配不到返回false才从头开始。例如有字符串’12cd’,正则表达式是/\d/。第一次调用test方法返回true,lastIndex为1,第二次调用返回true,lastIndex为2,第三次调用返回false,lastIndex重置为0,第四次调用返回true,lastIndex又变回1。 鉴于有这样的特性,我们建议在使用这个方法时要回归他的本意,即测试某字符串能否匹配到你想要的东西,知道有没有就行了,不关心有多少个在哪里等信息(一定要这些信息的话就改用下面说到的方法)。 因此使用这个方法时不要给正则表达式加上修饰符g。 RegExp.prototype.exec(str) 使用正则表达式对字符串执行搜索,并将跟新全局RegExp对象的属性以反映匹配结果。 如果没有匹配的文本就返回null,否则返回一个结果数组。 index声明匹配文本的第一个字符的位置。 input存放被检索的字符串string。 非全局调用情况 代用非全局RegExp的exec()时,返回数组。 第一个元素是与正则表达式相匹配的文本。 第二个元素是与RegExpObject的第一个子表达式相匹配的文本(如果有)。 第三个元素是与RegExp对象的第二个子表达式相匹配的文本(如果有),以此类推。 看例子: 123456789101112131415161718192021var reg3 = /\d(\w)(\w)\d/;var reg4 = /\d(\w)(\w)\d/g; // 全局var ts = '$1az2bb3cy4dd5ee'; // 测试用字符串var ret3 = reg3.exec(ts);console.log(reg3.lastIndex + '\t' + ret.index + 't' + ret.toString());console.log(reg3.lastIndex + '\t' + ret.index + 't' + ret.toString()); // 执行两次// 结果为:// '0 1 1az2,a,z'// '0 1 1az2,a,z'// 两次结果一样while(ret4 = reg4.exec(ts)){ console.log(reg4.lastIndex + '\t' + ret.index + '\t' + ret.toString());}// 结果为:// '5 1 1az2,a,z'// '11 7 3cy4,c,y'// 两次结果不一样 字符串对象有关正则表达式的方法JS中的字符串对象其实也支持正则表达式,下面是这个对象有关正则表达式的一些方法。 String.prototype.search(reg) search()方法用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串。 返回第一个匹配结果的index,找不到返回-1. search()方法不执行全局匹配,会忽略修饰符g,并且总从字符串的开始进行检索 String.prototype.match(reg) match()方法将检索字符串,以找到一个或多个与regexp匹配的文本 regexp有无修饰符g对结果影响很大。 非全局调用 返回数组的第一个元素存放的是匹配文本,而其余的元素存放的是与正则表达式的子表达式匹配的文本 除了常规的数组元素以外,返回的数组还含有2个对象属性 index声明匹配文本的原始字符正字字符串中的位置 input声明对stringObject的引用 看一个例子1234567891011var reg3 = /\d(\w)\d/;var reg4 = /\d(\w)\d/g; // 全局var ts = '$1a2b3c4d5e'; // 测试用字符串var ret = ts.match(reg3);console.log(ret);console.log(ret.index + '\t' + reg3.lastIndex);// ["1a2", "a"]// "1 0"// 表明不会影响正则表达式的lastIndex属性 全局调用 如果regexp具有g,则match()方法将执行全局搜索,找到字符串中的所有匹配子字符串 如果没有找到,返回null 如果找到一个多多个,则返回一个数组 数组元素中存放的是字符串中所有的匹配子串,而且也没有index属性或者input属性 看一个例子12345678910var reg3 = /\d(\w)\d/;var reg4 = /\d(\w)\d/g; // 全局var ts = '$1a2b3c4d5e'; // 测试用字符串ret = ts.match(reg4);console.log(ret);console.log(ret.index + '\t' + reg4.lastIndex);// ["1a2", "3c4"]// "undefined 0" String.prototype.split(reg) 一般用法'a,b,c,d'.split(','); // ["a", "b", "c", "d"] 除此之外我们还可以传入正则表达式实现复杂点的分割:'a1b1c2d'.split(/\d/); // ["a", "b", "c", "d"] String.prototype.replace String.prototype.replace(str,replaceStr) String.prototype.replace(reg,replaceStr) String.prototype.replace(reg,function) function会在每次匹配替换时调用,有四个参数: 匹配字符串 正则表达式分组内容(如果没有分组则没有这个参数) 匹配项在字符串中的index 原字符串 Referencehttp://blog.guowenfh.com/2015/12/01/Regexp-basis/ http://www.runoob.com/regexp/regexp-tutorial.html http://www.jb51.net/tools/zhengze.html http://www.imooc.com/learn/706]]></content>
<tags>
<tag>正则表达式</tag>
</tags>
</entry>
<entry>
<title><![CDATA[利用rem进行移动端适配]]></title>
<url>%2Fposts%2Fe6ac1031%2F</url>
<content type="text"><![CDATA[之前面试的时候被问到移动端适配用什么单位,答不出来,遂搜集了一些资料总结成这篇文章。水平有限,虽然已经尽可能地保证不出错,但难免有这样或那样的漏洞,如果你发现任何问题,请给我留言。感恩。 什么是rem?一般来说我们听到更多的是em,而rem则是root em的意思。顾名思义,rem是指相对于根元素字体大小的单位,而这个根元素,一般指的就是html。例如,若html的font size为16px, 则1rem=16px。 兼容性目前PC端的Chrome(4+),Firefox(3.6+),Safari(5+),以及IE(11+)均支持rem,移动端的主流浏览器,包括UC,QQ浏览器,三星内置浏览器也都支持rem。可以说只要不用支持古老的浏览器,如IE8、初代iPhone的Safari,都可以放心地使用rem。 优点在说rem的优点之前我们先来回顾一下移动端适配的一般做法。 流式布局,宽度使用百分比定义,高度和文字大小用px固定。缺点是不同屏幕尺寸下显示效果会有差异,在大屏幕手机下甚至会因为元素宽度被过度拉伸导致显示效果不协调。因此仅适用于一些比较简单的页面。 固定宽度,给页面设置固定宽度,超出部分留白。很明显,这种粗暴的方法对大屏手机很不友好。 弹性盒子布局。 响应式布局,其实就是针对几个主流设置对应的CSS,再利用媒体查询(Media Queries)来确定需要显示的样式。当然这样要求我们写额外的样式,提高了工作量,同时由于不可能为所有屏幕尺寸写针对性的样式,因此只能在若干主流的屏幕尺寸下做到完美显示。 我们发现这些方法要么不能在不同设备上完美显示,要么就是过于复杂,而rem却能很好的兼顾简单和兼容性。 之前提到rem是相对于根元素字体大小的单位,如果根元素的font size是16px,那么1rem就是16px,如果根元素是37.5px,那么1rem就是37.5px。可见,如果页面元素都用rem来设置大小,那么我们只需要修改根元素的font size就能等比例地缩放整个页面。也就是说只要根据不同屏幕尺寸设置根元素font size,我们就能等比例适配所有屏幕,是不是很优雅? 用法说了那么多我们来看看究竟要怎么使用吧。先看一个例子。 Demo1<div class="btn">button</div> 123456789101112html{ font-size:40px;}.btn{ width: 2rem; height: 0.5rem; font-size: 0.5rem; line-height: 0.5rem; text-align:center; background-color:red; } 例子中div的单位都是rem,留意宽高,可以看到宽高分别是80px和10px,而这正分别等于 2*40px和0.5*40px。这表明宽高是通过根元素html的font-size计算出来的,此时1rem=40px。 此时我们把html的font-size修改成原来的两倍即80px,其他不作改动,结果如下图所示。显然div的宽高也变成了原来的两倍,此时1rem=80px。这也印证了之前所说的只需通过修改根元素的font-size值就可以等比例缩放。 相信到这里大家也发现,每次设置rem都要换算一遍,非常麻烦。为了方便我们可以: 将根元素的font-size设置成100px,这样只需要将px的数字的小数点往前挪两位再把单位换成rem就可以了,例如160px换成rem就是1.6rem。 利用sass,less等css预处理语言,设置一个函数专门用于单位转换。由于各种语言实现有区别,这里就不给出例子了。 利用现成的转换工具,这里推荐一个在线转换工具: px–>rem 这篇文章的主题是移动端适配,现在就具体讲讲在实际运用中如何用rem适配不同大小的屏幕 一般来说我们拿到的设计稿都是以iphone6的屏幕为基准设计的,也就是说宽度为375px,假设我们把根元素的font-size设置为100px。如果我们要适配的屏幕宽度为320px(即iphone5的屏幕宽度),那么此时根元素font-size应该设置为:100*(320/375),即85.3px。通过这样设置我们的页面便能完美等比例适配320px的屏幕。 为了计算方便,也为了避免根元素的font-size太大我们一般把根元素font-size设置为屏幕宽度的1/10(即375px的屏幕就设置为37.5px,320px的屏幕就设置为32px)。这样我们无需经过计算就能直接根据需要适配的屏幕宽度来得出font-size的大小,同时也能避免根元素font-size太大。 现在问题来了,学挖掘机哪家强 究竟怎么动态修改font-size呢?有一个简单粗暴地方法,既然主流的屏幕分辨率也就那几个,我们大可以通过media query媒体查询,把最常见的几种情况写下来。1234567891011121314html { font-size : 37.5px;}@media only screen and (min-width: 320px){ html { font-size: 32px !important; }}@media only screen and (min-width: 414px){ html { font-size: 41.4px !important; }}} 这样基本能覆盖大部分设备了,如果说一定要适配所有设备,我们还可以通过JS动态计算font-size。淘宝的首页就是用这个方法,审查元素之后可以看到不断改变屏幕大小,html的font-size都能实时变化。1document.getElementsByTagName('html')[0].style.fontSize = window.innerWidth / 10 + 'px'; 我们把上面的代码绑定到DOMContentLoaded时间中,这样在进入页面的时候就能马上计算出font-size。 参考资料: http://www.alloyteam.com/2016/03/mobile-web-adaptation-tool-rem/ http://caibaojian.com/toutiao/6215 https://isux.tencent.com/web-app-rem.html]]></content>
<tags>
<tag>CSS</tag>
</tags>
</entry>
<entry>
<title><![CDATA[hello-world]]></title>
<url>%2Fposts%2Fb1d4025b%2F</url>
<content type="text"><![CDATA[Hello World!This is Weibin’s first artical.]]></content>
</entry>
</search>