Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 国产亚洲精品yxsp,国产精品视频视频久久,亚洲一区二区三区在线免费观看

          整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          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相關文章


          主站蜘蛛池模板: 日本在线电影一区二区三区| 怡红院AV一区二区三区| 国产AV一区二区三区无码野战| 国产品无码一区二区三区在线蜜桃 | 亚洲另类无码一区二区三区| 丰满爆乳无码一区二区三区| 精品在线一区二区三区| 亚洲一区日韩高清中文字幕亚洲| 人妻免费一区二区三区最新| 亚洲国产情侣一区二区三区| 在线日韩麻豆一区| 丝袜无码一区二区三区| 亚洲色精品VR一区区三区| 日本一区二区三区精品国产| 国产一区二区精品| 国产乱人伦精品一区二区在线观看 | 欲色影视天天一区二区三区色香欲 | 亚州国产AV一区二区三区伊在| 东京热人妻无码一区二区av| 色噜噜狠狠一区二区| 男人的天堂亚洲一区二区三区| 国产精品视频第一区二区三区| 精品一区狼人国产在线| 99精品国产高清一区二区三区| 亚洲国产AV一区二区三区四区| 精品国产免费一区二区三区| 伊人久久精品一区二区三区| 中文字幕在线观看一区二区| 无码中文人妻在线一区二区三区| 亚洲Aⅴ无码一区二区二三区软件| 老熟妇仑乱视频一区二区| 无码欧精品亚洲日韩一区夜夜嗨 | 国产伦一区二区三区高清| 亚洲av成人一区二区三区| 人妻无码一区二区三区四区| 一本久久精品一区二区| 骚片AV蜜桃精品一区| 中文字幕一区二区三区乱码| 视频一区在线播放| 亚洲AV无码一区二区三区性色| 伊人久久大香线蕉av一区|