参考
单页面应用的优缺点
react是 单页应用 的跳转,也就是说,不管怎么进行页面跳转,整个网站只会加载一次html文件 ,这也就决定了不能使用<a>
标签进行页面跳转。(可参考“首页开发(3)” )
如果使用<a>
标签进行页面跳转,跳转时会发送HTTP请求。
而借助react-router-dom的Link组件实现跳转则不会,因此加载速度会快很多 ,借此也可提高性能。
优点:
良好的交互体验 :单页应用的内容的改变不需要重新加载整个页面 ,获取数据也是通过Ajax异步获取 ,没有页面之间的切换,就不会出现“白屏现象”,也不会出现假死并有“闪烁”现象,页面显示流畅。
良好的前后端工作分离模式 :后端不再负责模板渲染、输出页面工作,后端API通用化,即同一套后端程序代码,不用修改就可以用于Web界面、手机、平板等多种客户端 。
减轻服务器压力 :单页应用相对服务器压力小,服务器只用出数据就可以,不用管展示逻辑和页面合成 ,吞吐能力会提高几倍。
缺点:
首屏加载慢 :如果不对路由进行处理,在加载首页的时候,就会将所有组件全部加载,并向服务器请求数据,这必将拖慢加载速度;(可使用react-loadable模块 实现异步加载组件 )
不利于SEO :seo 本质是一个服务器向另一个服务器发起请求,解析请求内容。但一般来说搜索引擎是不会去执行请求到的js的。也就是说,搜索引擎的基础爬虫的原理就是抓取url,然后获取html源代码并解析。
如果一个单页应用,html在服务器端还没有渲染部分数据数据,在浏览器才渲染出数据,即搜索引擎请求到的html是模型页面而不是最终数据的渲染页面 。 这样就很不利于内容被搜索引擎搜索到。
轮播图是怎么实现的,后台数据抓取是怎么写的
html 重点就是包裹关系,注意动态添加小圆点(js中根据图片数量来决定圆点数量)
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 <div class ="slider-box" > <div class ="slider" id ="slider" > <ul class ="clearfix" > <li > <img width ="590" height ="470" src ="./img/1.jpg" alt ="" > </li > <li > <img width ="590" height ="470" src ="./img/2.jpg" alt ="" > </li > <li > <img width ="590" height ="470" src ="./img/3.jpg" alt ="" > </li > <li > <img width ="590" height ="470" src ="./img/4.jpg" alt ="" > </li > <li > <img width ="590" height ="470" src ="./img/5.jpg" alt ="" > </li > </ul > <div class ="left-box" > <span > < </span > </div > <div class ="right-box" > <span > > </span > </div > <div class ="index-box" > <ol > </ol > </div > </div > </div >
css 重点就是子绝父相(子为ul元素,父为显示在页面上的div),图片浮动为一排通过left移动,注意父元素使用overflow:hidden隐藏滚动条
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 .slider-box { margin-right : 10px ; } .slider-box .slider { position : relative; width : 590px ; height : 470px ; overflow : hidden; } .slider-box .slider ul { position : absolute; top : 0px ; left : -590px ; width : 500% ; height : 100% ; } .slider-box .slider ul li { float : left; } .slider-box .left-box { background : rgba (0 , 0 , 0 , 0.2 ); position : absolute; top : 45% ; left : 0px ; width : 50px ; height : 50px ; color : #fff ; display : flex; justify-content : center; align-items : center; cursor : pointer; } .slider-box .right-box { background : rgba (0 , 0 , 0 , 0.2 ); position : absolute; top : 45% ; right : 0px ; width : 50px ; height : 50px ; color : #fff ; display : flex; justify-content : center; align-items : center; cursor : pointer; } .slider-box .left-box , .slider-box .right-box span { font-size : 24px ; } .slider-box .index-box { position : absolute; bottom : 3% ; left : 10% ; } .slider-box .index-box > ol { height : 14px ; display : flex; justify-content : space-evenly; align-items : center; background : rgba (0 , 0 , 0 , 0.6 ); } .slider-box .index-box > ol > li { width : 10px ; height : 10px ; border-radius : 50% ; background : #fff ; list-style : none; cursor : pointer; } .slider-box .index-box > ol > li .active { background : #e1251b ; }
js中使用class
我们先通过ES6 class 创建一个class Slider ,这个类的作用就是获取轮播图的父盒子id然后给他内部的轮播图、小圆点、左右按钮添加js逻辑 。然后在需要使用轮播图的文件中使用Slider类来new一个对象,同时将Slider需要的DOM元素(轮播图的父盒子)的id传过去,相对应的逻辑就绑上去了 。
class的使用 :1 2 3 4 5 6 <script type ="text/javascript" > const slider = new Slider("#slider"); </script >
动态添加小圆点
DOM children
在slider类 中定义的函数initPoint :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 class Slider { constructor (id) { this .box = document.querySelector(id) this .picBox = this .box.querySelector("ul" ) this .indexBox = this .box.querySelector(".index-box" ) this .init () } init () { this .initPoint() } initPoint() { const num = this .picBox.children.length; let frg = document.createDocumentFragment(); for (let i = 0 ; i < num; i++) { let li = document.createElement("li" ) li.setAttribute("data-index" , i + 1 ) if (i == 0 ) li.className = "active" frg.appendChild(li) } this .indexBox.children[0 ].style.width = num * 10 * 2 + "px" ; this .indexBox.children[0 ].appendChild(frg) } }
实现图片可无限滚动
实现效果 :点击“向右”到最后一张图片时再次点击“向右”可回到第一张图(“向左”也是),非常流畅
思路:
在 绝对定位的ul 的头和尾各增加一个 辅助图 ,当我们的辅助图进入红框(显示的相对定位父元素)时
将辅助图与对应的真正的图的位置迅速替换
补充:
添加辅助图
Slider类中: 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 class Slider { constructor (id) { this .sliderWidth = this .box.clientWidth this .init () } init () { this .copyPic() } copyPic() { const first = this .picBox.firstElementChild.cloneNode(true ) const last = this .picBox.lastElementChild.cloneNode(true ) this .picBox.appendChild(first) this .picBox.insertBefore(last, this .picBox.firstElementChild) this .picBox.style.width = this .sliderWidth * this .picBox.children.length + "px" this .picBox.style.left = -1 * this .sliderWidth + "px" } }
(copyPic()后面几行代码可在css中设置)添加成功后修改css中ul这个绝对定位元素的left ,使其初始值向左一个图片的宽度,达到首先显示图1而不是辅助图5 的效果:1 2 3 4 5 6 7 8 9 10 11 .slider-box .slider ul { position : absolute; top : 0px ; left : -590px ; width : 700% ; height : 100% ; }
抵达辅助图时替换为真正的对应图
【可以看完下面的“预防多次连续点击出错”再来看这里会更加完整】
在Slider类的animate()函数中实现替换 (具体animate()实现功能见下“左右按钮”中):构造函数中定义 的:sliders轮播图数量 (注意是5不是7,此时还未添加辅助图)、整个轮播图的宽度sliderWidth (轮播图容器宽度clientWidth)
左右按钮
实现效果 :点击左右按钮可切换图片
思路:
leftRight()函数 中获取左右按钮,绑定点击事件,分别调用move(offset)函数,offset是位移
move(offset)函数 中改变圆点样式,调用animate(offset)函数
animate(offset)函数 中实现移动的动画效果
Slider类中: 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 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 class Slider { constructor (id) { this .sliderWidth = this .box.clientWidth this .init () } init () { this .leftRight() } move(offset) { this .animate(offset); const num = this .indexBox.children[0 ].children.length for (let i = 0 ; i < num; i++) { this .indexBox.children[0 ].children[i].className = "" } this .indexBox.children[0 ].children[this .index - 1 ].className = "active" } animate(offset) { const time = 1000 const rate = 100 let speed = offset / (time / rate) let goal = parseFloat(this .picBox.style.left) - offset let animate = setInterval(() => { if (this .picBox.style.left == goal || Math.abs(Math.abs(parseFloat(this .picBox.style.left)) - Math.abs(goal)) < Math.abs(speed)) { this .picBox.style.left == goal clearInterval(animate) } else { this .picBox.style.left = parseFloat(this .picBox.style.left) - speed + "px" ; } }, rate); } leftRight() { this .box.querySelector(".left-box" ).addEventListener("click" , () => { console.log("left" ) if (this .index - 1 < 1 ) { this .index = this .sliders } else { this .index-- } this .move(-this .sliderWidth) }) this .box.querySelector(".right-box" ).addEventListener("click" , () => { console.log("right" ) if (this .index + 1 > this .sliders) { this .index = 1 } else { this .index++ } this .move(this .sliderWidth) }) } }
点击圆点跳转对应图片
实现效果 :点击圆点跳转对应图片
思路:
在动态添加小圆点的initPoint()中调用move(offset)函数中绑定点击事件,复用上面左右按钮中的move(),offset是位移
move(offset)函数 中改变圆点样式,调用animate(offset)函数 (见上)
animate(offset)函数 中实现移动的动画效果 (见上)
Slider类中:
预防多次连续点击出错
实现效果 :快速连续点击左右按钮跳转时,限制其跳转一张图结束后才能跳转下一张图片(连续点击出现空白图时为溢出,列表溢出则重置)
思路:
在构造函数中添加属性animated,默认false,用来判断此时是否正在移动 。如是则需等待移动结束才能继续移动下一张。
在animate()中设置animated ,移动开始则为true,结束则为false
**优化leftRight()**,通过animated判断是否处于移动中,否才允许继续移动,当处于轮播图最左端(index=1,辅助图5)时,若还想继续向左则
Slider类中优化leftRight(): 1 2 3 4 5 6 7 class Slider { constructor (id ) { this .animated = false } }
自动播放
实现效果 :页面自动向右播放轮播图,鼠标移入时停止自动播放,移出时继续
思路:
构造函数中调用的init()中**添加play()**,自动调用
play()中,采用setInterval()每隔2秒点击一次“向右”按钮
给 轮播图盒子 绑定 onmouseenter/onmouseover事件,规定鼠标进入 轮播图盒子 时,清除定时器(停止向右)
给 轮播图盒子 绑定 onmouseleave/onmouseout事件,规定鼠标移出 轮播图盒子 时,继续执行定时器(继续向右)
补充: onmouseenter、onmouseleave可参考DOM事件
Slider类中: 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 class Slider { constructor (id) { this .init () } init () { this .play() } play() { this .auto = setInterval(() => { this .box.querySelector(".right-box" ).click() }, 2000 ); this .box.addEventListener("mouseenter" , () => { clearInterval(this .auto) }) this .box.addEventListener("mouseleave" , () => { this .auto = setInterval(() => { this .box.querySelector(".right-box" ).click() }, 2000 ); }) } }
js完整代码 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 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 class Slider { constructor (id) { this .box = document.querySelector(id) this .picBox = this .box.querySelector("ul" ) this .indexBox = this .box.querySelector(".index-box" ) this .sliderWidth = this .box.clientWidth this .sliders = this .picBox.children.length this .index = 1 this .animated = false this .auto = null this .init () } init () { this .initPoint() this .copyPic() this .leftRight() this .play() } initPoint() { const num = this .picBox.children.length; let frg = document.createDocumentFragment(); for (let i = 0 ; i < num; i++) { let li = document.createElement("li" ) li.setAttribute("data-index" , i + 1 ) if (i == 0 ) li.className = "active" frg.appendChild(li) } this .indexBox.children[0 ].style.width = num * 10 * 2 + "px" ; this .indexBox.children[0 ].appendChild(frg) this .indexBox.children[0 ].addEventListener("click" , (e) => { console.log("point" ) let pointIndex = (e.target).getAttribute("data-index" ) if (pointIndex == this .index || this .animated) { return } let offset = (pointIndex - this .index) * this .sliderWidth this .index = pointIndex this .move(offset) }) } copyPic() { const first = this .picBox.firstElementChild.cloneNode(true ) const last = this .picBox.lastElementChild.cloneNode(true ) this .picBox.appendChild(first) this .picBox.insertBefore(last, this .picBox.firstElementChild) this .picBox.style.width = this .sliderWidth * this .picBox.children.length + "px" this .picBox.style.left = -1 * this .sliderWidth + "px" } move(offset) { this .animate(offset); const num = this .indexBox.children[0 ].children.length for (let i = 0 ; i < num; i++) { this .indexBox.children[0 ].children[i].className = "" } this .indexBox.children[0 ].children[this .index - 1 ].className = "active" } animate(offset) { const time = 1000 const rate = 100 let speed = offset / (time / rate) let goal = parseFloat(this .picBox.style.left) - offset this .animated = true let animate = setInterval(() => { if (this .picBox.style.left == goal || Math.abs(Math.abs(parseFloat(this .picBox.style.left)) - Math.abs(goal)) < Math.abs(speed)) { this .picBox.style.left == goal clearInterval(animate) this .animated = false if (parseFloat(this .picBox.style.left) == 0 ) { this .picBox.style.left = -this .sliders * this .sliderWidth + "px" } else if (parseFloat(this .picBox.style.left) == -(this .sliders + 1 ) * this .sliderWidth) { this .picBox.style.left = -this .sliderWidth + "px" } } else { this .picBox.style.left = parseFloat(this .picBox.style.left) - speed + "px" ; } }, rate); } leftRight() { this .box.querySelector(".left-box" ).addEventListener("click" , () => { console.log("left" ) if (this .animated) { return } if (this .index - 1 < 1 ) { this .index = this .sliders } else { this .index-- } this .move(-this .sliderWidth) }) this .box.querySelector(".right-box" ).addEventListener("click" , () => { console.log("right" ) if (this .animated) { return } if (this .index + 1 > this .sliders) { this .index = 1 } else { this .index++ } this .move(this .sliderWidth) }) } play() { this .auto = setInterval(() => { this .box.querySelector(".right-box" ).click() }, 2000 ); this .box.addEventListener("mouseenter" , () => { clearInterval(this .auto) }) this .box.addEventListener("mouseleave" , () => { this .auto = setInterval(() => { this .box.querySelector(".right-box" ).click() }, 2000 ); }) } }
跨域的方法有哪些,常用的是?
怎么理解前端,怎么学习前端(途径)
“前端”是与用户直接交互的部分,包括你在浏览网页时接触的所有视觉内容。
好比你去快餐店吃饭。你在外面看到的店面装饰、点餐员、菜单、保温柜里面摆着的鸡腿汉堡这些给用户看的都是属于前端 ,相当于门户网站、用户输入、数据列表等。大概可以归类为网页开发的html、css 。
你跟点餐员点好餐之后,点餐员会录入你点的菜,向你确认可乐加不加冰、咖啡加不加糖然后让你支付。这些用户与前端的交互就交给了javascript 来完成。
然后点餐员向厨房喊一声“一个xx套餐,可乐不加冰”。这就向后端发送了一个ajax请求 。这些请求是异步的,你得等一会儿才能收到你点的餐。
而他们厨房里炸鸡的、炸薯条的、做汉堡的,弄好之后递给前台,这些就相当于后端 。他们负责把食物(数据)制作好,再返回给前端,前端再将收到的这些东西装盘(将收到的数据转为需要的格式)递给用户 。这基本就是一个前后端交互的逻辑了。
对后端语言的了解程度 常用的浏览器及内核
可参考这篇文章
目前最为主流浏览器有五大款,分别是IE、Firefox、Google Chrome、Safari、Opera 。
浏览器内核 又可以分成两部分:渲染引擎(layout engineer 或者 Rendering Engine)和 JS 引擎 。(JS 引擎越来越独立,内核倾向于只指渲染引擎 )
渲染引擎 负责取得网页的内容(HTML、XML、图像等等)、整理讯息(例如加入 CSS 等),以及计算网页的显示方式,然后会输出至显示器或打印机。浏览器的内核的不同对于网页的语法解释会有不同,所以渲染的效果也不相同。所有网页浏览器、电子邮件客户端以及其它需要编辑、显示网络内容的应用程序都需要内核。
JS 引擎 则是解析 Javascript 语言,执行 javascript 语言来实现网页的动态效果。
常见的浏览器内核 可以分这四种:Trident、Gecko、Blink、Webkit 。
我使用的是chrome浏览器+Blink内核
总结一下各常用浏览器所使用的内核 :
IE浏览器内核:Trident内核,也是俗称的IE内核;
Chrome浏览器内核:统称为Chromium内核或Chrome内核,以前是Webkit内核,现在是Blink内核;
Firefox浏览器内核:Gecko内核,俗称Firefox内核;
Safari浏览器内核:Webkit内核;
Opera浏览器内核:最初是自己的Presto内核,后来是Webkit,现在是Blink内核;
百度浏览器、世界之窗内核:IE内核;
前端性能优化的方法,举例
可参考前端基础-HTTP/HTML/浏览器(2) /JS 运行环境
让加载更快 :
减少资源体积:压缩代码
比如使用webpack在production环境下 进行打包时,就会自动压缩代码至1/3的大小 ,在浏览器上进行反解析再进行渲染。
减少请求次数 :
合并代码 :比如webpack中,在index.js中引入a.js、b.js,打包后只生成一个bundle.js ,这就是合并代码。(加载3次3kb的文件不如加载1次9kb快,这和网络请求有关系)
SSR服务器端渲染 :不需要通过ajax发送请求。
缓存 :原本需要发送多个请求的数据直接在缓存中获取 即可减少访问次数。
应该是尽可能命中强缓存 ,同时,能在更新版本的时候让客户端的缓存失效 。
更新版本的时候,顺便把静态资源的路径改了,这样,就相当于第一次访问这些资源,就不会存在问题了(webpack打包时会把静态资源的路径加上hash值)
较为合理的缓存方案:
HTML :使用协商缓存 。
CSS&JS&图片 :使用强缓存 ,文件命名带上hash值。
雪碧图
使用更快的网络:CDN
CDN是分区域 的,也就是使用CDN的时候上海和北京对同一个网站的IP地址是不同的。CDN会选择一个离用户最近的CDN边缘节点来响应用户的请求 ,这样海南移动用户的请求就不会千里迢迢跑到北京电信机房的服务器(假设源站部署在北京电信机房)上了
图片、js等静态资源 采用CDN是很快的,我们经常使用的bootstrap就是使用的CDN
让渲染更快 :
CSS放在head中,JS放在body最下面
原因 :
页面是边解析边渲染 的,如果把CSS写在HTML后,则先解析DOM树渲染在页面上,再生成CSSOM合成渲染树重新渲染在页面上 ,这样会有一个过程,用户可能会看到一个过程变化。所以在DOM树生成之前就先生成CSSOM会更好,这样当DOM树生成时就可直接和所有CSSOM进行合并,一步渲染完成。
页面是边解析边渲染 的,如果把JS写在body中间,虽然JS有异步的处理机制,但极端情况会出现前面已经渲染了一半就卡住的情况,渲染时间会拖长 。
尽早开始执行JS ,用DOMContentLoaded 触发
懒加载 (图片懒加载,上滑加载更多)
对DOM查询进行缓存 (DOM操作很耗性能)
合并的频繁DOM操作 ,一起插入DOM结构(利用createDocumentFragment() )
让渲染更流畅:
**节流throttle **:无论执行动作多快,都固定每隔100ms触发一次(时间自定义)。
防抖debounce :用户输入 结束或暂停时,才会触发相关事件 。际上是对定时器setTimeout 的使用,但我们通常将其封装为debounce函数来使用。
可以通过什么途径查看到网站目前使用的技术
google插件WhatRuns
检测的内容:
网页服务器
内容管理系统
网页字体
JavaScript 框架
Wordpress插件等
react的核心是什么
可参考知乎 、segmentfault ,总得来说就是组件+虚拟DOM
组件 :所有React代码基本上就是一个包含许多小组件在内的大组件 。
组件API :render、setState、constructor(一般会在其中初始化state并做一些函数this的绑定)、生命周期函数等
组件类型 :class组件、函数式组件、高阶组件
JSX :React的意思就是让我们把HTML和JS代码全都写在一起 。React是通过一种叫做JSX的语法扩展(X代表XML)来实现的 。
Props & State :React当中,属性就被称作props (properties的缩写),组件之间可以通过Props进行交互 。
正因如此,React当中的数据流是单向的 :数据只能从父组件传向子组件,反过来则不行。可是组件不可能只接受从父组件传来的数据(例如还有用户在input当中的输入),这时state就派上了用场 。
声明式渲染(虚拟DOM) :
初始化渲染:JSX语法 =》render函数渲染出虚拟DOM树 =》react将虚拟DOM渲染成真实的DOM
页面更新渲染:state改变 =》render函数重新渲染出虚拟DOM =》diff算法比对 当前虚拟DOM和需要更新的虚拟DOM=》重新渲染部分真实DOM
总结:
React的代码是由一个个的组件构成的。
组件采用了JSX语法扩展的写法。
数据流总是从父组件到子组件,除state可以通过调用方法修改之外。
组件包含一些特定方法和生命周期函数。
你也完全可以用函数声明只有render方法的无状态组件。
区分处理UI和数据逻辑的组件是一种很好的开发实践。
高阶组件函数可以传入一个组件并为其赋予更多功能。
写过项目最难的部分说一下