怎么架构vue,有vue脚手架什么意思吗?

vue2.0(39)
Vue-cli是Vue的脚手架工具
主要作用:目录结构、本地调试、代码部署、热加载、单元
安装Vue-cli
npm install -g vue-cli11
安装成功之后,可以执行vue list&命令查看:&
举例出了我们可以用的哪些模板。
然后我们初始化一个基于webpack模板的项目
vue init webpack sell11
sell&是我们项目名称。&
可以一直回车(我这里最后2个选择的no).
执行完后,我们会发现多了一个名为sell&的文件夹,目录结构如下图:&
npm install1212
安装所需要的依赖.&
安装完依赖之后项目目录结构变化如下图:&
(多了一个node_modules&目录)
然后启动服务,执行
npm run dev11
浏览器打开:&
项目目录结构介绍
.babelrc&babel配置文件,把我们ES2105的代码通过它编译成ES5的。&
.editorconfig&编辑器配置&
.eslintignore&忽略语法检查的目录文件配置&
.eslintrc.js&eslint的配置文件&
.gitignore&配置仓库的忽略&
index.html&项目入口模板文件&
package.json&node配置文件
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:3470057次
积分:21730
积分:21730
排名:第340名
原创:26篇
转载:825篇
评论:937条
(14)(31)(1)(5)(3)(74)(30)(126)(18)(3)(1)(3)(7)(16)(43)(1)(5)(2)(2)(1)(18)(6)(3)(27)(10)(7)(6)(2)(7)(8)(1)(6)(27)(22)(9)(29)(30)(42)(19)(34)(10)(27)(83)(17)(17)以上是Vue脚手架的基本的目录结构,我们一般编写代码都放在src目录下,这里有一个components文件夹,该目录用于存在自定义组件。src目录小还有一个App.vue,该文件是项目展示的窗口文件。
接下来为大家介绍一下vue文件的结构,每个vue文件包含三个部分
HTML部分的代码要包裹在&template&&/tempate&标签内。注意:如果&template&&/tempate&标签内包裹不止一个元素,我们要用一个&div&&/div&标签给包裹起来,否则会出现问题。
JS部分的代码要包裹在&script&&/script&标签内。在该标签内我们一般进行组件的属性和方法的的设置以及数据的传递等。
CSS部分的代码要包裹在 &style&&/style&标签内。注意:style内的样式是作用在全局的,如果我们只在该组件生效,我们应该在style标签中添加scoped。这样可以限定样式的作用域。即&style scoped&&/style&。
使用一个组件要有几步?
通过import 引入
在components中注册
通过注册的标签在template中使用。
这里我们需要注意的是组件的命名规则。由于vue组件在新版中是不区分大小写的,我们在引入组件的时候尽量使用驼峰命名法,在template标签引用的时候使用以-形式的拆分法引用组件。虽然直接用驼峰命名法引用组件也是行,但这不符合HTML标签的命名规则。
举个栗子:
import myComponentName from './components/myComponent';
export default {
name: 'app',
components: {
myComponentName
在script标签中,export default { }用来暴露组件,以便在使用的时候可以通过import 引入,这是ES6的写法。export default { }内的data并不是我们习以为常的对象,而是函数,该函数返回的才是我们的data对象,这样做是避免引用赋值。属性的接收是通过props接收,放在数组里,使用时把它当做data来使用;如果我们要对props接受并对传递过来的数值类型进行规定。我们可以以对象形式的进行接收,键名是传递过来的属性名,值是数据类型。
举个栗子:
export default {
props: ['name', 'content', 'age'],
'name': String,
'content': String,
'age': Number
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:2020次
排名:千里之外问题对人有帮助,内容完整,我也想知道答案
问题没有实际价值,缺少关键内容,没有改进余地
还是先说说我的一部分理解吧!
官方给出的说明,在cmd中运行 cnpm run dev ,这个命令实际是就是跑的就是build文件下的dev-server.js
这是vue官方脚手架的目录结构,几个webpack开头的文件是webpack的相关配置文件,那么有没有大神可以解释一下其他的几个文件作用是什么?并说说整个build目录下的文件打包思路是什么?每个文件分别起到了什么作用。(越详细越好,谢谢!)
这段代码是截取vue官方脚手架中的src/components/hellow.vue文件。官方是在这边style标签内写的css代码片段,那么我们使用vue很多的时候都是在移动端,会经常使用less,scss,尤其在做适配的时候经常会使用scss中的自定义函数,好自动将px转为rem,那么此时我又该怎么做?
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
是hello.vue, 不是hellow.vue。接下来正式回答:
1. 文件解释
build/dev-server.js
npm run dev 其实是通过node执行这个文件,启动一个express服务器。这个服务器加载了几个中间件,如:http-proxy-middleware(代理转发中间件),
webpack-dev-middleware(webpack开发中间件), webpack-hot-middleware(webpack热替换中间件)。
build/dev-client.js
这个文件主要是注入到浏览器,监听webpack-hot-middleware传过来的事件,如reload action。用于代码热更新等。
build/build.js
npm run build,主要是执行这个文件,主要的动作是
copy static目录到dist目录
根据webpack.prod.conf.js配置文件,对源码进行编译,输出到dist目录
build/utils.js
主要是生成webpack css-loader, sass-loader(与styles相关的Loader)等配置。
webpack配置文件不解释了,不然变成webpack入门文章。
2.如何使用less/sass
在style标签内指定对应的lang即可,如下:
&style lang="scss"&
font-size: 10
font-size: 1.2
分享到微博?
你好!看起来你挺喜欢这个内容,但是你还没有注册帐号。 当你创建了帐号,我们能准确地追踪你关注的问题,在有新答案或内容的时候收到网页和邮件通知。还能直接向作者咨询更多细节。如果上面的内容有帮助,记得点赞 (????)? 表示感谢。
明天提醒我
关闭理由:
删除理由:
忽略理由:
推广(招聘、广告、SEO 等)方面的内容
与已有问题重复(请编辑该提问指向已有相同问题)
答非所问,不符合答题要求
宜作评论而非答案
带有人身攻击、辱骂、仇恨等违反条款的内容
无法获得确切结果的问题
非开发直接相关的问题
非技术提问的讨论型问题
其他原因(请补充说明)
我要该,理由是:{"debug":false,"apiRoot":"","paySDK":"/api/js","wechatConfigAPI":"/api/wechat/jssdkconfig","name":"production","instance":"column","tokens":{"X-XSRF-TOKEN":null,"X-UDID":null,"Authorization":"oauth c3cef7c66aa9e6a1e3160e20"}}
{"database":{"Post":{"":{"contributes":[{"sourceColumn":{"lastUpdated":,"description":"一个专注互联网相关技术的专栏","permission":"COLUMN_PUBLIC","memberId":,"contributePermission":"COLUMN_PUBLIC","translatedCommentPermission":"all","canManage":true,"intro":"高效的中文IT技术平台","urlToken":"dreawer","id":21254,"imagePath":"v2-def9c21d9ca33ad157f4208.jpg","slug":"dreawer","applyReason":"0","name":"极乐科技","title":"极乐科技","url":"/dreawer","commentPermission":"COLUMN_ALL_CAN_COMMENT","canPost":true,"created":,"state":"COLUMN_NORMAL","followers":22346,"avatar":{"id":"v2-def9c21d9ca33ad157f4208","template":"/{id}_{size}.jpg"},"activateAuthorRequested":false,"following":false,"imageUrl":"/v2-def9c21d9ca33ad157f4208_l.jpg","articlesCount":481},"state":"accepted","targetPost":{"titleImage":"/v2-fff9bcc10cf56b4f18e3b_r.jpg","lastUpdated":,"imagePath":"v2-fff9bcc10cf56b4f18e3b.jpg","permission":"ARTICLE_PUBLIC","topics":[,769],"summary":" 一开始在vue1.0的时候我就开始关注掘金的vue专栏,但是一直给我一种感受,真正能静下心来给读者写好每一个细讲,每一个讲解点,我觉得是最重要的,虽然我没有什么能力给大家带来像iview,mint ui等一系列大牛的基础vue带来的ui框…","copyPermission":"ARTICLE_COPYABLE","translatedCommentPermission":"all","likes":0,"origAuthorId":0,"publishedTime":"T18:42:19+08:00","sourceUrl":"","urlToken":,"id":2634742,"withContent":false,"slug":,"bigTitleImage":false,"title":"史上最详细VUE2.0全套demo讲解 基础篇","url":"/p/","commentPermission":"ARTICLE_ALL_CAN_COMMENT","snapshotUrl":"","created":,"comments":0,"columnId":21254,"content":"","parentId":0,"state":"ARTICLE_PUBLISHED","imageUrl":"/v2-fff9bcc10cf56b4f18e3b_r.jpg","author":{"bio":"极乐小程序商店(/)","isFollowing":false,"hash":"4b43cf6f3d97f480b3ca2c13e5b12752","uid":563500,"isOrg":false,"slug":"Dreawer","isFollowed":false,"description":"知乎专栏 && /dreawer \n极乐小程序商店 && /\n极乐科技
&& /","name":"极乐君","profileUrl":"/people/Dreawer","avatar":{"id":"v2-8cb80f97b7d5f74c7a8997cbc4089e1f","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},"memberId":,"excerptTitle":"","voteType":"ARTICLE_VOTE_CLEAR"},"id":598722}],"title":"史上最详细VUE2.0全套demo讲解 基础篇","author":"Dreawer","content":"一开始在vue1.0的时候我就开始关注掘金的vue专栏,但是一直给我一种感受,真正能静下心来给读者写好每一个细讲,每一个讲解点,我觉得是最重要的,虽然我没有什么能力给大家带来像iview,mint ui等一系列大牛的基础vue带来的ui框架,但是我相信通过我自己一点一点的积累,自己如何去入门一个mvvm框架的时候,慢慢用上所有最常用的api这都是一个积累的过种,这也是我在掘金的第一篇,我只想写一个大家所有所期望的东西列一个清单吧,其实入门就是基础,如何入门我下面总结一下我展示结的流程,用心去给大家写一个就像我标题写的史上最详细VUE2.0最详细全套讲解(基础篇),听了小编的建议分多次写,把每一细讲透彻讲清楚讲明白,如果反响好的话,我会更加动力快速打造出更多基础篇``组件篇``进阶篇``插件篇``如何为自己公司打造组件,也希望大牛能给我一点错误的指点,小白们能和我一起共同学习进步,我同时也希望大家能看了之后能给我点一个大大的赞!
基础api的讲解,也是根据官方文档讲解,比方说把一些mutation method ,变异方法讲的更白话文一点,让大家更容易的明白结合工作同时大家会接触的业务场景给出不同的demo,这些demo也是我自己精心在实战中总结出来的es6在vue中写法的拓展通过一步一步的学习,此文是浅入深出,给出的demo我们可以复制到自己的电脑上自己跑一遍,跟着自己的想法拓展出别的demo,丰富自己的技术栈Vue是什么,Vue好在那里,多牛B,也就不说了,既然你进来看了,说明他就是牛B的,整个vue生态圈证明了一切,掘金的发表vue的文章也证明了一切。我所讲的一切都建立在vue-cli 2.2 的脚手架工具里实现 这样东西我就不详细的讲解了我们主要把核心放在基础部分但是我要对所写的目录结构说一下其它我们先不用管,基础部分我们只要对app.vue文件进行改动就可以,然后复制我的demo代码就可以看到所有讲解的demo效果其它我们先不用管,基础部分我们只要对app.vue文件进行改动就可以,然后复制我的demo代码就可以看到所有讲解的demo效果模板语法 1.文本数据绑定最常见的形式就是使用 “Mustache” 语法(双大括号)的文本插值:Mustache 标签将会被替代为对应数据对象上 world 属性的值 ,而且一直会监听world的值,一但改变会跟着改变应用场景 : 可以说处处都用的到&template&\n
&p&hello {{world}}&/p&\n
&p v-text=\"'hello ' + world\"&&/p&\n
&p&{{`hello ${world}`}}&/p&\n
&p v-text=\"`hello ${world}`\"&&/p&\n
&button @click=\"world='ziksang'\"&改变wrold值&/button&\n
&/div&\n&/template&\n\n&script&\n\nexport default {\n
data () {\n
return {\n
world : \"world\"\n
}\n}\n\n&/script&\n以上用了四种写法第一种“Mustache” 语法(双大括号)写法第二种 用v-text的指今写法第三种和第四是对es6写法的拓展写法,称模板字符串2. v-once通过指令我们可以对文本值进行一次性赋值操作,只进行第一次的数据渲染,如果再次改变值,文本值也不会改变应用场景 : 一般是用在组件树中传递时,导致组件数据一层一层传递时,变改了不需要改变的场景,用v-once可以避免在组件数中只需用一次性赋值操作&template&\n
&p v-once&hello {{world}}&/p&\n
&button @click=\"world='ziksang'\"&改变wrold值&/button&\n
&/div&\n&/template&\n\n&script&\n\nexport default {\n
data () {\n
return {\n
world : \"world\"\n
}\n}\n\n&/script&\n我们再次点击button时,我们会发现没有任何改变,值行了所谓的一次性赋值3. 纯html我们在解析的不是文件而是一个html格式的时候放在v-text中或者{{}}就会被当作一个文本解析,所以我们此时要用v-html指令进行解析,在1.0中支持{{{}}}这种格式,为了防止xss功击,去除了这个功能常用场景 : 当我们在跟前后台对接口数据时,后台会返回一个html格式,一般是后台操作界面编译的样式文本,此时我们就要用v-html来进行解析&template&\n
&p v-html='html'&&/p&\n
&/div&\n&/template&\n\n&script&\n\nexport default {\n
data () {\n
return {\n
html : `&span style='color :'&显示红色的字你就解析成功了&/span&`\n
}\n}\n&/script&\n此时在界面我们就能看到显示红色的字你就解析成功了这几个字样,就是被成功解析了4. 属性在vue中属性这个东西很关健,在组件与组件中数据传递时会很有用,但是对于属性的解析我们不能用{{}}“Mustache” 语法(双大括号)写法,我们同时还是要用指令去解析,那就是v-bind:*,同时我们可以简写用v-bind语法糖 :即可如果我们先不考虑组件传递,我们就是考虑简单的给元素加属性应用场景 在组件中传递时需要用,其它元素上的绑定属性都需要这个功能&template&\n
&a :href='href'&href&/a&\n
&p :id='id'&id&/p&\n
&img :src=\"src\" alt=\"图片\"&\n
&button :disabled = 'disabled'&按钮&/button&\n
&/div&\n&/template&\n\n&script&\n\nexport default {\n
data () {\n
return {\n
href : '',\n
src : 'https://cn.vuejs.org/images/logo.png',\n
disabled : true\n
}\n}\n&/script&\n我们在属性中支持number string boolean类型,以上显示能在界面中看出都能正常进行和原本属性所预期的,不用:来绑定的属性可以直接属性赋值,如果一定要通过data数据选项中返回的值一定要加 :5.使用javascript表达式应用场景 :在业务场景中一些方法判断或者简单的过滤,那我们可以用javascript表达式,能让代码更简洁,更清晰,比方说用一个三元表达式。等等 &template&\n
&div id=\"method1\"&\n
&p&{{ count & 0 ? '+' : '-'}} {{count + 1}}&/p&\n
&button @click='count ++'&增加&/button&\n
&button @click='count --'&减少&/button&\n
&div id=\"method2\"&\n
&input type=\"text\" v-model='message'&\n
&h1&{{ message.split('').reverse().join('') }}&/h1&\n
&/div&\n&/template&\n\n&script&\n\nexport default {\n
data () {\n
return {\n
count : 0,\n
message : \"\"\n
}\n}\n\n&/script&\n在id:method1 里,我们做了一个对count进行+1,对count进行三元表达式来进行判断做出不同的显示在id:method2 里,我进行了v-model指令和h1里message进行了双向绑定,随着message的改变h1里的值随着表达式的的改变而改变注意示项,在官方提出来的这几种是不可行的这是语句,不是表达式 \n{{ var a = 1 }}\n流控制也不会生效,请使用三元表达式 \n{{ if (ok) { return message } }}\n6.修饰赋修饰符( Modifiers )是以半角句号, 指明的特殊后缀, 用于指出一个指令应该以特殊方式绑定。应用场景 :对于一些特殊的指令操作时需要,比方说对组件加事件,组织元素的默认行为,组织冒泡。等等一系列,官方文档有详细解说每一个修饰赋的具体用途再次提示主逻辑代码都是写在.App.vue中,所有其它的组件代码都是写在componentes里首我们先对components文件夹中创建一个myButton组件&template&\n
&button&按钮&/button&\n&/template&\n\n&script&\nexport default {\n\n}\n&/script&\n再在app.vue中写入&template&\n
&my-button @click.native=\"buttonClick\"&&/my-button&\n
&/div&\n&/template&\n\n&script&\nimport myButton from './components/myButton.vue'\nexport default {\n
components : {\n
myButton\n
methods : {\n
buttonClick () {\n
alert(\"原生点击\")\n
}\n}\n\n&/script&\n我们可以尝试一下如果我们去掉.native的话你会发现组件根本出alert(原生点击)的弹框,这是为什么呢,正常的情况在一个单个组件内部自己使用v-on的事件,ok都不会有问题,如果在一个组件上定义一个指令事件,必须要用.native,这里大家一定要注意####推荐很实用的demo我们用jquery的时候经常会遇到这种场景,当我们一个layer层弹出来的时候,内部是一个超过整个屏幕长度的滚动,当我们滑动layer的时候会导致里面的滚动也会一起滚动起来,这个是一件很操蛋的事,在网上也有着很多相关解决的办法,vue可以通过修饰符来解决这个问题index.html文件中加一个视口度适配,更好的来查看效果&meta name=”viewport” content=”width=device-width, initial-scale=1, maximum-scale=1″&\napp.vue文件中&template&\n
&li v-for=\"item in list\"&\n
&h1&{{item}}&/h1&\n
&div @touchmove.prevent class=\"layer\"&&/div&\n
&/div&\n&/template&\n\n&script&\nimport myButton from './components/myButton.vue'\nexport default {\n
data () {\n
return {\n
list : [1,2,3,4,5,6,7,8,9,10]\n
}\n}\n\n&/script&\n\n&style&\nbody,html {width:100%;height:100%}\nh1{margin-top:10rem;}\n.layer{width:100%;height:100%;position:fixed;top:0;left:0;z-index:99;background:rgba(0,0,0,.5)}\n&/style&\n此时我们打开chrome调试工具的时候我们会发当我们滑动layer层的,里面的滚动不会再滚动了,我们再试着把.prevent给去掉,会发现滚动再次出现,这个原理就是event.preventDefault来阻止默认事件事执行的。实现原理,我们对layer层把它的touchmove滑动事件给干掉了,就不会触发滚动区域的滑动事件,个人认为这个场景每个项目都会用的到再做一个表单的简单示例&template&\n
&input type=\"text\" v-model.trim=\"content\"&\n
&input type=\"text\" v-model = 'content'&\n
&/div&\n&/template&\n\n&script&\nexport default {\n
data () {\n
return {\n
content : \"\"\n
}\n}\n&/script&\n这个我们能发现当加入.trim的修饰符的时候给到第二个input的时候都是去掉前后空白符的,这个功能也就是去掉前后空白符,这也是很常用的场景,一些表单当用户输入的时候,有些用户会打出一个前后空白符,有一次我保用户数据给数据库,操作的时候还代码还好好的,突然一个空白字符引发的一场血案,要注意细节点6. 过滤器 Vue.js 允许你自定义过滤器,可被用作一些常见的文本格式化。过滤器可以用在两个地方:mustache 插值和 v-bind 表达式。过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符指示:其实本质上过滤器也就是一个或多个表达式,但是有极条件控制的表达式或者长业务逻辑的表达式写在mustache不够清楚,明了在v-bind中也就是2.10的时候才支持这个功能,这个大家要对这个版本有一定的了解,正确的使用应用场景 :我说说我遇到过的应用场景,再把我应用的场景再加深一点逻辑1.首先我们从后台拿到10个用户的手机,但是要把前7位掩码,做成*,然判断如果手机尾数是偶数的话,我们就显示已中奖,不是的话显示谢谢参与 &template&\n
&p v-for='tel in telephone'&{{tel | newtel}}&/p&\n
&/div&\n&/template&\n\n&script&\nexport default {\n
data () {\n
return {\n
telephone : [\n
filters : {\n
newtel (value) {\n
if(!value) return ''\n
value = value.toString().substr(7,4)\n
value = '*'.repeat(7)+value\n
let endMember = value.substr(-1,1)\n
if(endMember % 2){\n
value = value+'中奖'\n
value = value+'谢谢参与'\n
return value\n
}\n}\n&/script&\n代码会析,在filters选项里有个newtel的函数,value则是函数中固定的一个参数,代码过滤前的值,但最后在管道符里我们不用把这个固定的参数写在调用里面,如果没有,则返回\"\",我们再取后四位,在前7位拼接7个*,这里我用到了es6新特性,repeat这个方法,原本我想用padStart可惜是es7的新性,babel不支持,再截取尾数,如果尾数%2是偶数则再后面加一个中奖,否则谢谢参与,最后把这个value的最终值返回出去 7.结合过滤器参数与v-bind过滤以上过滤器原理也跟大家说了,我们进一步深化一下,如果两都结合使用应用场景当后台传给很多链接的地址的时候,每个链接要带上不同的参数,针对于这种场景我给大家实战一下&template&\n
&a v-for='(url,index) in urlList' :href='url.url | getquery(url.name,url.age)'&{{url.url}}&/a&\n
&/div&\n&/template&\n\n&script&\nexport default {\n
data () {\n
return {\n
urlList : [\n
{url : '',name :'ziksang',age : 20},\n
{url : '',name :'ziksang2',age : 30}\n
filters : {\n
getquery (value,name,age) {\n
if(!value) return \"\"\n
return `${value}?name=${name}&age=${age}`\n
}\n}\n&/script&\n在v-bind管道符中的getquery(第一个参,第二个参)第一个参就是传给fitler里getquery里的第二个参数,就是name第二个参就是传给fitler里getquery里的第三个参数,就是age因为第一个参数是默认的这里我大量用了v-for来进行循环,如果有看不懂的话,等讲到第基础二或者三的时候再回头看看这里的v-for用法你就一目了然了8.缩写&!-- 完整语法 --&\n&a v-bind:href=\"url\"&&/a&\n&!-- 缩写 --&\n&a :href=\"url\"&&/a&\n&!-- 完整语法 --&\n&a v-on:click=\"doSomething\"&&/a&\n&!-- 缩写 --&\n&a @click=\"doSomething\"&&/a&\n这没有什么可以多讲的,就是一个语法糖,方便大家书写写到这里,只是简单的讲一下模板语法,但是我个人认为,还展拓了很多其它知识点,刚好如果那些知识点你不会的,可以去查一查做一个预热,在接下来几天中,我们把上出第二篇基础( v-for 列表渲染 )作者:版权归作者所有,转载请注明出处 ","updated":"T10:42:19.000Z","canComment":false,"commentPermission":"anyone","commentCount":10,"collapsedCount":0,"likeCount":83,"state":"published","isLiked":false,"slug":"","isTitleImageFullScreen":false,"rating":"none","titleImage":"/v2-fff9bcc10cf56b4f18e3b_r.jpg","links":{"comments":"/api/posts//comments"},"reviewers":[],"topics":[{"url":"/topic/","id":"","name":"Vue.js"},{"url":"/topic/","id":"","name":"HTML"},{"url":"/topic/","id":"","name":"JavaScript"}],"adminClosedComment":false,"titleImageSize":{"width":1350,"height":750},"href":"/api/posts/","excerptTitle":"","column":{"slug":"dreawer","name":"极乐科技"},"tipjarState":"inactivated","annotationAction":[],"sourceUrl":"","pageCommentsCount":10,"hasPublishingDraft":false,"snapshotUrl":"","publishedTime":"T18:42:19+08:00","url":"/p/","lastestLikers":[{"bio":"如有异议,你是对的。","isFollowing":false,"hash":"b0effbcc44410bbdfe07e50a","uid":08,"isOrg":false,"slug":"shandian","isFollowed":false,"description":"VR虚拟现实从业者","name":"王又贝","profileUrl":"/people/shandian","avatar":{"id":"c","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},{"bio":null,"isFollowing":false,"hash":"50cc43b6","uid":080900,"isOrg":false,"slug":"loufeng_teck","isFollowed":false,"description":"","name":"小锋","profileUrl":"/people/loufeng_teck","avatar":{"id":"c6fd0ed6072b9acdfdccce0","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},{"bio":"学生","isFollowing":false,"hash":"93d4e3bab2764baad05b9b95cd26888c","uid":267100,"isOrg":false,"slug":"jack-18-86-95","isFollowed":false,"description":"","name":"Jack","profileUrl":"/people/jack-18-86-95","avatar":{"id":"da8e974dc","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},{"bio":".net Developer, C#, javascript 工作年限:5年。 相关行业:LBS, 位置监控. 金融数据搜集、计算","isFollowing":false,"hash":"68aeeb626fb1bdc1ebd474c","uid":48,"isOrg":false,"slug":"jason-gao","isFollowed":false,"description":"","name":"Jason gao","profileUrl":"/people/jason-gao","avatar":{"id":"da8e974dc","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},{"bio":"民工","isFollowing":false,"hash":"25760e05abbb980abccd9cd9e9ec9354","uid":051900,"isOrg":false,"slug":"ye-xiao-wu-52","isFollowed":false,"description":"","name":"叶小污","profileUrl":"/people/ye-xiao-wu-52","avatar":{"id":"v2-623e7ca2f86ac66ab0009beac5806515","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false}],"summary":" 一开始在vue1.0的时候我就开始关注掘金的vue专栏,但是一直给我一种感受,真正能静下心来给读者写好每一个细讲,每一个讲解点,我觉得是最重要的,虽然我没有什么能力给大家带来像iview,mint ui等一系列大牛的基础vue带来的ui框…","reviewingCommentsCount":0,"meta":{"previous":{"isTitleImageFullScreen":false,"rating":"none","titleImage":"","links":{"comments":"/api/posts//comments"},"topics":[{"url":"/topic/","id":"","name":"Blink(浏览器内核)"},{"url":"/topic/","id":"","name":"前端开发"}],"adminClosedComment":false,"href":"/api/posts/","excerptTitle":"","author":{"bio":"极乐小程序商店(/)","isFollowing":false,"hash":"4b43cf6f3d97f480b3ca2c13e5b12752","uid":563500,"isOrg":false,"slug":"Dreawer","isFollowed":false,"description":"知乎专栏 && /dreawer \n极乐小程序商店 && /\n极乐科技
&& /","name":"极乐君","profileUrl":"/people/Dreawer","avatar":{"id":"v2-8cb80f97b7d5f74c7a8997cbc4089e1f","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},"column":{"slug":"dreawer","name":"极乐科技"},"content":"作为一个前端开发,最常见的运行环境应该是浏览器吧,为了更好的通过浏览器把优秀的产品带给用户,也为了更好的发展自己的前端职业之路,有必要了解从我们在浏览器地址栏输入网址到看到页面这期间浏览器是如何进行工作的,进而了解如何更好的优化实践,本篇主要围绕这两点展开阐述。前端页面渲染机制可谓是老生常谈,但又很有必要再谈的话题,于是还是决定写一篇,即是对知识的回顾总结,又能与大家分享,何乐而不为。网上相关类型的文章也很多,有兴趣的可以多学习一下。浏览器在介绍浏览器工作流程之前,先了解一下主流浏览器的基础结构,本文所介绍的浏览器主要为开源的Chrome,FireFox及部分开源的Safari,这也是目前市场占比最高的几大浏览器,以本人博客网站为例,可以大致看出各浏览器使用比例: 浏览器基础结构浏览器基础结构主要包括如下7部分: 1.用户界面(User Interface):用户所看到及与之交互的功能组件,如地址栏,返回,前进按钮等;2.浏览器引擎(Browser engine):负责控制和管理下一级的渲染引擎;3.渲染引擎(Rendering engine):负责解析用户请求的内容(如HTML或XML,渲染引擎会解析HTML或XML,以及相关CSS,然后返回解析后的内容);4.网络(Networking):负责处理网络相关的事务,如HTTP请求等;5.UI后端(UI backend):负责绘制提示框等浏览器组件,其底层使用的是操作系统的用户接口;6.JavaScript解释器(JavaScript interpreter):负责解析和执行JavaScript代码;7.数据存储(Data storage):负责持久存储诸如cookie和缓存等应用数据。浏览器内核各大主要浏览器使用内核也是有差别的,大致可以分为以下几类:Trident内核: IEWebkit内核:Chrome,SafariGecko内核:FireFox网络当用户访问页面时,浏览器需要获取用户请求内容,这个过程主要涉及浏览器网络模块:1.用户在地址栏输入域名,如,DNS(Domain Name System,域名解析系统)服务器根据输入的域名查找对应IP,然后向该IP地址发起请求;2.浏览器获得并解析服务器的返回内容(HTTP response);3.浏览器加载HTML文件及文件内包含的外部引用文件及图片,多媒体等资源。DNS预解析(DNS prefetch)浏览器DNS解析大多时候较快,且会缓存常用域名的解析值,但是如果网站涉及多域名,在对每一个域名访问时都需要先解析出IP地址,而我们希望在跳转或者请求其他域名资源时尽量快,则可以开启域名预解析,浏览器会在空闲时提前解析声明需要预解析的多进程我们通常说JavaScript执行是单进程的,但是浏览器网络部分通常是有几个平行进程同时开启,但是也会有限制,一般为2-6个。渲染引擎及关键渲染路径(Critical Rendering Path)渲染引擎所做的事是将请求内容展现给我们,默认支持HTML,XML和图片类型,对于其他诸如PDF等类型的内容则需要安装相应插件,但浏览器的展示工作流程基本是一样的。通过网络模块加载到HTML文件后渲染引擎渲染流程如下,这也通常被称作关键渲染路径(Critical Rendering Path):1.构建DOM树(DOM tree):从上到下解析HTML文档生成DOM节点树(DOM tree),也叫内容树(content tree);2.构建CSSOM(CSS Object Model)树:加载解析样式生成CSSOM树;3.执行JavaScript:加载并执行JavaScript代码(包括内联代码或外联JavaScript文件);4.构建渲染树(render tree):根据DOM树和CSSOM树,生成渲染树(render tree);渲染树:按顺序展示在屏幕上的一系列矩形,这些矩形带有字体,颜色和尺寸等视觉属性。5.布局(layout):根据渲染树将节点树的每一个节点布局在屏幕上的正确位置;6.绘制(painting):遍历渲染树绘制所有节点,为每一个节点适用对应的样式,这一过程是通过UI后端模块完成;为了更友好的用户体验,浏览器会尽可能快的展现内容,而不会等到文档所有内容到达才开始解析和构建/布局渲染树,而是每次处理一部分,并展现在屏幕上,这也是为什么我们经常可以看到页面加载的时候内容是从上到下一点一点展现的。流程图Webkit渲染引擎流程如下图:Gecko渲染引擎流程如下图:如上图,Webkit浏览器和Gecko浏览器渲染流程大致相同,不同的是:1.Webkit浏览器中的渲染树(render tree),在Gecko浏览器中对应的则是框架树(frame tree),渲染对象(render object)对应的是框架(frame);2.Webkit中的布局(Layout)过程,在Gecko中称为回流(Reflow),本质是一样的,后文会解释回流的另一层含义–重新布局;3.Gecko中HTML和DOM树中间多了一层内容池(Content sink),可以理解成生成DOM元素的工厂。单进程不同于网络部分的多进程渲染引擎是单线程工作的,意味着渲染流程是一步一步渐进完成的。解析文档(parser HTML)在详细介绍浏览器渲染文档之前,先应该理解浏览器如何解析文档:解析文档的顺序,对于CSS和JavaScript如何处理等。 解析顺序浏览器按从上到下的顺序扫描解析文档;解析样式和脚本脚本或许是由于通常会在JavaScript脚本中改变文档DOM结构,于是浏览器以同步方式解析,加载和执行脚本,浏览器在解析文档时,当解析到&script&标签时,会解析其中的脚本(对于外链的JavaScript文件,需要先加载该文件内容,再进行解析),然后立即执行,这整个过程都会阻塞文档解析,直到脚本执行完才会继续解析文档。就是说由于脚本是同步加载和执行的,它会阻塞文档解析,这也解释了为什么现在通常建议将&script&标签放在标签前面,而不是放在&head&标签里。现在HTML5提供defer和async两个属性支持延迟和异步加载JavaScript文件,如:&script defer src=\"script.js\"& 改进针对上文说的脚本阻塞文档解析,主流浏览器如Chrome和FireFox等都有一些优化,比如在执行脚本时,开启另一个进程解析剩余的文档以找出并加载其他的待下载外部资源(不改变主进程的DOM树,仅优化加载外部资源)。样式不同于脚本,浏览器对样式的处理并不会阻塞文档解析,大概是因为样式表并不会改变DOM结构。样式表与脚本你可能想问样式是否会阻塞脚本文件的加载执行呢?正常情况是不会的,但是存在一个问题是通常我们会在脚本中请求样式信息,但是在文档解析时,如果样式尚未加载或解析,将会得到错误信息,对于这一问题,FireFox浏览器和Webkit浏览器处理策略不同:当存在有样式文件未被加载和解析时,FireFox浏览器会阻塞所有脚本;而Webkit浏览器只会阻塞操作了改文件内声明的样式属性的脚本。构建DOM树,DOM树,即文档内所有节点构成的一个树形结构。假设浏览器获取返回的如下HTML文档:
&!doctype html&\n
&link rel=\"stylesheet\" href=\"./theme.css\"&&/link&\n
&script src=\"./config.js\"&&/script&\n
&title&关键渲染路径&/title&\n
&h1 class=\"title\"&关键渲染路径&/h1&\n
&p&关键渲染路径介绍&/p&\n
&footer&@copyright2017&/footer&\n
&/html&\n 首先浏览器从上到下依次解析文档构建DOM树,如下:构建CSSOM树,CSSOM树,与DOM树结构相似,只是另外为每一个节点关联了样式信息。theme.css样式内容如下:html, body {\n
width: 100%;\n
height: 100%;\n
background-color: #fcfcfc;\n
.title {\n
font-size: 20px;\n
.footer {\n
font-size: 12px;\n
color: #aaa;\n
}\n构建CSSOM树如图:执行JavaScript上文已经阐述了文档解析时对脚本的处理,我们得知脚本加载,解析和执行会阻塞文档解析,而在特殊情况下样式的加载和解析也会阻塞脚本,所以现在推荐的实践是&script&标签放在&/body&标签前面。构建渲染树(render tree)DOM树和CSSOM树都构建完了,接着浏览器会构建渲染树:渲染树,代表一个文档的视觉展示,浏览器通过它将文档内容绘制在浏览器窗口,展示给用户,它由按顺序展示在屏幕上的一系列矩形对象组成,这些矩形对象都带有字体,颜色和尺寸,位置等视觉样式属性。对于这些矩对象,FireFox称之为框架(frame),Webkit浏览器称之为渲染对象(render object, renderer),后文统称为渲染对象。 这里把渲染树节点称为矩形对象,是因为,每一个渲染对象都代表着其对应DOM节点的,该盒子包含了尺寸,位置等几何信息,同时它指向一个样式对象包含其他视觉样式信息。渲染树与DOM树每一个渲染对象都对应着DOM节点,但是非视觉(隐藏,不占位)DOM元素不会插入渲染树,如&head&元素或声明display:的元素,渲染对象与DOM节点不是简单的一对一的关系,一个DOM可以对应一个渲染对象,但一个DOM元素也可能对应多个渲染对象,因为有很多元素不止包含一个,如当文本被折行时,会产生多个行盒,这些行会生成多个渲染对象;又如行内元素同时包含块元素和行内元素,则会创建一个匿名块级盒包含内部行内元素,此时一个DOM对应多个矩形对象(渲染对象)。渲染树及其对应DOM树如图:图中渲染树viewport即视口,是文档的初始包含块,scroll代表滚动区域,详见渲染树并不会包含显式或隐式地display:的标签元素。布局(Layout)或回流(reflow,relayout)创建渲染树后,下一步就是布局(Layout),或者叫回流(reflow,relayout),这个过程就是通过渲染树中渲染对象的信息,计算出每一个渲染对象的位置和尺寸,将其安置在浏览器窗口的正确位置,而有些时候我们会在文档布局完成后对DOM进行修改,这时候可能需要重新进行布局,也可称其为回流,本质上还是一个布局的过程,每一个渲染对象都有一个布局或者回流方法,实现其布局或回流。 流(flow)HTML采用的是基于流的方式定位布局,其按照从左到右,从上到下的顺序进行排列,详见。 全局布局与局部布局对渲染树的布局可以分为全局和局部的,全局即对整个渲染树进行重新布局,如当我们改变了窗口尺寸或方向或者是修改了根元素的尺寸或者字体大小等;而局部布局可以是对渲染树的某部分或某一个渲染对象进行重新布局。 脏位系统(dirty bit system)大多数web应用对DOM的操作都是比较频繁,这意味着经常需要对DOM进行布局和回流,而如果仅仅是一些小改变,就触发整个渲染树的回流,这显然是不好的,为了避免这种情况,浏览器使用了脏位系统,只有一个渲染对象改变了或者某渲染对象及其子渲染对象脏位值为”dirty”时,说明需要回流。表示需要布局的脏位值有两种:“dirty”–自身改变,需要回流“children are dirty”–子节点改变,需要回流布局过程布局是一个从上到下,从外到内进行的递归过程,从根渲染对象,即对应着HTML文档根元素&html&,然后下一级渲染对象,如对应着&body&元素,如此层层递归,依次计算每一个渲染对象的几何信息(位置和尺寸)。几何信息-位置和尺寸,即相对于窗口的坐标和尺寸,如根渲染对象,其坐标为(0, 0),尺寸即是视口尺寸(浏览器窗口的可视区域)。每一个渲染对象的布局流程基本如:1.计算此渲染对象的宽度(width);2.遍历此渲染对象的所有子级,依次:2.1设置子级渲染对象的坐标2.2判断是否需要触发子渲染对象的布局或回流方法,计算子渲染对象的高度(height)3.设置此渲染对象的高度:根据子渲染对象的累积高,margin和padding的高度设置其高度;4.设置此渲染对象脏位值为false。强制回流在渲染树布局完成后,再次操作文档,改变文档的内容或结构,或者元素定位时,会触发回流,即需要重新布局,如请求某DOM的”offsetHeight”样式信息等诸多情况:DOM操作,如增加,删除,修改或移动;变更内容;激活伪类;访问或改变某些CSS属性(包括修改样式表或元素类名或使用JavaScript操作等方式);浏览器窗口变化(滚动或尺寸变化) $('body').css('padding'); // reflow\n
$('body')[0].offsetHeight; // relow\n有过CSS3动画开发经验的同学可能会有经历,如下入场动画: .slide-left {\n
-webkit-transition: margin-left 1s ease-out;\n
-moz-transition: margin-left 1s ease-out;\n
-o-transition: margin-left 1s ease-out;\n
transition: margin-left 1s ease-out;\n
}\n然后执行如下脚本: var $slide = $('.slide-left');\n
$slide.css({\n
\"margin-left\": \"100px\"\n
}).addClass('slide-left');\n
$slide.css({\n
\"margin-left\": \"10px\"\n
});\n我们会发现并没有效果,为什么呢?因为对margin-left的修改并没有触发回流,元素margin-left值的改变被缓存,如果我们在中间强制触发回流:var $slide = $('.slide-left');\n
$slide.css({\n
\"margin-left\": \"100px\"\n
console.log($slide.css('padding');\n
$slide.addClass('slide-left');\n
$slide.css({\n
\"margin-left\": \"10px\"\n
});\n再看就达到了预期效果。绘制(painting)最后是绘制(paint)阶段或重绘(repaint)阶段,浏览器UI组件将遍历渲染树并调用渲染对象的绘制(paint)方法,将内容展现在屏幕上,也有可能在之后对DOM进行修改,需要重新绘制渲染对象,也就是重绘,绘制和重绘的关系可以参考布局和回流的关系。全局与局部绘制与布局相似,绘制也分为全局和局部绘制,即对整个渲染树或某些渲染对象进行绘制。触发重绘我们已经知道很多操作可能会触发回流,那么什么时候可能触发重绘呢,通常,当改变元素的视觉样式,如background-color,visibility,margin,padding或字体颜色时会触发全局或局部重绘,如:$('body').css('color', 'red'); // repaint\n$('body').css('margin', '2px'); // reflow, repaint\n页面渲染优化浏览器对上文介绍的关键渲染路径进行了很多优化,针对每一次变化产生尽量少的操作,还有优化判断重新绘制或布局的方式等等。在改变文档根元素的字体颜色等视觉性信息时,会触发整个文档的重绘,而改变某元素的字体颜色则只触发特定元素的重绘;改变元素的位置信息会同时触发此元素(可能还包括其兄弟元素或子级元素)的布局和重绘。某些重大改变,如更改文档根元素&html&的字体尺寸,则会触发整个文档的重新布局和重绘,据此及上文所述,推荐以下优化和实践:1.HTML文档结构层次尽量少,最好不深于六层;2.脚本尽量后放,放在&/body&前即可;3.少量首屏样式内联放在&head&标签内;4.样式结构层次尽量简单;5.在脚本中尽量减少DOM操作,尽量缓存访问DOM的样式信息,避免过度触发回流;6.减少通过JavaScript代码修改元素样式,尽量使用修改class名方式操作样式或动画;7.动画尽量使用在绝对定位或固定定位的元素上;8.隐藏在屏幕外,或在页面滚动时,尽量停止动画;9.尽量缓存DOM查找,查找器尽量简洁;10.涉及多域名的网站,可以开启域名预解析实例当我们访问一个页面时,浏览器渲染事件详细日志图如下: 1.发起请求;2.解析HTML;3.解析样式;4.执行JavaScript;5.布局; 作者:版权归作者所有,转载请注明出处","state":"published","sourceUrl":"","pageCommentsCount":0,"canComment":false,"snapshotUrl":"","slug":,"publishedTime":"T11:34:46+08:00","url":"/p/","title":"浅析前端页面渲染机制","summary":"作为一个前端开发,最常见的运行环境应该是浏览器吧,为了更好的通过浏览器把优秀的产品带给用户,也为了更好的发展自己的前端职业之路,有必要了解从我们在浏览器地址栏输入网址到看到页面这期间浏览器是如何进行工作的,进而了解如何更好的优化实践,本篇…","reviewingCommentsCount":0,"meta":{"previous":null,"next":null},"commentPermission":"anyone","commentsCount":13,"likesCount":73},"next":{"isTitleImageFullScreen":false,"rating":"none","titleImage":"","links":{"comments":"/api/posts//comments"},"topics":[{"url":"/topic/","id":"","name":"上传文件"},{"url":"/topic/","id":"","name":"HTML5"},{"url":"/topic/","id":"","name":"JavaScript"}],"adminClosedComment":false,"href":"/api/posts/","excerptTitle":"","author":{"bio":"极乐小程序商店(/)","isFollowing":false,"hash":"4b43cf6f3d97f480b3ca2c13e5b12752","uid":563500,"isOrg":false,"slug":"Dreawer","isFollowed":false,"description":"知乎专栏 && /dreawer \n极乐小程序商店 && /\n极乐科技
&& /","name":"极乐君","profileUrl":"/people/Dreawer","avatar":{"id":"v2-8cb80f97b7d5f74c7a8997cbc4089e1f","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},"column":{"slug":"dreawer","name":"极乐科技"},"content":"发请求有两种方式,一种是用ajax,另一种是用form提交,默认的form提交如果不做处理的话,会使页面重定向。以一个简单的demo做说明:html如下所示,请求的路径action为”upload”,其它的不做任何处理:&form method=\"POST\" action=\"upload\" enctype=\"multipart/form-data\"&\n
名字 &input type=\"text\" name=\"user\"&&/input&\n
头像 &input type=\"file\" name=\"file\"&&/input&\n
&input type=\"submit\" id=\"_submit\" value=\"提交\"&&/input&\n
&/form&\n 服务端(node)response直接返回: “Recieved form data”,演示如下:可以看到默认情况下,form请求upload的同时重定向到upload。但是很多情况下是希望form请求像ajax一样,不会重定向或者刷新页面。像上面的场景,当上传完成之后,将用户选择的头像显示在当前页面。解决办法第一种是使用html5的FormData,将form里面的数据封装到FormData对象里,然后再以POST的方式send出去。如下面代码所示,对提交按钮的单击事件做一个响应,代码第6行获取到form的DOM对象,然后第8行构造一个FormData的实例,第18行,将form数据发送出去。document.getElementById(\"_submit\").onclick = function(event){\n
//取消掉默认的form提交方式\n
if(event.preventDefault) event.preventDefault();\n
else event.returnValue = false;
//对于IE的取消方式\n \n
var formDOM = document.getElementsByTagName(\"form\")[0];\n
//将form的DOM对象当作FormData的构造函数\n
var formData = new FormData(formDOM);\n
var req = new XMLHttpRequest();\n
req.open(\"POST\", \"upload\");\n
//请求完成\n
req.onload = function(){\n
if(this.status === 200){\n
//对请求成功的处理\n
//将form数据发送出去\n
req.send(formData);\n     //避免内存泄漏\n     req = null;\n}\n 上传成功后,服务将返回图片的访问地址,补充14行对请求成功的处理:在submit按钮的上方位置显示上传的图片:var img = document.createElement(\"img\");\nimg.src = JSON.parse(this.responseText).path;\nformDO \n示例:如果使用jQuery,可以把formData作为ajax的data参数,同时设置contentType: false和processData: false,告诉jQuery不要去处理请求头和发送的数据。看起来这种提交方式跟ajax一样,但是其实并不是完全一样,form提交的数据格式有,如果要上传文件则必须为multipart/form-data,所以上面的form提交请求里的http的头信息里面的Content-Type为multipart/form-data,而普通的ajax提交为application/json。form提交完整的Content-Type如下: \"content-type\":\"multipart/form- boundary=------WebKitFormBoundaryYOE7pWLqdFYSeBFj\"\n除了multipart/form-data之外,还指定了boundary,这个boundary的作用是用来区分不同的字段。由于FormData对象是不透明的,调用JSON.stringify将会返回一个空的对象{},同时FormData只提供append方法,所以无法得到FormData实际上传的内容,但是可以通过分析工具或者服务收到的数据进行查看。在上面如果上传一个文本文件,那么服务收到的POST数据的原始格式是这样的:------WebKitFormBoundaryYOE7pWLqdFYSeBFj\nContent-Disposition: form-data; name=\"user\"\n \nabc\n------WebKitFormBoundaryYOE7pWLqdFYSeBFj\nContent-Disposition: form-data; name=\"file\"; filename=\"test.txt\"\nContent-Type: text/plain\n \n这是一个文本文件的内容。\n \n------WebKitFormBoundaryYOE7pWLqdFYSeBFj--\n从上面服务收到的数据看出FormData提交的格式,每个字段以boundary隔开,最后以–结束。而ajax请求,send出去的数据格式是自定义的,一般都是以key=value中间用&连接:var req = new XMLHttpRequest();\nvar sendData = \"user=abc&file=这是一个文本文件的内内容\";\nreq.open(\"POST\", \"upload\");\n//发送的数据需要转义,见上面提到的三种格式\nreq.setRequestHeader(\"Content-Type\", \"application/x-www-form-urlencoded\");\nreq.send(sendData);\n服务就会收到和send发出去的字符串一模一样的内容,然后再作参数解析,所以就得统一参数的格式:user=abc&file=这是一个文本文件的内容\n从这里可以看出POST本质上并不比GET安全,POST只是没有将数据放在网址传送而已。考虑到FormData到了IE10才支持,如果要支持较低版本的IE,那么可以借助iframe。 文中一开始就说,默认的form提交会使页面重定向,而重定向的规则在,可以和a标签一样指定为”_blank”,在新窗口中打开;还可以指定为一个iframe,在该iframe中打开。所以可以弄一个隐藏的iframe,将form的target指向这个iframe,当form请求完成时,返回的数据就会由这个iframe显示,正如上面在新页面显示的:”Recieved form data”。请求完成后,iframe加载完成,触发load事件,在load事件的处理函数里,获取该iframe的内容,从而拿到服务返回的数据了!拿到后再把iframe删掉。在提交按钮的响应函数里,首先创建一个iframe,设置iframe为不可见,然后再添加到文档里: var iframe = document.createElement(\"iframe\");\niframe.width = 0;\niframe.height = 0;\niframe.border = 0;\niframe.name = \"form-iframe\";\niframe.id = \"form-iframe\";\niframe.setAttribute(\"style\", \"width:0;height:0;border:none\");\n//放到document\nthis.form.appendChild(iframe);\n改变form的target为iframe的name值:this.form.target = \"form-iframe\";\n然后再响应iframe的load事件:iframe.onload = function(){\n
var img = document.createElement(\"img\");\n
//获取iframe的内容,即服务返回的数据\n
var responseData = this.contentDocument.body.textContent || this.contentWindow.document.body.textC\n
img.src = JSON.parse(responseData).\n
f.insertBefore(img, document.getElementById(\"_submit\"));\n
//删掉iframe\n
setTimeout(function(){\n
var _frame = document.getElementById(\"form-iframe\");\n
_frame.parentNode.removeChild(_frame);\n
}, 100);\n}\n最后提交请求://如果提示submit函数不存在,请注意form里面是否有id/value为submit的控件\nthis.form.submit();\n第二种办法到这里就基本可以了,但是如果看163邮箱或者QQ邮箱上传文件的方式,会发现和上面的两种方法都不太一样。用httpfox抓取请求的数据,会发现上传的内容的格式并不是上面说的用boundary隔开,而是直接把文件的内容POST出去了,而文件名、文件大小等相关信息放在了文件的头部。如163邮箱:POST Data: this is a textHeaders:Mail-Upload-name: content.txtMail-Upload-size: 15 可以推测它们应该是直接读取了input文件的内容,然后直接POST出去了。要实现这样的功能,可以借助FileReader,读取input文件的内容,再保留二进制的格式发送出去:var req = new XMLHttpRequest();\nreq.open(\"POST\", \"upload\"); \n//设置和邮箱一样的Content-Type\nreq.setRequestHeader(\"Content-Type\", \"application/octet-stream\");\nvar fr = new FileReader();\nfr.onload = function(){\n
req.sendAsBinary(this.result);\n}\nreq.onload = function(){\n
//一样,省略\n}\n //读取input文件内容,放到fileReader的result字段里\nfr.readAsBinaryString(this.form[\"file\"].files[0]);\n代码第13行执行读文件,读取完毕后触发第6行的load响应函数,第7行以二进制文本形式发送出去。由于sendAsBinary的支持性不是很好,可以自行:if(typeof XMLHttpRequest.prototype.sendAsBinary === 'undefined'){\n
XMLHttpRequest.prototype.sendAsBinary = function(text){\n
var data = new ArrayBuffer(text.length);\n
var ui8a = new Uint8Array(data, 0);\n
for (var i = 0; i & text.length; i++){ \n
ui8a[i] = (text.charCodeAt(i) & 0xff);\n
this.send(ui8a);\n
}\n}\n代码的关键在于第6行,将字符串转成8位无符号整型,还原二进制文件的内容。在执行了fr.readAsBinaryString之后,二进制文件的内容将会以utf-8的编码以字符串形式存放到result,上面的第6行代码将每个unicode编码转成整型(&0xff或者parseInt),存放到一个8位无符号整型数组里面,第8行把这个数组发送出去。如果直接send,而不是sendAsBinary,服务收到的数据将无法正常还原成原本的文件。上面的实现需要考虑文件太大,需分段上传的问题。关于FileReader的,IE10以上支持,IE9有另外一套File API。文章讨论了3种办法实现无刷新上传文件,分别是使用iframe、FormData和FileReader,支持性最好是的iframe,但是从体验的效果来看FormData和FileReader更好,因为这两者不用生成一个无用的DOM再删除,其中FormData最简单,而FileReader更加灵活。作者:版权归作者所有,转载请注明出处
","state":"published","sourceUrl":"","pageCommentsCount":0,"canComment":false,"snapshotUrl":"","slug":,"publishedTime":"T11:41:03+08:00","url":"/p/","title":"三种上传文件不刷新页面的方法讨论:iframe/FormData/FileReader","summary":"发请求有两种方式,一种是用ajax,另一种是用form提交,默认的form提交如果不做处理的话,会使页面重定向。以一个简单的demo做说明:html如下所示,请求的路径action为”upload”,其它的不做任何处理:&form method=\"POST\" action=\"upload\" enctype=\"multi…","reviewingCommentsCount":0,"meta":{"previous":null,"next":null},"commentPermission":"anyone","commentsCount":7,"likesCount":66}},"annotationDetail":null,"commentsCount":10,"likesCount":83,"FULLINFO":true}},"User":{"Dreawer":{"isFollowed":false,"name":"极乐君","headline":"知乎专栏 && /dreawer \n极乐小程序商店 && /\n极乐科技
&& /","avatarUrl":"/v2-8cb80f97b7d5f74c7a8997cbc4089e1f_s.jpg","isFollowing":false,"type":"people","slug":"Dreawer","bio":"极乐小程序商店(/)","hash":"4b43cf6f3d97f480b3ca2c13e5b12752","uid":563500,"isOrg":false,"description":"知乎专栏 && /dreawer \n极乐小程序商店 && /\n极乐科技
&& /","profileUrl":"/people/Dreawer","avatar":{"id":"v2-8cb80f97b7d5f74c7a8997cbc4089e1f","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false,"badge":{"identity":null,"bestAnswerer":null}}},"Comment":{},"favlists":{}},"me":{},"global":{},"columns":{"next":{},"dreawer":{"following":false,"canManage":false,"href":"/api/columns/dreawer","name":"极乐科技","creator":{"slug":"Dreawer"},"url":"/dreawer","slug":"dreawer","avatar":{"id":"v2-def9c21d9ca33ad157f4208","template":"/{id}_{size}.jpg"}}},"columnPosts":{},"columnSettings":{"colomnAuthor":[],"uploadAvatarDetails":"","contributeRequests":[],"contributeRequestsTotalCount":0,"inviteAuthor":""},"postComments":{},"postReviewComments":{"comments":[],"newComments":[],"hasMore":true},"favlistsByUser":{},"favlistRelations":{},"promotions":{},"switches":{"couldAddVideo":false},"draft":{"titleImage":"","titleImageSize":{},"isTitleImageFullScreen":false,"canTitleImageFullScreen":false,"title":"","titleImageUploading":false,"error":"","content":"","draftLoading":false,"globalLoading":false,"pendingVideo":{"resource":null,"error":null}},"drafts":{"draftsList":[],"next":{}},"config":{"userNotBindPhoneTipString":{}},"recommendPosts":{"articleRecommendations":[],"columnRecommendations":[]},"env":{"isAppView":false,"appViewConfig":{"content_padding_top":128,"content_padding_bottom":56,"content_padding_left":16,"content_padding_right":16,"title_font_size":22,"body_font_size":16,"is_dark_theme":false,"can_auto_load_image":true,"app_info":"OS=iOS"},"isApp":false},"sys":{}}

我要回帖

更多关于 vue脚手架有什么用 的文章

 

随机推荐