整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

2018春招前端面试:闯关记(精排精校) - 掘金技

2018春招前端面试:闯关记(精排精校) - 掘金技术征文

年末研发组解散失业, 选择回去学车了,也顺利拿到了驾照,最近回归大深圳,开始踏上漫漫的找工作之路。

拉勾上吊一百年不匹配, BOSS直聘日夜没反应。

题目范围涵盖我最近遇到的笔试题和面谈的(CSS/JS/HTTP/Node/Hybrid/Vue/NG/React)

emm,这里不列举哪些公司了, 若是你完整的阅读一遍,相信你有不少的收获,谢谢阅读

  • 问题截止日期(2018/3/23),我去面的创业,中大型皆有。
  • 期间死在各种一面/二面/三面/四面皆有之,也拿到部分和推掉部分offer,还有一些后续不清楚的

问题汇总,想到就写

Q: CSS 有哪些样式可以给子元素继承!

  • 可继承的:font-size,font-weight,line-height,color,cursor等
  • 不可继承的一般是会改变盒子模型的:display,margin、border、padding、height等

更加全面的可以到引擎找

Q: 行内元素有哪些?块级元素有哪些? 空(void)元素有那些?

  • 行内: input,span,a,img以及display:inline的元素
  • 块级: p,div,header,footer,aside,article,ul以及display:block这些
  • void: br,hr

Q: CSS3实现一个扇形

  • 思路跟画实体三角形一个道理,只不过多了一个圆角属性
<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <meta http-equiv="X-UA-Compatible" content="ie=edge">
 <title>扇形</title>
 <style>
 .sector {
 width: 0;
 height: 0;
 border-width: 50px;
 border-style: solid;
 border-color: #f00 transparent transparent;
 border-radius: 50px;
 }
 </style>
</head>
<body>
 <div class="sector"></div>
</body>
</html>
复制代码

Q: box-sizing常用的属性有哪些? 分别有啥作用?

box-sizing有两个值:content-box(W3C标准盒模型),border-box(怪异模型),

这个css 主要是改变盒子模型大小的计算形式

可能有人会问padding-box,这个之前只有 Firefox 标准实现了,目前50+的版本已经废除;

用一个栗子来距离,一个div的宽高分别100px,border为5px,padding为5px

 <style>
 .test {
 box-sizing: content-box;
 border: 5px solid #f00;
 padding:5px;
 width: 100px;
 height: 100px;
 }
 </style>
 <div class="test"></div>
<!--
content-box的计算公式会把宽高的定义指向 content,border和 padding 另外计算,
也就是说 content + padding + border=120px(盒子实际大小)
而border-box的计算公式是总的大小涵盖这三者, content 会缩小,来让给另外两者
content(80px) + padding(5*2px) + border(5*2px)=100px
-->
复制代码

Q: 清除浮动的方式有哪些?比较好的是哪一种?

常用的一般为三种.clearfix, clear:both,overflow:hidden;

比较好是 .clearfix,伪元素万金油版本,后两者有局限性..等会再扯

 .clearfix:after {
 visibility: hidden;
 display: block;
 font-size: 0;
 content: " ";
 clear: both;
 height: 0;
 }
<!--
为毛没有 zoom ,_height 这些,IE6,7这类需要 csshack 不再我们考虑之内了
.clearfix 还有另外一种写法,
-->
.clearfix:before, .clearfix:after {
	content:"";
	display:table;
}
.clearfix:after{
	clear:both;
	overflow:hidden;
}
.clearfix{
 zoom:1;
}
<!--
用display:table 是为了避免外边距margin重叠导致的margin塌陷,
内部元素默认会成为 table-cell 单元格的形式
-->
复制代码

clear:both:若是用在同一个容器内相邻元素上,那是贼好的,有时候在容器外就有些问题了, 比如相邻容器的包裹层元素塌陷

overflow:hidden:这种若是用在同个容器内,可以形成 BFC避免浮动造成的元素塌陷

Q: CSS 中transition和animate有何区别? animate 如何停留在最后一帧!

这种问题见仁见智,我的回答大体是这样的..待我捋捋.

transition一般用来做过渡的, 没时间轴的概念, 通过事件触发(一次),没中间状态(只有开始和结束)

而animate则是做动效,有时间轴的概念(帧可控),可以重复触发和有中间状态;

过渡的开销比动效小,前者一般用于交互居多,后者用于活动页居多;

至于如何让animate停留在最后一帧也好办,就它自身参数的一个值就可以了

animation-fill-mode: forwards;
<!--backwards则停留在首帧,both是轮流-->
复制代码

让我们来举个栗子,.自己新建一个 html 跑一下,.

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <meta http-equiv="X-UA-Compatible" content="ie=edge">
 <title>Box-sizing</title>
 <style>
 .test {
 box-sizing: border-box;
 border: 5px solid #f00;
 padding: 5px;
 width: 100px;
 height: 100px;
 position:absolute;
 /*
 简写的姿势排序
 @keyframes name : 动画名
 duration 持续时间
 timing-function 动画频率
 delay 延迟多久开始
 iteration-count 循环次数
 direction 动画方式,往返还是正向
 fill-mode 一般用来处理停留在某一帧
 play-state running 开始,paused 暂停 ,.
 更多的参数去查文档吧..我就不一一列举了
 */
 animation: moveChangeColor ease-in 2.5s 1 forwards running;
 }
 @keyframes moveChangeColor {
 from {
 top:0%;
 left:5%;
 background-color:#f00
 }
 to{
 top:0%;
 left:50%;
 background-color:#ced;
 }
 }
 </style>
</head>
<body>
 <div class="test"></div>
</body>
</html>
复制代码

Q: 块级元素水平垂直居中的方法

我们要考虑两种情况,定宽高和不定宽高的;

方案 N 多种,我记得我很早写过这类的笔记

传送门:网页元素居中攻略记

Q: 说说样式权重的优先级;

!important > 行内样式 > id > class > tag

样式权重可以叠加, 比如 id>class

Q: 对HTML语义化的理解

简言之:就是不滥用标签(比如 DIV)/随意嵌套(比如 span>div) ,

类的命名要合理, 利于浏览器解析乃至引擎收录,也利于团队协作和维护

Q: JS有几种数据类型,其中基本数据类型有哪些!

七种数据类型

  • Boolean
  • Null
  • Undefined
  • Number
  • String
  • Symbol (ECMAScript 6 新定义)
  • Object

(ES6之前)其中5种为基本类型:string,number,boolean,null,undefined,

ES6出来的Symbol也是原始数据类型 ,表示独一无二的值

Object 为引用类型(范围挺大),也包括数组、函数,

Q: null和undefined的差异

大体说一下,想要知其所以然请引擎搜索

相同点:

  • 在 if判断语句中,值都默认为 false
  • 大体上两者都是代表,具体看差异

差异:

  • null转为数字类型值为0,而undefined转为数字类型为 NaN(Not a Number)
  • undefined是代表调用一个值而该值却没有赋值,这时候默认则为undefined
  • null是一个很特殊的对象,最为常见的一个用法就是作为参数传入(说明该参数不是对象)
  • 设置为null的变量或者对象会被内存收集器回收

Q: JS 的DOM 操作(Node节点获取及增删查改);

  • 获取(太多了,有document.getElementById/ClassName/Name/TagName 等,或者 querySelector)
// example
// get Node
var element=document.querySelector('#test');
// 追加
element.appendChild(Node);
// 删除
element.removeChild(Node);
// 查找
element.nextSibling // 获取元素之后的兄弟节点 , 会拿到注释文本,空白符这些
element.nextElementSibling // 等同, 获取标签(不会拿到注释文本这些)
element.previousSibling // 和上面同理,往前找兄弟节点
element.previousElementSibling
// 改动,比如 属性这些
element.setAttribute(name, value); // 增加属性
element.removeAttribute(attrName); //删除属性
// 来一个简易的练习题,随便一个网页追加插入一块DOM(非覆盖:不能 innerHTML);
/*
<div id="test">
 <span>Hello, World</span>
</div>
*/
// 以上面的例子为例
var test=document.createElement('div'); // 创建一个块级元素
test.setAttribute("id","test"); // 设置其id 属性
var span=document.createElement('span'); // 创建一个 span
span.innerText="Hello,world"; // 插入 span 的文本内容
test.appendChild(span); // 组合节点
element.appendChild(test); //追加到某个节点区域
复制代码

Q:Javascript中,有一个函数,执行时对象查找时,永远不会去查找原型,这个函数是?

hasOwnProperty,这个更多的是用来区分自身属性和原型链上的属性。

Q: 给一个 DOM添加捕获和冒泡的两种写法的事件点击,谁先执行?

分情况分析:

  • 有拿到节点的,优先捕获,没有才往上冒泡寻找
  • 若是通过node.addEventListener('event',callback,bubble or capture); 谁先调用谁先执行

stackoverflow 有相关的探讨:

  • Event listeners registered for capturing phase not triggered before bubbling - why?

Q: 谈谈你对ajax 的理解,以及用原生 JS 实现有哪些要点需要注意;

ajax全称是异步 javascript 和 XML,用来和服务端进行数据交互的,让无刷新替换页面数据成了可能;

至于有哪些要要点,来一个简短的ajax请求

var xhr=new XMLHttpRequest(); // 声明一个请求对象
xhr.onreadystatechange=function(){
 if(xhr.readyState===4){ // readyState 4 代表已向服务器发送请求
 if(xhr.status===OK){ // // status 200 代表服务器返回成功
 console.log(xhr.responseText); // 这是返回的文本
 } else{
 console.log("Error: "+ xhr.status); // 连接失败的时候抛出错误
 }
 }
}
xhr.open('GET', 'xxxx');
// 如何设置请求头? xhr.setRequestHeader(header, value);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(null); // get方法 send null(亦或者不传,则直接是传递 header) ,post 的 send 则是传递值
复制代码

更为详细的可以阅读此处;

  • <<ajax 概念 by 阮一峰>>
  • <<XMLHttpRequest2 用法指南>>

Q: JS 实现一个闭包函数,每次调用都自增1;

这里主要考察了闭包,函数表达式以及 IIFE(立即执行表达式)

var add=(function() {
 // 声明一变量,由于下面 return所以变量只会声明一次
 var count=0;
 return function() {
 return console.log(count++);
 };
})();
add(); // 0
add(); // 1
add(); // 2
复制代码

Q: ['1','2','3'].map(parseInt) 输出什么,为什么?

['1','2','3'].map(parseInt); // [1,NaN,NaN]
// 刨析
// map有三个参数:数组元素,元素索引,原数组本身
// parseInt有两个参数,元素本身以及进制
// 理清了这两个就好办了,
// ['1','2','3'].map(parseInt); 等于如下
['1','2','3'].map(function(item,index,array){
 return parseInt(item,index); // 是不是一目了然
});
// parseInt("1",0);=> 1
// parseInt("2",1);=> NaN
// parseInt("3",2);=> NaN
复制代码

Q:如何实现浏览器内多个标签页之间的通信?

WebSocket、localstorge、cookies都可以。

要考虑浏览器无痕模式的话用WebSocket会更好,不然功能基本失效或者报错。

Q:webSocket如何兼容低浏览器?

最常见的就是轮询XHR

Q: 什么是window对象? 什么是document对象?

window对象是指浏览器打开的窗口。

document对象是HTML 文档对象的一个只读引用,window对象的一个属性。

Q: 对数组 ['2018-03-05', '2013-06-12','2019-03-12','2018-03-05','2014-02-22'] 去重且排序

我这里用的是结合 ES6的,代码量很短

//很好理解, Set 具有值唯一性(但不是所有值,等会我抛出我的另外一篇文章)
// 结合,解构,可以把可迭代(比如 arguments/nodelist 等)的转为数组
// sort 里面传入 两个值比较,返回-1和1是因为1代表这个数大排后(相对),-1代表小(相对),0为相等
let arr=[,new Set(['2018-03-05', '2013-06-12','2019-03-12','2018-03-05','2014-02-22'])].sort(function(a,b){
 return a<b ? -1:1; // 这里返回的是升序的,降序改下返回值就好了.所以是相对
})
// ["2013-06-12", "2014-02-22", "2018-03-05", "2019-03-12"]
复制代码

对于数组去重的,有兴趣的可以看下我这篇水文:

  • JS数组去重!!!一篇不怎么靠谱的"深度"水文

Q: 对数组[1,2,3,4,5,'6',7,'8','a','b','z']进行乱序

// 我们依旧可以用上面的 sort 的原理实现乱序
let tempArr=[1,2,3,4,5,'6',7,'8','a','b','z'].sort(function(){
 return Math.random() > 0.5 ? -1 : 1;
})
// 因为里面有随机数,所以答案没有标准答案,我这边跑了一次是输出这个
//["6", "z", 3, "b", 5, 2, 7, "8", "a", 1, 4]
复制代码

上面和这道题逗涉及到数组顺序的问题,想了解下为什么 a-b,a>b这类可以更改排序

可以看看知乎对于这块的探讨: 传送门:javascript排序return a-b?

Q: 求[1, 10, 11, -1,'-5',12, 13, 14, 15, 2, 3, 4, 7, 8, 9]内最大值与最小值之差

// 来一个很粗糙的版本,只当传入是数组且可以隐性转为数字的
function MaxMinPlus(arr) {
 // 返回最大值与最小值之差
 return Array.isArray(arr) ? Math.max.apply(Math, arr) - Math.min.apply(Math, arr) : console.log('传入的不是数组亦或者未能解决的错误')
}
// 结果是 20
// 若是要完善的话,要考虑传入的是非数组,
//传入字符串的时候要判断,然后切割为数组..
// 都要考虑进去代码量不短
复制代码

Q: 请给Array实现一个方法,去重后返回重复的字符(新数组)

 var testArr=[1,6,8,3,7,9,2,7,2,4,4,3,3,1,5,3];
 Array.prototype.extraChar=function(){
 var cacheExtraChar=[]; // 缓存重复出现的字符
 var that=this; // 缓存 this;
 this.map(function(item,index){
 // 怎么理解这段代码呢?
 // 就是向前往后查找一遍和从后往前查找一遍,不等就是没有重复
 // 为什么还要判断一遍缓存,是过滤缓存数组内多次写入
 (that.indexOf(item) !==that.lastIndexOf(item)) && cacheExtraChar.indexOf(item)===-1 ? cacheExtraChar.push(item) : -1;
 });
 return cacheExtraChar;
 }
testArr.extraChar(); // [1, 3, 7, 2, 4]
// 若是还需要排序就再排序下
[1,6,8,3,7,9,2,7,2,4,4,3,3,1,5,3]
.extraChar()
.sort(function(a,b){return a-b}) // [1, 2, 3, 4, 7]
复制代码

Q: 一个数组中 par中存放了多个人员的信息,每个人员的信息由 name 和 age 构成({name:'张三',age:15}).请用 JS 实现年龄从小到大的排序;

var par=[{age:5,name:'张三'},{age:3,name:'李四'},{age:15,name:'王五'},{age:1,name:'随便'}]
var parSort=par.sort(function(a,b){
 return a.age - b.age;
})
复制代码

Q: 判断一个回文字符串和同字母异序字符串

回文字符串

就是正序倒序都是一样的;

同字母异序字符串

字符串都一样,但是位置可能不一定一样,比如abcefd和dceabf=>return true

后者的思路就是用排序把异序扭正

普通版

// 回文判断 , 比如用 abcba
var isPalindromes=function(params){
 params=params.toString().toLowerCase()
 return params===params.split('').reverse().join('');
}
// 同字母异序判定,比如`abcefd`和`dceabf`
var isAnagram=function(str1, str2) {
 str1=str1.toString().toLowerCase();
 str2=str2.toString().toLowerCase();
 return str1.split('').sort().join('')===str2.split('').sort().join('')
}
复制代码

进阶版:多一些特殊字符

若是我们要去除所有非字母数字的字符,则需要用到正则

// 进阶版: isPalindromes('abc_ &b #@a')
var isPalindromes=function(params){
 // 传入参数先转为字符串且全部转为小写,最后去除多余字符比较
 params=params.toString().toLowerCase().replace(/[\W_\s]/g,'');
 console.log(params)
 return params===params.split('').reverse().join('');
}
// 进阶版同字母异序: isAnagram('ab *&cef#d','!d@ce^abf')
var isAnagram=function(str1, str2) {
 str1=str1.toString().toLowerCase().replace(/[\W_\s]/g,'');
 str2=str2.toString().toLowerCase().replace(/[\W_\s]/g,'');
 return str1.split('').sort().join('')===str2.split('').sort().join('')
}
复制代码

Q: JS 实现String.trim()方法;

// 原生是有 trim()方法的.我们要模拟一个;
String.prototype.emuTrim=function(){
 // 这条正则很好理解,就是把头部尾部多余的空格字符去除
 return this.replace(/(^\s*)|(\s*$)/g,'');
}
' fsaf fsdaf f safl lllll '.emuTrim(); //"fsaf fsdaf f safl lllll"
复制代码

Q: JS 实现函数运行一秒后打印输出0-9;给定如下代码

for(var i=0;i<10;i++){
 // TODO
}
复制代码
  • 解法
// 这道题涉及到作用域
for(var i=0;i<10;i++){
 setTimeout((function(i){
 return function(){
 console.log(i);
 }
 })(i),1000);
}
复制代码

若是用到 ES6,那简直不能再简便了

for(let i=0;i<10;i++){
 setTimeout(function(){
 console.log(i);
 },1000);
}
复制代码

Q: 实现对一个数组或者对象的浅拷贝和"深度"拷贝

浅拷贝就是把属于源对象的值都复制一遍到新的对象,不会开辟两者独立的内存区域;

深度拷贝则是完完全全两个独立的内存区域,互不干扰

  • 浅拷贝
// 这个 ES5的
function shallowClone(sourceObj) {
 // 先判断传入的是否为对象类型
 if (!sourceObj || typeof sourceObj !=='object') {
 console.log('您传入的不是对象!!')
 }
 // 判断传入的 Obj是类型,然后给予对应的赋值
 var targetObj=sourceObj.constructor===Array ? [] : {};
 // 遍历所有 key
 for (var keys in sourceObj) {
 // 判断所有属于自身原型链上的 key,而非继承(上游 )那些
 if (sourceObj.hasOwnProperty(keys)) {
 // 一一复制过来
 targetObj[keys]=sourceObj[keys];
 }
 }
 return targetObj;
}
 // ES6 可以用 Object.assign(targeObj, source1,source2,source3) 来实现对象浅拷贝
复制代码
  • 深度拷贝
// 就是把需要赋值的类型转为基本类型(字符串这些)而非引用类型来实现
// JOSN对象中的stringify可以把一个js对象序列化为一个JSON字符串,parse可以把JSON字符串反序列化为一个js对象
var deepClone=function(sourceObj) {
 if (!sourceObj || typeof sourceObj !=='object') {
 console.log('您传入的不是对象!!');
 return;
 }
 // 转->解析->返回一步到位
 return window.JSON
 ? JSON.parse(JSON.stringify(sourceObj))
 : console.log('您的浏览器不支持 JSON API');
};
复制代码
  • 深拷贝的考虑点实际上要复杂的多,详情看看知乎怎么说

Q: this对象的理解

简言之:谁调用指向谁,运行时的上下文确定,而非定义的时候就确定;

强行绑定 this的话,可以用 call,apply,bind,箭头函数来来改变this的指向

这类的文章太多,自行搜索吧。

Q: 看到你说到 bind,能用 JS简单的模拟个么?

Function.prototype.emulateBind=function (context) {
 var self=this;
 return function () {
 return self.apply(context);
 }
}
复制代码

这个实现很粗糙,更为详细全面,考虑周全的(比如参数的处理什么的),自行谷歌.

Q:JS 的作用域是什么?有什么特别之处么?

作用域就是有它自身的上下文区域(比如函数内),内部会有变量声明提升,函数声明提升这些;

函数声明提升优于变量声明提升..

作用域有全局作用域和块级作用域(局部,比如用 let 或者单纯花括号的);

作用域会影响this的指向

坐等补充,我回答的时候,面试大佬只是 嗯..恩,恩,也不知道具体如何

Q: 怎么解决跨域问题,有哪些方法,

我一般用这三种,cors,nginx反向代理,jsonp

  • jsonp : 单纯的 get 一些数据,局限性很大,就是利用script标签的src属性来实现跨域。
  • nginx 反向代理: 主要就是用了nginx.conf内的proxy_pass http://xxx.xxx.xxx,会把所有请求代理到那个域名,有利也有弊吧..
  • cors的话,可控性较强,需要前后端都设置,兼容性 IE10+ ,比如
  • Access-Control-Allow-Origin: foo.example // 子域乃至整个域名或所有域名是否允许访问
  • Access-Control-Allow-Methods: POST, GET, OPTIONS // 允许那些行为方法
  • Access-Control-Allow-Headers: X-PINGOTHER, Content-Type // 允许的头部字段
  • Access-Control-Max-Age: 86400 // 有效期

Q: 对于想携带一些鉴权信息跨域如何走起?比如cookie!

需要配置下 header Access-Control-Allow-Credentials:true ,具体用法看下面的nginxdemo

当然cros的配置不仅仅这些,还有其他一些,具体引擎吧,.

若是我们要用 nginx或者 express 配置cors应该怎么搞起? 来个简易版本的

  • nginx
location / {
 # 检查域名后缀
 add_header Access-Control-Allow-Origin xx.xx.com;
 add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
 add_header Access-Control-Allow-Credentials true;
 add_header Access-Control-Allow-Headers DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type;
 add_header Access-Control-Max-Age 86400;
}
复制代码
  • express, 当然这货也有一些别人封装好的 cors中间件,操作性更强,
let express=require('express');
let app=express();
//设置所有请求的头部
app.all('*', (req, res, next)=> {
 res.header("Access-Control-Allow-Origin", "xx.xx.com");
 res.header("Access-Control-Allow-Headers", "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type");
 res.header("Access-Control-Allow-Credentials","true")
 res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
 next();
});
复制代码

有些还会跟你死磕,除了这些还有其他姿势么,我说了一个HTML5的postMessage,

因为真心没用过,只是以前查阅的时候了解了下,只能大体点下

这货用于iframe 传递消息居多, 大体有这么两步步

  • window打开一个实例,传递一个消息到一个x域名
  • x 域名下监听message事件,获取传递的消息

这货的兼容性没那么好,而且没考虑周全下容易遭受 CSRF 攻击

Q: 对于XSS 和 CSRF 如何防范

这里就不说概念性的东西了

  • XSS的防范
  • 我能想到的就是转义<>这些造成代码直接运行的的标签..轮询或者正则替换
  • 而面试官说这种的效率最低下,我回来仔细找了找相关资料好像没有更优方案,有的留言,
  • 若是有用到 cookie,设置为http-only,避免客户端的篡改
  • CSRF的防范一般这几种
  • 验证码,用户体验虽然不好,,但是很多场合下可以防范大多数攻击
  • 验证 HTTP Referer 字段,判断请求来源
  • token加密解密,这种是目前很常用的手段了,

任何防范都有代价的,比如验证码造成的体验不好,token滥用造成的性能问题,轮询替换造成的响应时间等

Q: 描述下cookie,sessionStorage,localStorage的差异..

  • cookie : 大小4KB 左右,跟随请求(请求头),会占用带宽资源,但是若是用来判断用户是否在线这些挺方便
  • sessionStorage和localStorage大同小异,大小看浏览器支持,一般为5MB,数据只保留在本地,不参与服务端交互.
  • sessionStorage的生存周期只限于会话中,关闭了储存的数据就没了.
  • localStorage则保留在本地,没有人为清除会一直保留

Q: javascript的原型链你怎么理解?

原型链算是 JS 内一种独有的机制,

所有对象都有一个内置[[proto]]指向创建它的原型对象(prototype)

原型链的基本用来实现继承用的

Q: javascript里面的继承怎么实现,如何避免原型链上面的对象共享

我在写的时候,用了两种,一个是 ES5和 ES6的方案

  • ES5:寄生组合式继承:通过借用构造函数来继承属性和原型链来实现子继承父。
 function ParentClass(name) {
 this.name=name;
 }
 ParentClass.prototype.sayHello=function () {
 console.log("I'm parent!" + this.name);
 }
 function SubClass(name, age) {
 //若是要多个参数可以用apply 结合 ,解构
 ParentClass.call(this, name);
 this.age=age;
 }
 SubClass.prototype=Object.create(ParentClass.prototype);
 SubClass.prototype.constructor=SubClass;
 SubClass.prototype.sayChildHello=function (name) {
 console.log("I'm child " + this.name)
 }
 let testA=new SubClass('CRPER')
 // Object.create()的polyfill
 /*
 function pureObject(o){
 //定义了一个临时构造函数
 function F() {}
 //将这个临时构造函数的原型指向了传入进来的对象。
 F.prototype=obj;
 //返回这个构造函数的一个实例。该实例拥有obj的所有属性和方法。
 //因为该实例的原型是obj对象。
 return new F();
 }
 */
复制代码
  • ES6: 其实就是ES5的语法糖,不过可读性很强..
 class ParentClass {
 constructor(name) {
 this.name=name;
 }
 sayHello() {
 console.log("I'm parent!" + this.name);
 }
 }
 class SubClass extends ParentClass {
 constructor(name) {
 super(name);
 }
 sayChildHello() {
 console.log("I'm child " + this.name)
 }
 // 重新声明父类同名方法会覆写,ES5的话就是直接操作自己的原型链上
 sayHello(){
 console.log("override parent method !,I'm sayHello Method")
 }
 }
 let testA=new SubClass('CRPER')
复制代码

Q: ES6+你熟悉么,用过哪些特性?

  • 箭头函数
  • 类及引入导出和继承( class/import/export/extends)
  • 字符串模板
  • Promise
  • let,const
  • async/await
  • 默认参数/参数或变量解构装饰器
  • Array.inclueds/String.padStart|String.padEnd/Object.assign

Q: let 和 const 有啥差异?

  • let 会产生块级作用域,不会造成变量提升,无法重新声明(但可以重新赋值);
  • const
  • 是常量,若是基本数据类型,具有不变性(无法重新赋值改动)
  • 引用值可以调整内部值(可能设计的时候没有考虑周全!

Q: async和await的用途?

  • 让 promise 的异步变成同步运行成了可能,await 可以等到 promise 执行完毕

Q: 箭头函数的this指向谁?

肯定很多小伙伴会说指向局部方法内!!答案是错误的,

箭头函数所改变的并非把 this 局部化,而是完全不把 this 绑定到里面去;

就是 this 是取自外部的上下级作用域(但是又不是常规 function的语法糖)..

因为箭头函数里并不支持 var self=this 或者 .bind(this) 这样的写法。

Q: 问的时候你用过静态方法,静态属性,私有变量么?

静态方法是ES6之后才有这么个玩意,有这么些特点

  • 方法不能给 this引用,可以给类直接引用
  • 静态不可以给实例调用,比如 let a=new ParentClass=> a.sayHello() 会抛出异常
  • 父类静态方法,子类非static方法没法覆盖父类
  • 静态方法可以给子类继承
  • 静态属性可以继承也可以被修改

看下面的代码..

 class ParentClass {
 constructor(name) {
 this.name=name;
 }
 static sayHello() {
 console.log("I'm parent!" + this.name);
 }
 static testFunc(){
 console.log('emm,Parent test static Func')
 }
 }
 class SubClass extends ParentClass {
 constructor(name) {
 super(name);
 }
 sayChildHello() {
 console.log("I'm child " + this.name)
 }
 static sayHello() {
 console.log("override parent method !,I'm sayHello Method")
 }
 static testFunc2() {
 console.log(super.testFunc() + 'fsdafasdf');
 }
 }
 ParentClass.sayHello(); // success print
 let a=new ParentClass('test');
 a.sayHello() // throw error
 SubClass.sayHello(); // 同名 static 可以继承且覆盖
 SubClass.testFunc2(); // 可以继承
 let testA=new SubClass('CRPER');
复制代码

私有变量可以用WeakMap模拟,也能用语义化的下划线,亦或者symbol,

所以回来只是找了下相关的资料,发现有一个比较好的模拟方案,就是WeakMap;

WeakMap可以避免内存泄露,当没有被值引用的时候会自动给内存寄存器回收了.

const _=new WeakMap(); // 实例化,value 必须为对象,有 delete,get,has,set四个方法,看名字都知道了
class TestWeakMap {
 constructor(id, barcode) {
 _.set(this, { id,barcode });
 }
 testFunc() {
 let { id,barcode }=_.get(this); // 获取对应的值
 return { id,barcode };
 }
}
复制代码

当然你也可以用Symbol来实现一个私有变量,这也是一个好法子

Q: 谈谈你对 Promise 的理解? 和 ajax 有关系么?

Promise和ajax没有半毛钱直接关系.promise只是为了解决"回调地狱"而诞生的;

平时结合 ajax是为了更好的梳理和控制流程,这里我们简单梳理下..

Promise有三种状态,Pending/resolve()/reject();

一些需要注意的小点,如下

  • 在 Pending 转为另外两种之一的状态时候,状态不可在改变..
  • Promise的 then为异步.而(new Promise())构造函数内为同步
  • Promise的catch不能捕获任意情况的错误(比如 then 里面的setTimout内手动抛出一个Error)
  • Promise的then返回Promise.reject()会中断链式调用
  • Promise的 resolve若是传入值而非函数,会发生值穿透的现象
  • Promise的catch还是then,return的都是一个新的 Promise(在 Promise 没有被中断的情况下)

Promise 还有一些自带的方法,比如race,all,前者有任一一个解析完毕就返回,后者所有解析完毕返回,

实现一个延时的 promise 函数, 可以用async和await

const delay=(time)=> new Promise((resolve,reject)=>{
 setTimeout(resolve,time)
})
// test
let testRun=async function(){
 console.log(1);
 await delay(2000);
 console.log('我两秒后才触发',3)
}
// 1=> Promise=> 3
复制代码

以下这段代码的运行结果是什么?

var test=new Promise((resolve,reject)=>{
 resolve();
});
test
 .then(data=> {
 // promise start
 console.log('promise first then : ', data);
 return Promise.resolve(1); // p1
 })
 .then(data=> {
 // promise p1
 console.log('get parent(p1) resolve data : ', data);
 return Promise.reject(new Error('哎呀,中断了,你能奈我何!')); // p2
 })
 .then(data=> {
 // promise p2
 console.log('result of p2: ', data);
 return Promise.resolve(3); // p3
 })
 .catch(err=> {
 console.log('err: ', err);
 return false;
 });
// promise first then : undefined
// get parent(p1) resolve data : 1
// err: Error: 哎呀,中断了,你能奈我何!
// 这里在 then 返回 Promise.reject()的时候已经中断了链式调用.直接给 catch捕获到
复制代码

别急,假如你不管有没有捕获到错误,最后再执行一个回调函数如何实现?

这里说的就是类似try..catch..finally,给Promise实现一个 finally;

// finally比较好加,按照现在社区的讨论,finally的特点如下:
// url : https://www.v2ex.com/t/205715
//1. 不接收任何参数,原来的value或者Error在finally里是收不到的
//2. 处理后不影响原Promise的状态,该reject还是reject,该resolve还是resolve
//3. 不影响Promise向后传递的传,resolve状态还是传递原来的value,reject状态还是传递原来的Error
Promise.prototype.finally=function (callback) {
 let P=this.constructor; // 这里拿到的是 Promise 的构造函数
 //不管前面的 Promise 是fulfilled还是rejected,都会执行回调函数callback。
 return this.then(
 value=> P.resolve(callback()).then(()=> value),
 reason=> P.resolve(callback()).then(()=> { throw reason })
 );
};
// 用法很简单,就是可以传入一个回调函数..
// https://developers.google.com/web/updates/2017/10/promise-finally
// 这个 url 中说了 node 及 chrome 的哪些版本已经实现了 finally 及用法
// ES 2018已经把 finally 追加到 promise 的原型链中..
复制代码
  • <<Promise 必知必会(十道题)>>: 有助于你更加深刻的了解 promise 的运行情况
  • 关于 Promise 的 9 个提示
  • 更多的Promise 详情可以参考<<JavaScript Promise迷你书(中文版)>>;

Q: 谈谈你对 TCP 的理解;

Q: TCP 是在哪个OSI 的哪个层!通讯过程是全双工还是半双工(单工)?

A: 传输层,全双工

Q: TCP的通讯的过程是怎么样的!

A: 整个过程是三次握手,四次挥手..

Q: 你说的没错,说说整个过程如何?

A: 举个栗子,我把 TCP 比做两个人用对讲机沟通(大白话)..三次握手就是.A1(吼叫方,客户端)想要呼叫 A2(控制室的某某,服务端)..

A1对着对讲机说"over over ,听到请回答"(第一次,请求应答) ,

A2收到回应"收到收到,你说"(第二次,确认应答)

A1开始巴拉巴拉个不停而 A2没拒绝(第三次,通讯建立)

而四次挥手则是两者确认互相倾述完毕的过程..

A1说:"控制室,报告完毕了"(第一次挥手)

A2说:"知道了,那么你废话说完就好好听我指挥,.巴拉巴拉.."(第二次挥手)

A1此时等待控制室说完毕,而控制室等回应(第三次挥手)

等到 A1回馈控制室确认都知道完毕了..(第四次挥手),

以上都是瞎掰,可能有些地方描述不当,笑笑就好了

TCP没有百分百建立成功的,会造成链接失败的情况有很多..

比如长时间没应答(A1吼了半天没有反应或者 A2应答了而 A1不再鸟它)..亦或者丢包(对讲机也没了);

TCP 协议相关的文章网上很多,若是要更加全面的了解该协议请自行引擎..

我建议阅读<<TCP-IP详解卷1~卷3>>,这个是网络圣经,很厚,我只看了一丢丢..

Q: TCP 你了解了,那么 OSI 七层协议和五层网络架构应该知道吧?

对于这类的问题我也只能大体点了下,毕竟不是专攻网络这块的,

OSI 七层涵盖:物理层,数据链路层,网络层,传输层,会话层,表示层,应用层;

五层模型就是"会话,表示,应用层"同为一层;

Q: DNS 的大体的执行流程了解么,属于哪个层级?工作在哪个层级?

DNS 属于应用层协议, 至于TCP/UDP哪一层上面跑,看情况 , 大体的执行流程是这样的;

DNS 默认端口是53,走 UDP

  1. 优先读取浏览器缓存
  2. 其次系统的缓存
  3. 都没有的情况下,找本地hosts文件(比如你写了映射关系优先寻找)
  4. 再没有的情况找最近的域名解析服务器
  5. 再没有则扩大访问,最终找到根服务器,还是没有就失败了..

DNS 的解析的几个记录类型需要了解:

  • A: 域名直接到 IP
  • CNAME: 可以多个域名映射到一个主机,类似在 Github Page就用 CNAME 指向
  • MX: 邮件交换记录,用的不多,一般搭建邮件服务器才会用到
  • NS: 解析服务记录,可以设置权重,指定谁解析
  • TTL: 就是生存时间(也叫缓存时间),一般的域名解析商都有默认值,也可以人为设置
  • TXT: 一般指某个主机名或域名的说明

回来我找下相关的资料,有兴趣的可以深入了解下,传送门如下:

  • 梳理Linux下OSI七层网络与TCP/IP五层网络架构
  • TCP/IP(六)应用层(DNS和HTTP协议)
  • DNS域名解析解剖

Q: HTTP 和 HTTPS 有何差异? 听说过 SPDY 么?

我只是粗浅的回答了下,

HTTP相对于 HTTPS来说,速度较快且开销较小(没有 SSL/TSL) 对接,默认是80端口;

HTTP容易遭受域名劫持,而HTTPS相对来说就较为安全(加密),默认端口为443。

HTTP是明文跑在 TCP 上.而HTTPS跑在SSL/TLS应用层之下,TCP上的

Q: 那么 HTTPS中的TLS/SSL是如何保护数据的,

一般有两种形式,非对称加密,生成公钥和私钥,私钥丢服务器,公钥每次请求去比对验证;

更严谨的采用 CA(Certificate Authority),给密钥签名,.

Q: 你说到对称加密和非对称加密,能说说整个流程如何运转的么(HTTPS)

  • 对称加密:
  • 双方都有同样的密钥,每次通讯都要生成一个唯一密钥,速度很快
  • 安全性较低且密钥增长的数量极快
  • 非对称加密(一般用 RSA)
  • 安全性很高,对资源消耗很大(CPU),目前主流的加密算法(基本用于交换密钥或签名,而非所有通讯内容)
  • CA(数字签名):
  • 这个是为了防止中间人给偷换了造成数据被窃取而诞生的
  • 用一些权威机构颁布的算法来签名,权威机构做中间人,通讯过程都会跟机构核对一遍

懂得真心不多,回来找了下相关资料,有兴趣可以点击看看;

  • 深入揭秘HTTPS安全问题&连接建立全过程
  • 深入理解 https 通信加密过程:口语化 " : 看了上面那篇文章来看下面,会清晰很多

Q: SPDY 听说过么.什么来的?

谷歌推行一种协议(HTTP 之下SSL之上[TCP]),可以算是HTTP2的前身,有这么些优点

  • 压缩数据(HEADER)
  • 多路复用
  • 优先级(可以给请求设置优先级)

而这些优点基本 HTTP2也继承下来了..

Q: 你对 HTTP 的状态吗了解多少,

这里列举一丢丢常见的..

  • 1XX: 一般用来判断协议更换或者确认服务端收到请求这些
  • 100: 服务端收到部分请求,若是没有拒绝的情况下可以继续传递后续内容
  • 101: 客户端请求变换协议,服务端收到确认
  • 2xx: 请求成功,是否创建链接,请求是否接受,是否有内容这些
  • 200: (成功)服务器已成功处理了请求。
  • 201: (已创建)请求成功并且服务器创建了新的资源。
  • 202: (已接受)服务器已接受请求,但尚未处理。
  • 204: (无内容)服务器成功处理了请求,但没有返回任何内容。
  • 3XX: 一般用来判断重定向和缓存
  • 301: 所有请求已经转移到新的 url(永久重定向),会被缓存
  • 302: 临时重定向,不会被缓存
  • 304: 本地资源暂未改动,优先使用本地的(根据If-Modified-Since or If-Match去比对服务器的资源,缓存)
  • 4XX: 一般用来确认授权信息,请求是否出错,页面是否丢失
  • 400: 请求出错
  • 401: 未授权,不能读取某些资源
  • 403: 阻止访问,一般也是权限问题
  • 404: 页面丢失,资源没找到
  • 408: 请求超时
  • 415: 媒介类型不被支持,服务器不会接受请求。
  • 5XX: 基本都是服务端的错误
  • 500: 服务端错误
  • 502: 网关错误
  • 504: 网关超时

Q: HTTP的请求报文是怎么样的,能大体的说下么?

HTTP 的请求报文=请求行 + 请求头 + 请求体;

  • 请求行: 这个好理解就是访问的方法+ 协议+ 访问的 URL 构成
  • 请求头: 这个也好理解,比如 accept,content-type,user-agent这类值键对,服务端可以直接读取的
  • 请求体: 比如 POST 提交的一个表单,我们编码后放在上面需要传递的

想深入了解的具体引擎搜索

Q: 请求报文知道,那你说说cookie是如何跟随请求的?

Cookie 就是保存在 HTTP 协议的请求或者应答头部(Cookie 是由服务端生成),这样一路漂泊,

Q: Cookie 隔离是什么,如何做;

cookie 隔离就是降低 header 的数据包含,以达到加快访问速度的目的

方案: 静态资源丢 CDN或者非主域来加载

Q: 浏览器缓存和服务端的缓存控制你了解多少,说说看?

  • Last-Modified:
  • 第一次请求资源从服务器拉取的会自动带上该属性
  • 第二次请求会跟服务端比对If-Modified-Since的时间,没变动则使用本地的(状态304)
  • 结合Expires(过期时间:缓存的载止时间),跟随请求一起发出..资源没过期拿本地,否则重新请求
  • Cache-control 是 HTTP1.1的东西,判断资源过期结合max-age来替代Expires[http 1.0]
  • Etag:
  • 第一次请求url 时候会给服务器上标记(一串字符串)
  • 第二次请求时候会比对服务端的If-None-Match,没有改动依旧拿缓存(304)

Q: 几个短而让我印象深刻的题

if(!("a" in window)){
 var a=10;
}
console.log(a); // undefined
// !("a" i n window) , 返回 true
/*
 var a;
if(!("a" in window)){
 a=10;
}
*/
// 变种题
(function(){
 var x=c=b={a:1}
})()
console.log(x.a); // error , x is not defined
console.log(c,b) // {a: 1} {a: 1}
复制代码
var count=0;
console.log(typeof count==="number"); // true , 这个不用解释了
console.log(!!typeof count==="number"); // false
// 这里涉及到就是优先级和布尔值的问题
// typeof count 就是字符串"number"
// !!是转为布尔值(三目运算符的变种),非空字符串布尔值为 true
// 最后才===比较 , true==="number" , return false
复制代码
(function(){
 var a=b=3;
})()
console.log(typeof a==="undefined"); // false
console.log(typeof b==="undefined"); // false
// 这里涉及的就是立即执行和闭包的问题,还有变量提升,运算符执行方向(=号自左向右)
// 那个函数可以拆成这样
(function()
 var a; /* 局部变量,外部没法访问*/
 b=3; /* 全局变量,so . window.b===3 , 外部可以访问到*/
 a=b;
})()
// 若是改成这样,这道题应该是对的
console.log(typeof b==="number" && b===3
); // true
复制代码
function foo(something){
 this.a=something;
}
var obj1={
 foo:foo
};
var obj2={};
obj1.foo(2)
console.log(obj1.a) // 2 ,此时的 this 上下文还在 obj1内,若是 obj1.foo 先保存当做引用再执行传参,则上下文为 window
obj1.foo.call(obj2,3); // 用 call 强行改变上下文为 obj2内
console.log(obj2.a); // 3
var bar=new obj1.foo(4); // 这里产生了一个实例
console.log(obj1.a); // 2
console.log(bar.a); // 4; new的绑定比隐式和显式绑定优先级更高
复制代码
function fn(){
 alert(a);
 var a=200;
 alert(a);
}
fn(); // undefined / 200 ; 涉及变量提升
alert(a); // undefined
var a;
alert(a); // undefined
var a=300;
alert(a); // 300
复制代码
var obj1={
 name:'obj1',
 fn:function(){
 console.log(this.name);
 }
};
var obj2={name:'obj2'};
var obj3={name:'obj3'};
// 这道题主要涉及的是 this 指向的问题..
obj1.fn(); // obj1
var newFn=obj1.fn;
newFn(); // undefined, this 指向 window
newFn.call(obj2);// obj2, this 指向 obj2
obj3.fn=newFn;
/*
? (){
 console.log(this.name);
 }
*/
obj3.fn(); // 这里指向的是 obj3 .所以输出 obj3
复制代码
// 这道题来作为笔试题很绕,因为要回答的答案很多(脑海构思)..反正我是遇到了..
// 这道题主要考核的是对原型链继承这块的理解
function Parent(){
 this.a=1;
 this.b=[1,2,this.a];
 this.c={demo:5};
 this.show=function(){
 console.log(this.a + '' + this.c.demo + ':' + this.b)
 }
}
function Child(){
 this.a=2;
 this.change=function(){
 this.b.push(this.a);
 this.a=this.b.length;
 this.c.demo=this.a++;
 }
}
Child.prototype=new Parent();
var parent=new Parent();
var child1=new Child();
var child2=new Child();
child1.a=11;
child2.a=12;
// 这前面几个还算简单,继续看下去
parent.show(); // 15:1,2,1
// 因为 Child 自身没有 show 的方法,所以往原型链的上游找;
// 找到父类的,this 因为没更改,所以输出结果如下
child1.show(); // 115:1,2,1
child2.show(); // 125:1,2,1
child1.change(); // 改变一些数据,没有输出
child2.change(); // +1
parent.show(); // 15:1,2,1
child1.show(); // 55:1,2,1,11,12
child2.show(); // 65:1,2,1,11,12
复制代码
// 这道题也很绕,函数递归调用的
function test(a,b){
 console.log(b);
 return {
 test:function(c){
 return test(c,a);
 }
};
// 这道题的理解,拆成这样就好理解了
/*function test(a,b){
 console.log("a:"+a,"b:"+b);
 return {
 test:function(c){
 console.log("a:"+a,"b:"+b,"c"+c);
 return test(c,a);
 }
 }
}*/
var a=test(100); // undefined, 这个是不言而喻的;
a.test(200); // 100;
a.test(300); // 100;
var b=test(101).test(201).test(301); // undefined/101/201
var c=test(102).test(202); // undefined / 102
c.test(302); // 202
复制代码

Q:有字符串 var test='abc345efgabcab'; 请根据提示实现对应要求

  • 去掉字符串中的 a,b,c 字符 ,形成结果'345efg';
test.replace(/[abc]/g,''); // "345efg"
复制代码
  • 将字符串的数字用括号括起来, 形成结果: abc[3][4][5]efg,.'
test.replace(/\d/g,'[$&]'); // "abc[3][4][5]efgabcab"
// 若是有分组则按照$1, $2, $3的形式进行引用,而 $& 则表示的是整个正则表达式匹配的内容。
复制代码
  • 将字符串中的每个数字的值分别乘以2,输出:'abc6810,.'
var temp=test.split('').map(function(item){
 return /^\d$/.test(item) ? item * 2 : item;
}).join('');
// "abc6810efgabcab"
复制代码

Q: 使用不少于三种方式替换文本"dream"改成"package",提供字符串"I have a dream";

  • 正则替换
// 这是最简单的代码量了..
var str="I have a dream";
str.replace(/dream/g,"package");
// 不用正则也可以直接字符串替换
str.replace("dream","package")
复制代码
  • 数组遍历更改
// 很直白的大脑回路
var str="I have a dream";
str.split(" ").map(function(item){
 return item==="dream" ? item="package":item;
}).join(" ");
复制代码
  • 数组查询切割法
var str="I have a dream";
var tempArr=str.split(" "); // ["I", "have", "a", "dream"]
var removeIndex=tempArr.indexOf('dream'); // 3
tempArr.splice(removeIndex,1,"package");
var transStr=tempArr.join(" "); // "I have a package";
复制代码

这类东东弄成数组还是挺好弄的

这个是留言区小伙伴提供的方法..大同小异,如下;

// 源代码
// 字符串也有数组的 slice 以及 concat 的方法..思路和数组差不多
var str='I haved a dream';
str.indexOf('dream') !==-1 ? str.slice(0,str.indexOf('dream')).concat('package'):str;
复制代码

Q: 还有一道题目是涉及事件循环,执行优先权的..

就是 macrotask和microtask 相关的, 具体记不起来了,那时候给了答案虽然对了。

要说出所以然,给秀了一脸,回来找了下相关的资料;

  • JavaScript 运行机制详解:再谈Event Loop
  • 深入理解事件循环和异步流程控制
  • 所有你需要知道的关于完全理解 Node.js 事件循环及其度量

Q: 你对基础算法这块掌握的如何,.

来,这纸给你,写个快排试试,

// 快排的大体思路是这样的,
// 找个中位值,从原数组切割出来,
// 剩下的作为两个数组,每次都去比较;
// 直到递归的结果出来, 平均复杂度O(nlog n)
function quickSort(arr) {
 //如果数组长度<=1,则直接返回
 if (arr.length <=1) {
 return arr;
 }
 // 中间位(基准)取长度的一半向下取整
 var pivotIndex=Math.floor(arr.length / 2);
 //把中间位从原数组切割出来, splice 会改变原数组!!!!
 var pivot=arr.splice(pivotIndex, 1)[0];
 //定义两个空数组来存放比对后的值
 var left=[];
 var right=[];
 //比基准小的放在left,比基准大的放在right
 for (var i=0 , j=arr.length; i < j; i++) {
 if (arr[i] <=pivot) {
 left.push(arr[i]);
 } else {
 right.push(arr[i]);
 }
 }
 //递归下去 arr=[ left , pivot , right]
 // 怎么个递归法,就是比对后的数组还是会重复之前的取基准再切开比较..直到最后没有可以切了
 return quickSort(left).concat([pivot], quickSort(right));
}
复制代码

Q: 写一个二分法查找

// 二分法跟快排的思路差不多,对半比较
// 这个只用于排序好数组内的查询,高低位都知道的情况下
function binSearch(target, arr, start, end) {
 var start=start || 0; // 允许从什么位置开始,下标
 var end=end || arr.length - 1; // 什么位置结束,下标
 start >=end ? -1 : ''; // 没有找到,直接返回-1
 var mid=Math.floor((start + end) / 2); // 中位下标
 if (target==arr[mid]) {
 return mid; // 找到直接返回下标
 } else if (target > arr[mid]) {
 //目标值若是大于中位值,则下标往前走一位
 return binSearch(target, arr, start, mid - 1);
 } else {
 //若是目标值小于中位值,则下标往后退一位
 return binSearch(target, arr, mid + 1, end);
 }
}
// binSearch(5,[1,2,3,4,5,6,7,8])=> 4
// 无序的数组则需要先排序好数组,否则会堆栈溢出(死循环)
复制代码

这类的文章很多,有兴趣的可以阅读下面的一些文章

传送门:

  • <<十大经典排序算法总结(JavaScript描述>>
  • JavaScript数据结构和算法
  • javascript 常见排序算法

Q: 设计模式你了解多少?

  • Javascript常用的设计模式详解
  • js-design-pattern

Q: 思维拓展题: 你有两个玻璃球,有个100米的高楼,求玻璃球在哪个楼层扔下会碎(用的次数最少);

问题的要点: 玻璃球碎(有限个数) ,确定楼层数 , 最少次数=> 就是求最优的公式

面试大佬说这个还可以,那就暂且告一段落

,回来用万能的搜索引擎找了下..最优方案+最少次数需要考虑的东西很多,没那么简单

传送门: 知乎有人讨论了这个问题;

但是高数还老师了..这种帖子看的一脸懵逼,.抽空再好好研究下

Q: 你对优化这块了解多少?

大体常见的手段了解.

客户端着手

  • 压缩代码(JS/CSS),压缩图片
  • 合并一些小图片(css sprite)
  • 若是打包的代码尽可能切割成多个 chunk,减少单一 chunk过大
  • 静态文件采用 cdn 引入
  • HTTP的缓存头使用的合理
  • 减小第三方库的依赖
  • 对于代码应该考虑性能来编写,比如使用requestAnimationFrame绘制动画,尽可能减少页面重绘(DOM 改变)
  • 渐进升级,引入preload这些预加载资源
  • 看情况用service worker来缓存资源(比如移动端打算搞 PWA)

服务端着手

  • 带宽,域名解析, 多域名解析等
  • 页面做服务端渲染,减小对浏览器的依赖(不用客户端解析)
  • 渐进升级,比如引入 HTTP2(多路复用,头部压缩这些可以明显加快加载速度)

当然,这是这些都是很片面的点到,实际工作中去开展要复杂的多;

比如我们要多个维度去考虑的话,要去优化 DOM 的绘制时间,资源的加载时间,域名解析这些;

要全面的优化一个项目是一个大工程,

Q: MySQL有哪些索引类型? 索引的数据结构储存方式? MySQL和 MongoDB的差异

MySQL索引类型:

  • 普通索引: 就普通的类型
  • 唯一索引: 代表索引的值唯一不重复(允许有空值),相对于上面多了个UNIQUE
  • 主键索引:(创建表的跟随创建,唯一索引,不允许有空值)
  • 组合索引(就是将多个字段都建立到一个索引)

索引有利有弊,用的好加快查询速度,滥用索引会造成大量磁盘空间占用,维护性也会增多; 索引不会包含null的列;

索引的数据结构储存方式,我只简单了解过B-Tree

至于MySQL 和 MongoDB的差异;

前者是关系型数据库, 后者非关系型数据库(数据是以文档的方式储存,值为 key-value);

MySQL应用层面很广,有事务系统这些,链表查询这些都很方便.经常作为很多系统的主力数据库

而MongoDB作为NoSQL,虽然有些层面不如 MySQL,但是应用层面也挺广, 比如结合前端做一些用户的概要信息的维护,一些缓存信息的维护.

em,.后端了解不多,也能点到即止,.大学的时候学过一些..都差不多还给老师,.

Q: JS时间分段

给定一个时间段和步长,枚举该时间段内步长的划分

例如:时间段3:00-5:00,步长为20分钟

那么返回的数组为

['3:00-3:20', '3:20-3:40',.]等

这类问题,一般都要先梳理好思路再来写;

  • 给定字符串时间段,切割,转换为分钟
  • 跨日及跨时问题
// 这个东东我的小伙伴也写出来了.我的是在它的解答方式上加以注释和对参数的判断做了考虑
// 他的解法方案在他的 github 上 https://github.com/lyh2668/blog/issues/1 , by lyh2668
// 方便一些小伙伴的理解,以下代码包含ES6的姿势(参数默认值,剪头函数)
let inputDateRange=(date, step=30, separator='-')=> {
 let startTime, endTime; // 开始时间和结束时间
 if (Object.prototype.toString.call(date)==='[object String]') {
 date=date.trim(); // 去除两边的空格
 var tempDate='';
 if (separator) {
 tempDate=date.split(separator);
 } else {
 if (date.indexOf('-') !==-1) {
 tempDate=date.split('-');
 } else if (date.indexOf('~')) {
 tempDate=date.split('~');
 } else {
 console.log('您传入的也许不是一个时间段!!!');
 }
 }
 startTime=time2min(tempDate[0]); // 传入的开始时间
 endTime=time2min(tempDate[1]); //传入的结束时间
 } else if (Object.prototype.toString.call(date)==='[object Array]') {
 if (date.length===2) {
 startTime=time2min(date[0]); // 传入的开始时间
 endTime=time2min(date[1]); //传入的结束时间
 }
 } else {
 console.log('您传入的也许不是一个时间段!!!');
 }
 // 传入的 step 是否为数字,否则截图数字部分转化
 // 为什么和 NaN 比较(自身不等性),若是传入的连正则都没法识别,那只能给默认值了
 Object.prototype.toString.call(step)==='[object Number]'
 ? (step=parseInt(step, 10))
 : parseInt(step.replace(/[W\s\b]/g, ''), 10)===NaN
 ? (step=parseInt(step.replace(/[W\s\b]/g, ''), 10))
 : (step=30);
 // 若是开始时间大于结束时间则结束时间往后追加一天
 startTime > endTime ? (endTime +=24 * 60) : '';
 let transformDate=[]; // 储存转换后的数组,时间分段
 // 开始遍历判断,用 while
 while (startTime < endTime) {
 // 如果开始时间+步长大于结束时间,则这个分段结束,否则结束时间是步长递增
 let right=startTime + step > endTime ? endTime : startTime + step;
 transformDate.push(`${min2time(startTime)}-${min2time(right)}`);
 startTime +=step; // 步长递增
 }
 return transformDate;
};
// 时间转化为分钟
let time2min=time=> {
 // 获取切割的
 time.indexOf(':') ? (time=time.trim().split(':')) : '';
 return time[0] * 60 + parseInt(time[1]); // 返回转化的分钟
};
// 分钟转会字符串时间
let min2time=minutes=> {
 let hour=parseInt(minutes / 60); // 返回多少小时
 let minute=minutes - hour * 60; // 扣除小时后剩余的分钟数
 hour >=24 ? (hour=hour - 24) : ''; // 若是大于等于24小时需要扣除一天得到所剩下的小时
 minute < 10 ? (minute='0' + minute) : ''; // 小于10的都要补零
 hour < 10 ? (hour='0' + hour) : ''; // 小于10的都要补零
 return `${hour}:${minute}`;
};
// test ,支持字符串传入时间段
inputDateRange('3:00-5:00','20d'); // ["03:00-03:20", "03:20-03:40", "03:40-04:00", "04:00-04:20", "04:20-04:40", "04:40-05:00"]
// 亦或者数组传入
inputDateRange(['3:00','5:00'],'45df.3d'); // ["03:00-03:45", "03:45-04:30", "04:30-05:00"]
// step 支持数字亦或者带特殊字符的数字
inputDateRange(['6:00','8:00'],'55df.3d'); // ["06:00-06:55", "06:55-07:50", "07:50-08:00"]
inputDateRange('3:00-5:00',60); // ["03:00-04:00", "04:00-05:00"]
复制代码
  • JS不靠谱系列之枚举出时间段和对应的分钟数

Q: Vue-Router的两种模式主要依赖什么实现的

  • hash主要依赖location.hash来改动 URL,达到不刷新跳转的效果.每次 hash 改变都会触发hashchange事件(来响应路由的变化,比如页面的更换)
  • history主要利用了 HTML5的 historyAPI 来实现,用pushState和replaceState来操作浏览历史记录栈

Q: MVVM 和 MVC 的差异? 听说过 MVP?

这类的文章好多,三个开发模式的诞生都有前后,不是同时出现的.

传送门:

  • MVC,MVP 和 MVVM 的图示
  • 浅析前端开发中的 MVC/MVP/MVVM 模式

Q: 求100~999的所有"水仙花"数, 就是三位数中各数字的立方和等于自身,比如153=1^3+5^3+3^3

  • 常规遍历法
function threeWaterFlower(rangeStart, rangeEnd) {
 var temp=[];
 rangeStart=rangeStart || 100;
 rangeEnd=rangeEnd || 999;
 for (var i=rangeStart; i <=rangeEnd; i++) {
 var t=i.toString().split('');
 Math.pow(t[0], 3) + Math.pow(t[1], 3) + Math.pow(t[2], 3)==i
 ? temp.push(i)
 : '';
 }
 return temp;
}
threeWaterFlower(100,999); // [153, 370, 371, 407]
threeWaterFlower(); // [153, 370, 371, 407]
复制代码
  • 拓展写法,ES6版+不定花数,不折腾不舒服版本
let manyWaterFlower=(rangeStart=100, rangeEnd=999, flower=3)=> {
 let temp=[];
 for (let i=rangeStart; i <=rangeEnd; i++) {
 let t=i
 .toString()
 .split('')
 .map(item=> Math.pow(item, flower))
 .reduce((cur,next)=> parseInt(cur)+parseInt(next));
 let transformT=parseInt(t, 10);
 transformT==i ? temp.push(i) : '';
 }
 return temp;
}
manyWaterFlower(); // [153, 370, 371, 407]
manyWaterFlower(100,10000,4); // [1634, 8208, 9474]
manyWaterFlower(100,10000,5); // [4150, 4151]
复制代码

这种是穷举遍历,若是要快一点呢(考虑的周全一点呢),以及传参范围的矫正

相信小伙伴都看得懂,我已经尽量注释了..

let manyWaterFlower=(flower=3,rangeStart, rangeEnd )=> {
 let temp=[];// 缓存所有找到的花值
 // 这一段就是填充开始循环的范围,处理完毕后转为数字,推荐的开始值
 let flowerRecommandStart=Number(
 ''.padStart(flower, '0').replace(/^(\d{1})/g, '1')
 );
 let flowerRecommandEnd=Number(''.padStart(flower, '9'));
 // 判断是否传入开始值
 if (rangeStart) {
 rangeStart > flowerRecommandStart
 ? (rangeStart=flowerRecommandStart)
 : rangeStart;
 } else {
 rangeStart=flowerRecommandStart;
 }
 // 判断是否有传入结束值
 if (rangeEnd) {
 rangeEnd > flowerRecommandEnd ? (rangeEnd=flowerRecommandEnd) : rangeEnd;
 } else {
 rangeEnd=flowerRecommandEnd;
 }
 // 若是初始值大于结束值
 if (rangeStart > rangeEnd) {
 rangeEnd=flowerRecommandEnd;
 }
 for (let i=rangeStart; i <=rangeEnd; i++) {
 let t=i
 .toString()
 .split('')
 .map(item=> Math.pow(item, flower))
 .reduce((cur, next)=> parseInt(cur) + parseInt(next));
 let transformT=parseInt(t, 10);
 transformT==i ? temp.push(i) : '';
 }
 return temp;
};
console.time('manyWaterFlower');
manyWaterFlower(4)
console.timeEnd('manyWaterFlower');
// VM34013:4 manyWaterFlower: 8.112060546875ms ,这个是跑出来的时间
用上个例子的代码,从100到9999的,我们跑一下看看
console.time('manyWaterFlower');
manyWaterFlower(100,9999,4)
console.timeEnd('manyWaterFlower');
// VM3135:4 manyWaterFlower: 10.51904296875ms
// 我的 MBP 跑10花直接卡死,跑7花有点久,
console.time('7 flower')
manyWaterFlower(7);
console.timeEnd('7 flower')
// 7 flower: 6489.608154296875ms
// 8 花 CPU 的风扇狂叫,.
console.time('8 flower')
manyWaterFlower(8);
console.timeEnd('8 flower')
// VM644:3 8 flower: 68010.26489257812ms
// 对了我们还没有考虑数值溢出的问题..因为正整数在 JS 的范围是有限的.
// 有兴趣的小伙伴可以自行完善
复制代码

Q: 请使用递归算法在 TODO 注释后实现通过节点 key 数组寻找 json 对象中的对应值

比如console.log(findNode(['a1', 'b2'], data))===data.a1.b2

// 请使用递归算法在 TODO 注释后实现通过节点 key 数组寻找 json 对象中的对应值
var data={
 a1: {
 b1: 1,
 b2: 2,
 b3: {
 b4: 5
 }
 },
 a2: {
 b1: 3,
 b2: 4
 }
};
function findNode(inPath, inData) {
 // TODO
 // 判断传入的是否是一个数组
 if (Array.isArray(inPath)) {
 // 当长度为1的时候寻找该 key 是否有值,有则返回,无则返回-1
 if (inPath.length===1) {
 return inData[inPath[0]] ? inData[inPath[0]]: -1;
 }else{
 return findNode(inPath.slice(1), inData[inPath[0]]);
 }
 } else{
 console.log('您传入的不是一个数组')
 }
}
console.log(findNode(['a1', 'b2'], data)); // 2
console.log(findNode(['a1', 'b3','b4'], data)); // 5
复制代码
  • 来个拓展版?支持字符串或数组传入;findNode('a1.b2',data)?
var data={
 a1: {
 b1: 1,
 b2: 2,
 b3: {
 b4: 5
 }
 },
 a2: {
 b1: 3,
 b2: 4
 }
};
// 判断格式
function isType(params) {
 let type=Object.prototype.toString.call(params);
 if (type==='[object String]') {
 params=params.split('.');
 return params;
 }
 if (type==='[object Array]') {
 return params;
 }
}
function findNode(inPath, inData) {
 inPath=isType(inPath);
 // 判断传入的是否是一个数组
 if (Array.isArray(inPath)) {
 // 当长度为1的时候寻找该 key 是否有值,有则返回,无则返回-1
 if (inPath.length===1) {
 return inData[inPath[0]] ? inData[inPath[0]]: -1;
 }else{
 return findNode(inPath.slice(1), inData[inPath[0]]);
 }
 } else {
 console.log('您传入的不是一个数组');
 }
}
console.log(findNode(['a1', 'b2'], data)); // 2
console.log(findNode('a1.b3.b4', data)); // 5
复制代码

Q: webpack 是什么?webpack 常见的优化手段有哪些;

webpack 是一个资源处理工具,它的出现节省了我们的人力和时间; 可以对资源打包,解析,区分开发模式等等,

常见的优化手段:

  • 分离第三方库(依赖),比如引入dll
  • 引入多进程编译,比如happypack
  • 提取公共的依赖模块,比如commonChunkPlugin
  • 资源混淆和压缩:比如UglifyJS
  • 分离样式这些,减小bundle chunk的大小,比如ExtractTextPlugin
  • GZIP 压缩,在打包的时候对资源对齐压缩,只要部署的服务器能解析即可..减少请求的大小
  • 还有按需加载这些,一般主流的框架都有对应的模块懒加载方式.
  • 至于tree shaking目前webpack3/4已经默认集成

Q: 从你输入一个 URL 到页面渲染的大体过程,

大体过程是这样的,想了解很细致的可以自行引擎;

  1. IP->DNS(浏览器=>系统缓存=>DNS 服务器)->域名解析完成(这一步不用太多解析吧)
  2. TCP 协议走完->HTTP(S) 协议->缓存->(分析请求头)-> 回馈报文
  3. 请求文档下来->DOM->CSSDOM->静态资源下载->render(绘制文档)->js 解析
  4. 用户看到页面

Q: Vue 的组件的通讯手段有哪些..

  • 父-> 子: props
  • 子-> 父: on+emit
  • 父<>子: on.sync(语法糖)来的
  • 兄弟 : event bus | vuex

Q: Vuex你怎么理解?

vuex是一个状态管理容器(你也可以理解为全局变量),数据的流向是是单向数据流,

且数据并不具有持久化的特性(默认情况下刷新就重置所有状态);

里面的一些数据乃至方法,可以大致理解为 vue 的一些特性,比如

VuexVuestatedatagettercomputedmutation/actionsmethods

至于单向数据流(全局单例模式)怎么理解

state只能给mutation(同步操作) 改动, action只能反馈给mutation,可以进行异步操作(比如和后端交互拉取数据), state能触发 render,action能用dispatch分发..如图

结语

还有一些题目记不起来了,就没辙了,还有一些题目是看你个人发挥的,没法写,比如

  • Q: 让你来为公司的一个项目做技术选型,你会怎么做,为什么?
  • Q: React,Angular,Vue的比较?
  • Q: 说说你对 VNode的理解,diff的过程;
  • Q: Vue的双向绑定如何实现,用了什么模式(订阅模式),大体如何实现的。
  • Q: cmd/amd/commonjs的差异
  • Q: 小程序以及React Native的差异..等等

面试的过程中磕磕碰碰才能发现自身的很多不足和需要去努力的方向.

有不对之处请留言,会及时跟进修正,谢谢各位大佬

、canvas简介

? <canvas> 是 HTML5 新增的,一个可以使用脚本(通常为JavaScript)在其中绘制图像的 HTML 元素。它可以用来制作照片集或者制作简单(也不是那么简单)的动画,甚至可以进行实时视频处理和渲染。

? 它最初由苹果内部使用自己MacOS X WebKit推出,供应用程序使用像仪表盘的构件和 Safari 浏览器使用。 后来,有人通过Gecko内核的浏览器 (尤其是Mozilla和Firefox),Opera和Chrome和超文本网络应用技术工作组建议为下一代的网络技术使用该元素。

? Canvas是由HTML代码配合高度和宽度属性而定义出的可绘制区域。JavaScript代码可以访问该区域,类似于其他通用的二维API,通过一套完整的绘图函数来动态生成图形。

? Mozilla 程序从 Gecko 1.8 (Firefox 1.5)开始支持 <canvas>, Internet Explorer 从IE9开始<canvas> 。Chrome和Opera 9+ 也支持 <canvas>。

二、Canvas基本使用

2.1 <canvas>元素

<canvas id="tutorial" width="300" height="300"></canvas>

? <canvas>看起来和<img>标签一样,只是 <canvas> 只有两个可选的属性 width、heigth 属性,而没有 src、alt 属性。

? 如果不给<canvas>设置widht、height属性时,则默认 width为300、height为150,单位都是px。也可以使用css属性来设置宽高,但是如宽高属性和初始比例不一致,他会出现扭曲。所以,建议永远不要使用css属性来设置<canvas>的宽高。

###替换内容

? 由于某些较老的浏览器(尤其是IE9之前的IE浏览器)或者浏览器不支持HTML元素<canvas>,在这些浏览器上你应该总是能展示替代内容。

? 支持<canvas>的浏览器会只渲染<canvas>标签,而忽略其中的替代内容。不支持 <canvas> 的浏览器则 会直接渲染替代内容。

用文本替换:

<canvas>
 你的浏览器不支持canvas,请升级你的浏览器
</canvas>

用 <img> 替换:

<canvas>
 <img src="./美女.jpg" alt="">
</canvas>

结束标签</canvas>不可省

与 <img>元素不同,<canvas>元素需要结束标签(</canvas>)。如果结束标签不存在,则文档的其余部分会被认为是替代内容,将不会显示出来。

2.2 渲染上下文(Thre Rending Context)

? <canvas>会创建一个固定大小的画布,会公开一个或多个 渲染上下文(画笔),使用 渲染上下文来绘制和处理要展示的内容。

? 我们重点研究 2D渲染上下文。 其他的上下文我们暂不研究,比如, WebGL使用了基于OpenGL ES的3D上下文 (“experimental-webgl”) 。

var canvas=document.getElementById('tutorial');
//获得 2d 上下文对象
var ctx=canvas.getContext('2d');

2.3 检测支持性

var canvas=document.getElementById('tutorial');
if (canvas.getContext){
 var ctx=canvas.getContext('2d');
 // drawing code here
} else {
 // canvas-unsupported code here
}

2.4 代码模板

<html>
<head>
 <title>Canvas tutorial</title>
 <style type="text/css">
 canvas {
 border: 1px solid black;
 }
 </style>
</head>
<canvas id="tutorial" width="300" height="300"></canvas>
</body>
<script type="text/javascript">
 function draw(){
 var canvas=document.getElementById('tutorial');
 if(!canvas.getContext) return;
 var ctx=canvas.getContext("2d");
 //开始代码
 
 }
 draw();
</script>
</html>

2.5 一个简单的例子

绘制两个长方形。

<html>
<head>
 <title>Canvas tutorial</title>
 <style type="text/css">
 canvas {
 border: 1px solid black;
 }
 </style>
</head>
<canvas id="tutorial" width="300" height="300"></canvas>
</body>
<script type="text/javascript">
 function draw(){
 var canvas=document.getElementById('tutorial');
 if(!canvas.getContext) return;
 var ctx=canvas.getContext("2d");
 ctx.fillStyle="rgb(200,0,0)";
 //绘制矩形
 ctx.fillRect (10, 10, 55, 50);
 ctx.fillStyle="rgba(0, 0, 200, 0.5)";
 ctx.fillRect (30, 30, 55, 50);
 }
 draw();
</script>
</html>

三、绘制形状

3.1 栅格(grid)和坐标空间

? 如下图所示,canvas元素默认被网格所覆盖。通常来说网格中的一个单元相当于canvas元素中的一像素。栅格的起点为左上角(坐标为(0,0))。所有元素的位置都相对于原点来定位。所以图中蓝色方形左上角的坐标为距离左边(X轴)x像素,距离上边(Y轴)y像素(坐标为(x,y))。

? 后面我们会涉及到坐标原点的平移、网格的旋转以及缩放等。

3.2 绘制矩形

? <canvas> 只支持一种原生的 图形绘制:矩形。所有其他图形都至少需要生成一种路径(path)。不过,我们拥有众多路径生成的方法让复杂图形的绘制成为了可能。

canvast 提供了三种方法绘制矩形:

fillRect(x, y, width, height)

绘制一个填充的矩形

strokeRect(x, y, width, height)

绘制一个矩形的边框

clearRect(x, y, widh, height)

清除指定的矩形区域,然后这块区域会变的完全透明。

说明:

? 这3个方法具有相同的参数。

? x, y:指的是矩形的左上角的坐标。(相对于canvas的坐标原点)

? width, height:指的是绘制的矩形的宽和高。

function draw(){
 var canvas=document.getElementById('tutorial');
 if(!canvas.getContext) return;
 var ctx=canvas.getContext("2d");
 ctx.fillRect(10, 10, 100, 50); //绘制矩形,填充的默认颜色为黑色
 ctx.strokeRect(10, 70, 100, 50); //绘制矩形边框
 
}
draw();



ctx.clearRect(15, 15, 50, 25);

四、绘制路径(path)

? 图形的基本元素是路径。

? 路径是通过不同颜色和宽度的线段或曲线相连形成的不同形状的点的集合。

? 一个路径,甚至一个子路径,都是闭合的。

使用路径绘制图形需要一些额外的步骤:

创建路径起始点

调用绘制方法去绘制出路径

把路径封闭

一旦路径生成,通过描边或填充路径区域来渲染图形。

下面是需要用到的方法:

beginPath()

新建一条路径,路径一旦创建成功,图形绘制命令被指向到路径上生成路径

moveTo(x, y)

把画笔移动到指定的坐标(x, y)。相当于设置路径的起始点坐标。

closePath()

闭合路径之后,图形绘制命令又重新指向到上下文中

stroke()

通过线条来绘制图形轮廓

fill()

通过填充路径的内容区域生成实心的图形

4.1 绘制线段

function draw(){
 var canvas=document.getElementById('tutorial');
 if (!canvas.getContext) return;
 var ctx=canvas.getContext("2d");
 ctx.beginPath(); //新建一条path
 ctx.moveTo(50, 50); //把画笔移动到指定的坐标
 ctx.lineTo(200, 50); //绘制一条从当前位置到指定坐标(200, 50)的直线.
 //闭合路径。会拉一条从当前点到path起始点的直线。如果当前点与起始点重合,则什么都不做
 ctx.closePath();
 ctx.stroke(); //绘制路径。
}
draw();

4.2 绘制三角形边框

function draw(){
 var canvas=document.getElementById('tutorial');
 if (!canvas.getContext) return;
 var ctx=canvas.getContext("2d");
 ctx.beginPath();
 ctx.moveTo(50, 50);
 ctx.lineTo(200, 50);
 ctx.lineTo(200, 200);
 ctx.closePath(); //虽然我们只绘制了两条线段,但是closePath会closePath,仍然是一个3角形
 ctx.stroke(); //描边。stroke不会自动closePath()
}
draw();

4.3 填充三角形

function draw(){
 var canvas=document.getElementById('tutorial');
 if (!canvas.getContext) return;
 var ctx=canvas.getContext("2d");
 ctx.beginPath();
 ctx.moveTo(50, 50);
 ctx.lineTo(200, 50);
 ctx.lineTo(200, 200);
 
 ctx.fill(); //填充闭合区域。如果path没有闭合,则fill()会自动闭合路径。
}
draw();

4 绘制圆弧

有两个方法可以绘制圆弧:

arc(x, y, r, startAngle, endAngle, anticlockwise):

以(x, y)为圆心,以r为半径,从 startAngle弧度开始到endAngle弧度结束。anticlosewise是布尔值,true表示逆时针,false表示顺时针。(默认是顺时针)

注意:

这里的度数都是弧度。

0弧度是指的x轴正方形

radians=(Math.PI/180)*degrees //角度转换成弧度

arcTo(x1, y1, x2, y2, radius):

根据给定的控制点和半径画一段圆弧,最后再以直线连接两个控制点。

圆弧案例1:

function draw(){
 var canvas=document.getElementById('tutorial');
 if (!canvas.getContext) return;
 var ctx=canvas.getContext("2d");
 ctx.beginPath();
 ctx.arc(50, 50, 40, 0, Math.PI / 2, false);
 ctx.stroke();
}
draw();

圆弧案例2:

function draw(){
 var canvas=document.getElementById('tutorial');
 if (!canvas.getContext) return;
 var ctx=canvas.getContext("2d");
 ctx.beginPath();
 ctx.arc(50, 50, 40, 0, Math.PI / 2, false);
 ctx.stroke();
 ctx.beginPath();
 ctx.arc(150, 50, 40, 0, -Math.PI / 2, true);
 ctx.closePath();
 ctx.stroke();
 ctx.beginPath();
 ctx.arc(50, 150, 40, -Math.PI / 2, Math.PI / 2, false);
 ctx.fill();
 ctx.beginPath();
 ctx.arc(150, 150, 40, 0, Math.PI, false);
 ctx.fill();
}
draw();

圆弧案例3:

function draw(){
 var canvas=document.getElementById('tutorial');
 if (!canvas.getContext) return;
 var ctx=canvas.getContext("2d");
 ctx.beginPath();
 ctx.moveTo(50, 50);
 //参数1、2:控制点1坐标 参数3、4:控制点2坐标 参数4:圆弧半径
 ctx.arcTo(200, 50, 200, 200, 100);
 ctx.lineTo(200, 200)
 ctx.stroke();
 
 ctx.beginPath();
 ctx.rect(50, 50, 10, 10);
 ctx.rect(200, 50, 10, 10)
 ctx.rect(200, 200, 10, 10)
 ctx.fill()
}
draw();

arcTo方法的说明:

? 这个方法可以这样理解。绘制的弧形是由两条切线所决定。

? 第 1 条切线:起始点和控制点1决定的直线。

? 第 2 条切线:控制点1 和控制点2决定的直线。

? 其实绘制的圆弧就是与这两条直线相切的圆弧。

4.5 绘制贝塞尔曲线

4.5.1 什么是贝塞尔曲线

? 贝塞尔曲线(Bézier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。

? 一般的矢量图形软件通过它来精确画出曲线,贝兹曲线由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋,我们在绘图工具上看到的钢笔工具就是来做这种矢量曲线的。

? 贝塞尔曲线是计算机图形学中相当重要的参数曲线,在一些比较成熟的位图软件中也有贝塞尔曲线工具如PhotoShop等。在Flash4中还没有完整的曲线工具,而在Flash5里面已经提供出贝塞尔曲线工具。

? 贝塞尔曲线于1962,由法国工程师皮埃尔·贝塞尔(Pierre Bézier)所广泛发表,他运用贝塞尔曲线来为汽车的主体进行设计。贝塞尔曲线最初由Paul de Casteljau于1959年运用de Casteljau演算法开发,以稳定数值的方法求出贝兹曲线。

一次贝塞尔曲线(线性贝塞尔曲线)

? 一次贝塞尔曲线其实是一条直线。

二次贝塞尔曲线

三次贝塞尔曲线

4.5.2 绘制贝塞尔曲线

绘制二次贝塞尔曲线

quadraticCurveTo(cp1x, cp1y, x, y):

说明:

? 参数1和2:控制点坐标

? 参数3和4:结束点坐标

function draw(){
 var canvas=document.getElementById('tutorial');
 if (!canvas.getContext) return;
 var ctx=canvas.getContext("2d");
 ctx.beginPath();
 ctx.moveTo(10, 200); //起始点
 var cp1x=40, cp1y=100; //控制点
 var x=200, y=200; // 结束点
 //绘制二次贝塞尔曲线
 ctx.quadraticCurveTo(cp1x, cp1y, x, y);
 ctx.stroke();
 
 ctx.beginPath();
 ctx.rect(10, 200, 10, 10);
 ctx.rect(cp1x, cp1y, 10, 10);
 ctx.rect(x, y, 10, 10);
 ctx.fill();
 
}
draw();

绘制三次贝塞尔曲线

bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y):

说明:

? 参数1和2:控制点1的坐标

? 参数3和4:控制点2的坐标

? 参数5和6:结束点的坐标

function draw(){
 var canvas=document.getElementById('tutorial');
 if (!canvas.getContext) return;
 var ctx=canvas.getContext("2d");
 ctx.beginPath();
 ctx.moveTo(40, 200); //起始点
 var cp1x=20, cp1y=100; //控制点1
 var cp2x=100, cp2y=120; //控制点2
 var x=200, y=200; // 结束点
 //绘制二次贝塞尔曲线
 ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
 ctx.stroke();
 ctx.beginPath();
 ctx.rect(40, 200, 10, 10);
 ctx.rect(cp1x, cp1y, 10, 10);
 ctx.rect(cp2x, cp2y, 10, 10);
 ctx.rect(x, y, 10, 10);
 ctx.fill();
}
draw();

五、添加样式和颜色

? 在前面的绘制矩形章节中,只用到了默认的线条和颜色。

? 如果想要给图形上色,有两个重要的属性可以做到。

fillStyle=color

设置图形的填充颜色

strokeStyle=color

设置图形轮廓的颜色

备注:

1. `color` 可以是表示 `css` 颜色值的字符串、渐变对象或者图案对象。

2. 默认情况下,线条和填充颜色都是黑色。

3. 一旦您设置了 `strokeStyle` 或者 `fillStyle` 的值,那么这个新值就会成为新绘制的图形的默认值。如果你要给每个图形上不同的颜色,你需要重新设置 `fillStyle` 或 `strokeStyle` 的值。

1.fillStyle

function draw(){
 var canvas=document.getElementById('tutorial');
 if (!canvas.getContext) return;
 var ctx=canvas.getContext("2d");
 for (var i=0; i < 6; i++){
 for (var j=0; j < 6; j++){
 ctx.fillStyle='rgb(' + Math.floor(255 - 42.5 * i) + ',' +
 Math.floor(255 - 42.5 * j) + ',0)';
 ctx.fillRect(j * 50, i * 50, 50, 50);
 }
 }
}
draw();

2.strokeStyle

<script type="text/javascript">
 function draw(){
 var canvas=document.getElementById('tutorial');
 if (!canvas.getContext) return;
 var ctx=canvas.getContext("2d");
 for (var i=0; i < 6; i++){
 for (var j=0; j < 6; j++){
 ctx.strokeStyle=`rgb(${randomInt(0, 255)},${randomInt(0, 255)},${randomInt(0, 255)})`;
 ctx.strokeRect(j * 50, i * 50, 40, 40);
 }
 }
 }
 draw();

/**

返回随机的 [from, to] 之间的整数(包括from,也包括to)

*/

 function randomInt(from, to){
 return parseInt(Math.random() * (to - from + 1) + from);
 }
</script>

3.Transparency(透明度)

globalAlpha=transparencyValue

? 这个属性影响到 canvas 里所有图形的透明度,有效的值范围是 0.0 (完全透明)到 1.0(完全不透明),默认是 1.0。

? globalAlpha 属性在需要绘制大量拥有相同透明度的图形时候相当高效。不过,我认为使用rgba()设置透明度更加好一些。

line style

1. lineWidth=value

线宽。只能是正值。默认是1.0。

起始点和终点的连线为中心,上下各占线宽的一半

ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(100, 10);
ctx.lineWidth=10;
ctx.stroke();
ctx.beginPath();
ctx.moveTo(110, 10);
ctx.lineTo(160, 10)
ctx.lineWidth=20;
ctx.stroke()

###2. lineCap=type

线条末端样式。

共有3个值:

butt:线段末端以方形结束

round:线段末端以圆形结束

square:线段末端以方形结束,但是增加了一个宽度和线段相同,高度是线段厚度一半的矩形区域。

var lineCaps=["butt", "round", "square"];
 for (var i=0; i < 3; i++){
 ctx.beginPath();
 ctx.moveTo(20 + 30 * i, 30);
 ctx.lineTo(20 + 30 * i, 100);
 ctx.lineWidth=20;
 ctx.lineCap=lineCaps[i];
 ctx.stroke();
 }
 ctx.beginPath();
 ctx.moveTo(0, 30);
 ctx.lineTo(300, 30);
 ctx.moveTo(0, 100);
 ctx.lineTo(300, 100)
 ctx.strokeStyle="red";
 ctx.lineWidth=1;
 ctx.stroke();

3. lineJoin=type

同一个path内,设定线条与线条间接合处的样式。

共有3个值round, bevel 和 miter:

round

通过填充一个额外的,圆心在相连部分末端的扇形,绘制拐角的形状。 圆角的半径是线段的宽度。

bevel

在相连部分的末端填充一个额外的以三角形为底的区域, 每个部分都有各自独立的矩形拐角。

miter(默认)

通过延伸相连部分的外边缘,使其相交于一点,形成一个额外的菱形区域。

function draw(){
 var canvas=document.getElementById('tutorial');
 if (!canvas.getContext) return;
 var ctx=canvas.getContext("2d");
 var lineJoin=['round', 'bevel', 'miter'];
 ctx.lineWidth=20;
 for (var i=0; i < lineJoin.length; i++){
 ctx.lineJoin=lineJoin[i];
 ctx.beginPath();
 ctx.moveTo(50, 50 + i * 50);
 ctx.lineTo(100, 100 + i * 50);
 ctx.lineTo(150, 50 + i * 50);
 ctx.lineTo(200, 100 + i * 50);
 ctx.lineTo(250, 50 + i * 50);
 ctx.stroke();
 }
}
draw();

4. 虚线

用 setLineDash 方法和 lineDashOffset 属性来制定虚线样式. setLineDash 方法接受一个数组,来指定线段与间隙的交替;lineDashOffset属性设置起始偏移量.

function draw(){
 var canvas=document.getElementById('tutorial');
 if (!canvas.getContext) return;
 var ctx=canvas.getContext("2d");
 
 ctx.setLineDash([20, 5]); // [实线长度, 间隙长度]
 ctx.lineDashOffset=-0;
 ctx.strokeRect(50, 50, 210, 210);
}
draw();

备注:

? getLineDash():返回一个包含当前虚线样式,长度为非负偶数的数组。

六、绘制文本

绘制文本的两个方法

canvas 提供了两种方法来渲染文本:

fillText(text, x, y [, maxWidth])

在指定的(x,y)位置填充指定的文本,绘制的最大宽度是可选的.

strokeText(text, x, y [, maxWidth])

在指定的(x,y)位置绘制文本边框,绘制的最大宽度是可选的.

var ctx;
function draw(){
 var canvas=document.getElementById('tutorial');
 if (!canvas.getContext) return;
 ctx=canvas.getContext("2d");
 ctx.font="100px sans-serif"
 ctx.fillText("天若有情", 10, 100);
 ctx.strokeText("天若有情", 10, 200)
}
draw();

给文本添加样式

font=value

当前我们用来绘制文本的样式。这个字符串使用和 CSS font属性相同的语法. 默认的字体是 10px sans-serif。

textAlign=value

文本对齐选项. 可选的值包括:start, end, left, right or center. 默认值是 start。

textBaseline=value

基线对齐选项,可选的值包括:top, hanging, middle, alphabetic, ideographic, bottom。默认值是 alphabetic。

direction=value

文本方向。可能的值包括:ltr, rtl, inherit。默认值是 inherit。

七、绘制图片

? 我们也可以在canvas上直接绘制图片。

7.1 由零开始创建图片

创建<img>元素

var img=new Image(); // 创建一个<img>元素
img.src='myImage.png'; // 设置图片源地址

脚本执行后图片开始装载

绘制img

//参数1:要绘制的img 参数2、3:绘制的img在canvas中的坐标

ctx.drawImage(img,0,0); 

注意:

? 考虑到图片是从网络加载,如果 drawImage 的时候图片还没有完全加载完成,则什么都不做,个别浏览器会抛异常。所以我们应该保证在 img 绘制完成之后再 drawImage。

var img=new Image(); // 创建img元素
img.onload=function(){
 ctx.drawImage(img, 0, 0)
}
img.src='myImage.png'; // 设置图片源地址

7.2 绘制 img 标签元素中的图片

? img 可以 new 也可以来源于我们页面的 <img>标签

<img src="./美女.jpg" alt="" width="300"><br>
<canvas id="tutorial" width="600" height="400"></canvas>
<script type="text/javascript">
 function draw(){
 var canvas=document.getElementById('tutorial');
 if (!canvas.getContext) return;
 var ctx=canvas.getContext("2d");
 var img=document.querySelector("img");
 ctx.drawImage(img, 0, 0);
 }
 document.querySelector("img").onclick=function (){
 draw();
 }
</script>

第一张图片就是页面中的<img>标签

7.3 缩放图片

drawImage() 也可以再添加两个参数:

? drawImage(image, x, y, width, height)

? 这个方法多了2个参数:width 和 height,这两个参数用来控制 当像canvas画入时应该缩放的大小。

ctx.drawImage(img, 0, 0, 400, 200)

7.4 切片(slice)

drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)

? 第一个参数和其它的是相同的,都是一个图像或者另一个 canvas 的引用。

其他8个参数:

? 前4个是定义图像源的切片位置和大小,

? 后4个则是定义切片的目标显示位置和大小。

八、状态的保存和恢复

Saving and restoring state是绘制复杂图形时必不可少的操作。

save()和restore()

? save 和 restore 方法是用来保存和恢复 canvas 状态的,都没有参数。

? Canvas 的状态就是当前画面应用的所有样式和变形的一个快照。

关于 save()

Canvas状态存储在栈中,每当save()方法被调用后,当前的状态就被推送到栈中保存。一个绘画状态包括:

当前应用的变形(即移动,旋转和缩放)

strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation 的值

当前的裁切路径(clipping path)

?

可以调用任意多次 save方法。(类似数组的push())

关于restore()

每一次调用 restore 方法,上一个保存的状态就从栈中弹出,所有设定都恢复。(类似数组的pop())

var ctx;
function draw(){
 var canvas=document.getElementById('tutorial');
 if (!canvas.getContext) return;
 var ctx=canvas.getContext("2d");
 ctx.fillRect(0, 0, 150, 150); // 使用默认设置绘制一个矩形
 ctx.save(); // 保存默认状态
 ctx.fillStyle='red' // 在原有配置基础上对颜色做改变
 ctx.fillRect(15, 15, 120, 120); // 使用新的设置绘制一个矩形
 ctx.save(); // 保存当前状态
 ctx.fillStyle='#FFF' // 再次改变颜色配置
 ctx.fillRect(30, 30, 90, 90); // 使用新的配置绘制一个矩形
 ctx.restore(); // 重新加载之前的颜色状态
 ctx.fillRect(45, 45, 60, 60); // 使用上一次的配置绘制一个矩形
 ctx.restore(); // 加载默认颜色配置
 ctx.fillRect(60, 60, 30, 30); // 使用加载的配置绘制一个矩形
}
draw();

九、变形

9.1 translate

translate(x, y)

? 用来移动 canvas 的原点到指定的位置

? translate方法接受两个参数。x 是左右偏移量,y 是上下偏移量,如右图所示。

在做变形之前先保存状态是一个良好的习惯。大多数情况下,调用 restore 方法比手动恢复原先的状态要简单得多。又如果你是在一个循环中做位移但没有保存和恢复canvas 的状态,很可能到最后会发现怎么有些东西不见了,那是因为它很可能已经超出 canvas 范围以外了。

? 注意:translate移动的是canvas的坐标原点。(坐标变换)

var ctx;
function draw(){
 var canvas=document.getElementById('tutorial1');
 if (!canvas.getContext) return;
 var ctx=canvas.getContext("2d");
 ctx.save(); //保存坐原点平移之前的状态
 ctx.translate(100, 100);
 ctx.strokeRect(0, 0, 100, 100)
 ctx.restore(); //恢复到最初状态
 ctx.translate(220, 220);
 ctx.fillRect(0, 0, 100, 100)
}
draw();

9.2 rotate

rotate(angle)

? 旋转坐标轴。

? 这个方法只接受一个参数:旋转的角度(angle),它是顺时针方向的,以弧度为单位的值。

? 旋转的中心是坐标原点。

var ctx;
function draw(){
 var canvas=document.getElementById('tutorial1');
 if (!canvas.getContext) return;
 var ctx=canvas.getContext("2d");
 ctx.fillStyle="red";
 ctx.save();
 ctx.translate(100, 100);
 ctx.rotate(Math.PI / 180 * 45);
 ctx.fillStyle="blue";
 ctx.fillRect(0, 0, 100, 100);
 ctx.restore();
 ctx.save();
 ctx.translate(0, 0);
 ctx.fillRect(0, 0, 50, 50)
 ctx.restore();
}
draw();

9.3 scale

scale(x, y)

? 我们用它来增减图形在 canvas 中的像素数目,对形状,位图进行缩小或者放大。

? scale方法接受两个参数。x,y分别是横轴和纵轴的缩放因子,它们都必须是正值。值比 1.0 小表示缩 小,比 1.0 大则表示放大,值为 1.0 时什么效果都没有。

? 默认情况下,canvas 的 1 单位就是 1 个像素。举例说,如果我们设置缩放因子是 0.5,1 个单位就变成对应 0.5 个像素,这样绘制出来的形状就会是原先的一半。同理,设置为 2.0 时,1 个单位就对应变成了 2 像素,绘制的结果就是图形放大了 2 倍。

9.4 transform(变形矩阵)

transform(a, b, c, d, e, f)

a (m11)

? Horizontal scaling.

b (m12)

? Horizontal skewing.

c (m21)

? Vertical skewing.

d (m22)

? Vertical scaling.

e (dx)

? Horizontal moving.

f (dy)

? Vertical moving.

var ctx;
function draw(){
 var canvas=document.getElementById('tutorial1');
 if (!canvas.getContext) return;
 var ctx=canvas.getContext("2d");
 ctx.transform(1, 1, 0, 1, 0, 0);
 ctx.fillRect(0, 0, 100, 100);
}
draw();

十、合成

? 在前面的所有例子中、,我们总是将一个图形画在另一个之上,对于其他更多的情况,仅仅这样是远远不够的。比如,对合成的图形来说,绘制顺序会有限制。不过,我们可以利用 globalCompositeOperation 属性来改变这种状况。

globalCompositeOperation=type

var ctx;
 function draw(){
 var canvas=document.getElementById('tutorial1');
 if (!canvas.getContext) return;
 var ctx=canvas.getContext("2d");
 
 ctx.fillStyle="blue";
 ctx.fillRect(0, 0, 200, 200);
 ctx.globalCompositeOperation="source-over"; //全局合成操作
 ctx.fillStyle="red";
 ctx.fillRect(100, 100, 200, 200);
 }
 draw();

注:下面的展示中,蓝色是原有的,红色是新的。

type `是下面 13 种字符串值之一:

##1. source-over(default)

这是默认设置,新图像会覆盖在原有图像。

##2. source-in

仅仅会出现新图像与原来图像重叠的部分,其他区域都变成透明的。(包括其他的老图像区域也会透明)

##3. source-out

仅仅显示新图像与老图像没有重叠的部分,其余部分全部透明。(老图像也不显示)

##4. source-atop

新图像仅仅显示与老图像重叠区域。老图像仍然可以显示。

##5. destination-over

新图像会在老图像的下面。

##6. destination-in

仅仅新老图像重叠部分的老图像被显示,其他区域全部透明。

##7. destination-out

仅仅老图像与新图像没有重叠的部分。 注意显示的是老图像的部分区域。

##8. destination-atop

老图像仅仅仅仅显示重叠部分,新图像会显示在老图像的下面。

##9. lighter

新老图像都显示,但是重叠区域的颜色做加处理

##10. darken

保留重叠部分最黑的像素。(每个颜色位进行比较,得到最小的)

blue: #0000ff

red: #ff0000

所以重叠部分的颜色:#000000

##11. lighten

保证重叠部分最量的像素。(每个颜色位进行比较,得到最大的)

blue: #0000ff

red: #ff0000

所以重叠部分的颜色:#ff00ff

##12. xor

重叠部分会变成透明

##13. copy

只有新图像会被保留,其余的全部被清除(边透明)

#十一、裁剪路径

clip()

? 把已经创建的路径转换成裁剪路径。

? 裁剪路径的作用是遮罩。只显示裁剪路径内的区域,裁剪路径外的区域会被隐藏。

? 注意:clip()只能遮罩在这个方法调用之后绘制的图像,如果是clip()方法调用之前绘制的图像,则无法实现遮罩。

var ctx;
function draw(){
 var canvas=document.getElementById('tutorial1');
 if (!canvas.getContext) return;
 var ctx=canvas.getContext("2d");
 ctx.beginPath();
 ctx.arc(20,20, 100, 0, Math.PI * 2);
 ctx.clip();
 
 ctx.fillStyle="pink";
 ctx.fillRect(20, 20, 100,100);
}
draw();

十二、动画

动画的基本步骤

清空canvas

再绘制每一帧动画之前,需要清空所有。清空所有最简单的做法就是clearRect()方法

保存canvas状态

如果在绘制的过程中会更改canvas的状态(颜色、移动了坐标原点等),又在绘制每一帧时都是原始状态的话,则最好保存下canvas的状态

绘制动画图形

这一步才是真正的绘制动画帧

恢复canvas状态

如果你前面保存了canvas状态,则应该在绘制完成一帧之后恢复canvas状态。

控制动画

我们可用通过canvas的方法或者自定义的方法把图像会知道到canvas上。正常情况,我们能看到绘制的结果是在脚本执行结束之后。例如,我们不可能在一个 for 循环内部完成动画。

也就是,为了执行动画,我们需要一些可以定时执行重绘的方法。

一般用到下面三个方法:

setInterval()

setTimeout()

requestAnimationFrame()

##案例1:太阳系

let sun;
let earth;
let moon;
let ctx;
function init(){
 sun=new Image();
 earth=new Image();
 moon=new Image();
 sun.src="sun.png";
 earth.src="earth.png";
 moon.src="moon.png";
 let canvas=document.querySelector("#solar");
 ctx=canvas.getContext("2d");
 sun.onload=function (){
 draw()
 }
}
init();
function draw(){
 ctx.clearRect(0, 0, 300, 300); //清空所有的内容
 /*绘制 太阳*/
 ctx.drawImage(sun, 0, 0, 300, 300);
 ctx.save();
 ctx.translate(150, 150);
 //绘制earth轨道
 ctx.beginPath();
 ctx.strokeStyle="rgba(255,255,0,0.5)";
 ctx.arc(0, 0, 100, 0, 2 * Math.PI)
 ctx.stroke()
 let time=new Date();
 //绘制地球
 ctx.rotate(2 * Math.PI / 60 * time.getSeconds() + 2 * Math.PI / 60000 * time.getMilliseconds())
 ctx.translate(100, 0);
 ctx.drawImage(earth, -12, -12)
 //绘制月球轨道
 ctx.beginPath();
 ctx.strokeStyle="rgba(255,255,255,.3)";
 ctx.arc(0, 0, 40, 0, 2 * Math.PI);
 ctx.stroke();
 //绘制月球
 ctx.rotate(2 * Math.PI / 6 * time.getSeconds() + 2 * Math.PI / 6000 * time.getMilliseconds());
 ctx.translate(40, 0);
 ctx.drawImage(moon, -3.5, -3.5);
 ctx.restore();
 requestAnimationFrame(draw);
}

##案例2:模拟时钟

提起图标,大家可能第一个会想到PS、美工等词语,但很多小图标现在根本都不需要再打开PS了。

1、常见的括号( 前进或后退“>” )

.arrow{
  width:12rpx;
  height:12rpx; 
  border-top:1px solid #999;
  border-right:1px solid #999;
  transform:rotate(-45deg); 
  position:absolute; 
  right:10px; 
}


2、常见的关闭按钮( “X” ),这里需要用到一个伪类

.close {
        display: inline-block;
        width: 30px;
        height: 4px;
        background: #333;
        transform: rotate(45deg);
    }

    .close::after {
        content: '';
        display: block;
        width: 30px;
        height: 4px;
        background: #333;
        transform: rotate(-90deg);
    }


3、常见的勾选( “√” )

.check {
    position: relative;
    display: inline-block;
    width: 25px;
    height: 25px;
    background: #333;
    border-radius: 25px;
}
.check::after {
    content: "";
    position: absolute;
    left: 5px;
    top: 8px;
    width: 50%;
    height: 25%;
    border: 2px solid #fff;
    border-radius: 1px;
    border-top: none;
    border-right: none;
    background: transparent;
    transform: rotate(-45deg);
}


4、常见的加号( “+” ),同样需要利用伪类

.add {
  width: 100px;
  height: 100px;
  color: #ccc;
  transition: color .25s;
  position: relative;
}

 .add::before{
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 80px;
  margin-left: -40px;
  margin-top: -5px;
  border-top: 10px solid;
}

.add::after {
 content: '';
 position: absolute;
 left: 50%;
 top: 50%;
 height: 80px;
 margin-left: -5px;
 margin-top: -40px;
 border-left: 10px solid;
}


5、常见的波浪线( “~” ),同样需要利用伪类

.info::before {
content: '';
position: absolute;
top: 30px;
width: 100%;
height: 0.25em;

background:
 linear-gradient(
135deg, 
 transparent, 
 transparent 45%, 
 #008000, 
 transparent 55%, 
 transparent 100%
 ),
linear-gradient(
 45deg, 
 transparent, 
 transparent 45%, 
  #008000, 
 transparent 55%, 
 transparent 100%
);
background-size: 0.5em 0.5em;
background-repeat: repeat-x, repeat-x;
}


5、常见的三角形

.triangle-up {
width: 0;
height: 0;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-bottom: 100px solid red;
}


6、常见的扇形

.sector {
width: 0;
height: 0;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-top: 100px solid #f00;
border-radius: 50%;
}


7、仿微信对话框

.alertDialog {
/* 对话框:一个圆角矩形和一个小三角形 */
width: 150px;
height: 100px;
background: #f00;
border-radius: 10px;
position: relative;
}
.alertDialog:before {
content: "";
width: 0;
height: 0;
position: absolute;
left: -20px;
top: 40px;
border-top: 10px solid transparent;
border-bottom: 10px solid transparent;
border-right: 20px solid #f00;
}


8、钻石图标

.diamond {
/* 钻石:梯形和三角形组成 */
width: 50px;
height: 0;
position: relative;
border-bottom: 25px solid #f00;
border-left: 25px solid transparent;
border-right: 25px solid transparent;
}
.diamond:before {
content: "";
width: 0;
height: 0;
position: absolute;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-top: 70px solid #f00;
left: -25px;
top: 25px;
}


9、五角星图标

.starFive {
 width: 0;
 height: 0;
 position: relative;
 border-left: 80px solid transparent;
 border-right: 80px solid transparent;
 border-bottom: 60px solid #f00;
 transform: rotate(35deg);
}
.starFive:before {
 content: "";
 position: absolute;
 width: 0;
 height: 0;
 border-left: 80px solid transparent;
 border-right: 80px solid transparent;
 border-bottom: 60px solid #f00;
 transform: rotate(-70deg);
 top: 3px;
 left: -80px;
}
.starFive:after {
 content: "";
 position: absolute;
 width: 0;
 height: 0;
 border-bottom: 60px solid #f00;
 border-right: 20px solid transparent;
 border-left: 20px solid transparent;
 transform: rotate(-35deg);
        top: -40px;
        left: -49px;
}


喜欢的可以加个关注,不定期发布更多CSS相关文章