【JavaScript】整理

一、變量類型和計算


  1. JS中使用typeof能得到的哪些類型
  2. 何時使用===何時使用==
  3. JS中有哪些內置函數
  4. JS變量按照存儲方式區分爲哪些類型,並描述其特點
  5. 如何理解JSON

變量類型

JS中有7種內置類型,7種內置類型又分爲兩大類型

  • 基本類型/值類型:nullundefinedbooleannumberstringsymbol
  • 對象/引用類型:object

基本類型/值類型

是什麼

把每一個值存放在對應變量內存的位置,數據分塊存放在內存中,數據之間不會相互影響

var a = 100;
var b = a;
a = 200;
console.log(b); // 100

條件

原始類型存儲的都是值,是沒有函數可以調用的,比如undefined.toString()
在這裏插入圖片描述
'1'.toString() 是可以使用的。在這種情況下,'1' 已經不是原始類型了,而是被強制轉換成了 String 類型(大寫)也就是引用類型,所以可以調用 toString 函數

JS 的number 類型是浮點類型的,在使用中會遇到某些 Bug

  • NaN也屬於number類型,並且NaN不等於自身
  • 0.1 + 0.2 !== 0.3

string類型是不可變的,無論你在string類型上調用何種方法,都不會對值有改變

對於null來說,很多人會認爲他是個引用類型,其實這是錯誤的。雖然 typeof null 會輸出 object,但是這只是 JS 存在的一個悠久 Bug。在 JS 的最初版本中使用的是 32 位系統,爲了性能考慮使用低位存儲變量的類型信息,000 開頭代表是對象,然而 null 表示爲全零,所以將它錯誤的判斷爲 object 。雖然現在的內部類型判斷代碼已經改變了,但是對於這個 Bug 卻是一直流傳下來

對象/引用類型

是什麼

當你創建了一個引用類型a的時候,計算機會在內存中幫我們開闢一個空間來存放值,但是我們需要找到這個空間,這個空間會擁有一個地址(指針),引用類型a存儲的就是這個地址

const a = [];

對於常量 a 來說,假設內存地址(指針)爲 #001,那麼在地址 #001 的位置存放了值 [],常量 a 存放了地址(指針) #001

當我們將變量賦值給另外一個變量時,複製的是原本變量的地址(指針),也就是說當前變量 b 存放的地址(指針)也是 #001,當我們進行數據修改的時候,就會修改存放在地址(指針) #001 上的值,也就導致了兩個變量的值都發生了改變

const a = []
const b = a
b.push(1)

產生原因

若a賦值成一個對象,特別大,a再賦值給b,b也會佔很大的空間,不合理,所以引用類型是爲了讓內存共用空間,好幾個變量共用1個內存塊,節省內存空間,賦值只是變量指針的賦值,並不是每次賦值都把對象真正的值複製一份,所以值的修改相互干預

分類邊界

數組array、函數function、對象object

目的

無限制擴展屬性,比如說對象有個age屬性,可以加第2個屬性name屬性

組合
深淺拷貝

條件

函數參數是對象的情況

function test(person) {
  person.age = 26
  person = {
    name: 'yyy',
    age: 30
  }
  return person;
}

const p1 = {
  name: 'yck',
  age: 25
}

const p2 = test(p1)
console.log(p1) // -> ?
console.log(p2) // -> ?
  • 首先,函數傳參是傳遞對象指針的副本
  • 到函數內部修改參數的屬性這步,我相信大家都知道,當前 p1 的值也被修改了
  • 但是當我們重新爲 person 分配了一個對象時就出現了分歧,請看下圖

在這裏插入圖片描述

所以最後 person 擁有了一個新的地址(指針),也就和 p1 沒有任何關係了,導致了最終兩個變量的值是不相同的

typeof運算符

是什麼

只能區分基本類型的詳細類型,引用類型無法細分

分類邊界

typeof對於基本類型來說,除了null都可以顯示正確的類型

typeof null // 'object' BUG
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof 1 // 'number'
typeof '1' // 'string'
typeof Symbol() // 'symbol'

typeof對於引用類型來說,除了函數都會顯示object,所以說typeof並不能準確判斷引用變量到底是什麼類型。因爲函數是一個十分特殊的引用類型,在JS中函數的地位非常高,所以需要在任何地方輕鬆判斷出這個是函數,所以typeof單獨把函數列出來

typeof [] // 'object'
typeof {} // 'object'
typeof console.log // 'function'

組合

instanceof

類型轉換

是什麼

在JS中類型轉換隻有三種情況

  • 轉換爲布爾值
  • 轉換爲數字
  • 轉換爲字符串

在這裏插入圖片描述
組合

轉Boolean

在條件判斷時,除了undefinednullfalseNaN''0-0,其他所有值都轉爲true,包括所有對象

引用類型轉基本類型

引用類型在轉換類型的時候,會調用內置的 [[ToPrimitive]] 函數,對於該函數來說,算法邏輯一般來說如下

  • 如果已經是基本類型了,那就不需要轉換了
  • 調用 x.valueOf(),如果轉換爲基礎類型,就返回轉換的值
  • 調用 x.toString(),如果轉換爲基礎類型,就返回轉換的值
  • 如果都沒有返回基本類型,就會報錯

當然你也可以重寫 Symbol.toPrimitive ,該方法在轉原始類型時調用優先級最高

let a = {
  valueOf() {
    return 0
  },
  toString() {
    return '1'
  },
  [Symbol.toPrimitive]() {
    return 2
  }
}
1 + a // => 3

四則運算

目的

  • 字符串拼接
  • 運算

組合

  • 加法運算中一方爲字符串,那麼就會把另一方也轉換爲字符串
  • 加法運算中一方不是數字或字符串,那麼會將它轉換爲數字或字符串
1 + '1' // '11'
true + true // 2
4 + [1,2,3] // "41,2,3"
  • 對於第一行代碼來說,觸發特點一,所以將數字 1 轉換爲字符串,得到結果 '11'
  • 對於第二行代碼來說,觸發特點二,所以將 true 轉爲數字 1
  • 對於第三行代碼來說,觸發特點二,所以將數組通過 toString 轉爲字符串 1,2,3,得到結果 41,2,3

條件

對於加法還需要注意這個表達式 'a' + + 'b'

'a' + + 'b' // -> "aNaN"

因爲 + 'b' 等於 NaN,所以結果爲 "aNaN"

Tips

  • + '1' 的形式來快速獲取 number 類型
  • !!判斷變量會被當做true還是false

那麼對於除了加法的運算符來說,只要其中一方是數字,那麼另一方就會被轉爲數字

4 * '3' // 12
4 * [] // 0
4 * [1, 2] // NaN

比較運算符

是什麼
== > < !=

組合

如果是對象,就通過 toPrimitive 轉換對象
如果是字符串,就通過 unicode 字符索引來比較

let a = {
  valueOf() {
    return 0
  },
  toString() {
    return '1'
  }
}
a > -1 // true

在以上代碼中,因爲 a 是對象,所以會通過 valueOf 轉換爲原始類型再比較值

題目解答

  1. JS中使用typeof能得到的哪些類型

typeof可以識別6種數據類型:number、string、boolean、object、function、undefined

  1. 何時使用===何時使用==

使用jQuery源碼中推薦的寫法,當obj.a爲null或undefined時,看一個對象的屬性是否存在或看一個函數的參數是否存在,但對象和形參必須定義,否則會報錯,可簡寫使用==,除此外一律用===

// 看一個對象的屬性是否存在
if(obj.a == null) {
  // 相當於obj.a === null || obj.a ===undefined,簡寫形式
}

// 看一個函數的參數是否存在
function(a, b) {
  if(a == null) {...}
}
  1. JS中有哪些內置函數(數據封裝類對象)

都是函數

  • Boolean
  • Number
  • String
  • Object
  • Array
  • Function
  • Date
  • RegExp
  • Error(一定要大寫)

內置對象

  • Math
  • JSON
  1. JS變量按照存儲方式區分爲哪些類型,並描述其特點

基本類型:數據分塊存放在內存中,數據不會相互干涉

var a = 100;
var b = a;
a = 200;
console.log(b); //100

引用類型:好幾個變量共用1個內存塊,節省內存空間,賦值只是變量指針的賦值,並不是真正值的拷貝,所以值的修改相互干預

var a = {age: 20};
var b = a;
b.age = 21;
conlose.log(a.age) //21
  1. 如何理解JSON
  • JSON只不過是一個JS對象而已
  • JSON也是一種數據格式
  • Math也是JS對象
JSON.stringify({a:10,b:20}) //將對象轉換爲字符串
JSON.parse('{"a": 10,"b":20}') //將字符串變爲對象

二、原型與原型鏈


  1. 如何準確判斷一個變量是數組類型
  2. 寫一個原型鏈繼承的例子
  3. 描述new一個對象的過程
  4. zepto(或其他框架)源碼中如何使用原型鏈

構造函數

  • 構造函數首字母大寫
  • 構造函數類似於模板

new一個構造函數,返回一個對象的過程

  1. new的時候把參數傳入也可不傳
  2. new函數執行時,創建一個空對象
  3. this指向這個新對象this = {}
  4. 執行代碼,即對this.name等開始順序賦值
  5. 賦值完後,默認return this
  6. 賦值給ff.namef.agef.class生效
function Foo(name, age){
  this.name = name;
  this.age = age;
  this.class = 'class-1';
  // return this    //默認有這一行
}
var f = new Foo('zhangsan', 20);
// var f1 = new Foo('lisi', 23); 可創建多個對象

Tips

  • var obj = {}其實是var obj = new Object()的語法糖
  • var arr = []其實是var arr = new Array()的語法糖
  • var fn = funtion () {...}其實是var fn = new Function()的語法糖
  • 所有的引用類型(對象、數組、函數)都有構造函數
  • 推薦使用前者的寫法

原型

5條原型規則和示例

  1. 所有的引用類型(數組、對象、函數),都具有對象特性,即可自由擴展屬性(null除外)
var obj ={};
obj.a = 100 ;
var arr = [];
arr.a = 100;
var fn = function () {};
fn.a = 100;
  1. 所有的引用類型,都有一個__proto__屬性(隱式原型屬性),屬性值是一個普通的對象
console.log(obj.__proto__);
console.log(arr.__proto__);
console.log(fn.__proto__);
  1. 所有函數,都有一個prototype屬性(顯式原型屬性),屬性值是一個普通的對象
  • Number、String、Boolean、ObjectArrayFunction、Date、RegExp、Error(一定要大寫)都是函數
console.log(fn.prototype);
  1. 所有引用類型__proto__屬性值指向(完全等===)他的構造函數的prototype屬性值
console.log(obj.__proto__ === Object.prototype)
  1. 當試圖得到一個對象的某個屬性時,若果這個對象本身沒有這個屬性,那麼在它的__proto__(即它的構造函數的prototype)中尋找
// 構造函數
function Foo(name, age){
  this.name = name;
}
Foo.prototype.alertName = function () {
  alert(this.name);
}
// 創建實例
var f = new Foo('zhangsan');
f.printName = function () {
  console.log(this.name);
}
// 測試
f.printName();
f.alertName();
  • f本身沒有alertName的屬性,所以會去f的隱式原型__proto__中去尋找,f的隱式原型__proto__即爲其構造函數Foo的顯式原型prototype,Foo的顯式原型已被擴展了alertName的屬性,所以可順利執行
  • this永遠指向對象本身,在執行f.alertName()的時候會執行到第6行alert(this.name),但是這裏的this還是f本身

原型鏈

  • f.toString() -> f.__proto__ -> Foo.prototype -> 無toString屬性 -> Foo.prototype是一個對象 -> Foo.prototype.__proto__-> Object.prototype -> f.__proto__.__proto__
  • Object.prototype.__proto__ = null
// 構造函數
function Foo(name, age){
  this.name = name;
}
Foo.prototype.alertName = function () {
  alert(this.name);
}
// 創建實例 
var f = new Foo('zhangsan');
f.printName = function () {
  console.log(this.name);
}
// 測試
f.printName();
f.alertName();
f.toString(); // 要去f.__proto__.__proto__中查找

在這裏插入圖片描述

在這裏插入圖片描述

instanceof

  • 判斷引用類型屬於哪個構造函數的方法
  • f instanceof Foo判斷邏輯:f__proto__一層一層往上,能否對應到Foo.prototype
  • f instanceof Object判斷邏輯:f__proto__一層一層往上,是否對應到Object.prototype

循環對象自身屬性

  • 從上述代碼中可得f擁有三個屬性:name、printName、alertName
  • 但我們往往希望拿到對象本身定義的屬性,而不要來自其原型的屬性
var item;
for(item in f){
  // 高級瀏覽器已經在for in中屏蔽了來自原型的屬性
  // 但這裏建議大家加上這個判斷,保證程序的健壯性以滿足瀏覽器的兼容性
  if(f.hasOwnProperty(item)){
    console.log(item)
  }
}

題目解答

  1. 如何準確判斷一個變量是數組類型
var arr = []
arr instanceof Array //true
typeof arr //object,typeof是無法判斷數組的
  1. 寫一個原型鏈繼承的例子
  • 面試千萬不要這麼寫
  • 面試寫更貼近實戰的例子
// 動物
function Animal() {
  this.eat = function () {
    console.log('animal eat');
  }
}
// 狗
function Dog() {
  this.bark = function () {
    console.log('dog bark');
  }
}
Dog.prototype = new Animal();
// 哈士奇
var hashiqi = new Dog();
hashiqi.eat();
hashiqi.bark();
// 一個封裝DOM查詢的例子
function Elem(id) {
  this.elem = document.getElementById(id);
}

Elem.prototype.html = function (val) {
  var elem = this.elem;
  if (val) {
    elem.innerHTML = val;
    return this; // 鏈式操作
  } else {
    return elem.innerHTML
  }
}

Elem.prototype.on = function (type, fn) {
  var elem = this.elem;
  elem.addEventListener(type, fn);
  return this; // 鏈式操作
}

var div1 = new Elem('div1');
console.log(div1.html());
div1.html('<p>hello world</p>').on('click', function () {
  alert('clicked');
}).html('<p>javascript</p>')
  1. 描述new一個對象的過程
  • 創建一個空對象
  • this指向這個新對象
  • 執行代碼即對this賦值
  • 返回this
  1. zepto(或其他框架)源碼中如何使用原型鏈
  • 閱讀源碼是最高效提高技能的方式
  • 但不能“埋頭苦鑽”,有技巧在其中,搜索別人的閱讀體會
  • 慕課網搜索“zepto設計和源碼分析”
  • 在面試時說出讀過源碼並分享心得體會十分加分
  • jQuery也可
  • Vue、React不建議現在讀

三、閉包和作用域


  1. 說一下變量提升的理解
  2. 說明this幾種不同的使用場景
  3. 創建10個<a>標籤,點擊的時候彈出來對應的序號
  4. 如何理解作用域
  5. 實際開發中閉包的應用

執行上下文(聲明提升)

範圍:一段<script>或者一個函數

  • 全局(某個<script>):變量定義提前、函數聲明提前
  • 函數(函數即將執行之前):變量定義提前、函數聲明提前、確定this的值、確定arguments的值

注意:函數聲明和函數表達式的區別

函數聲明

fn(); // 不會報錯,因爲函數聲明會提升
function fn() {
  ... // 函數聲明
}

函數表示式

fn1(); // 會報錯,fn1會被當做變量定義,會提升相當於var fn1 = undefined,在執行fn1();
// 以下均爲函數表達式,函數表達式本質上即爲變量定義
var a = 100;
var fn1 = function () {
  ...
}

假如下列代碼在一個<script>中,在一個<script>中要定義一個全局的執行上下文,在執行第1行代碼之前,會把所有的變量聲明和函數聲明都執行一遍,執行順序如下:

  • 第2行,變量定義 ,還未執行到此,所以不會賦值,把a先拿出來,用undefined代替來佔位
  • 第4-8行,函數聲明,把整個函數拿出來,以此函數生效可以執行
  • 第1行,undefined
  • 第2行,a被賦值成100
  • 第3行,進入函數體,函數順利執行
  • 第7行,變量定義,並用undefined代替來佔位
  • 第5行,age被賦值成20
  • 第6行,打印arguments參數
console.log(a); // undefined
var a = 100;
fn('zhangsan'); // 'zhangsan', 20
function fn(name) {
  age = 20;
  console.log(name, age);
  var age;
}
// 全局
// var a = undefined;
console.log(a);
var a = 100;

fn('zhangsan');
function fn(name) {
  // 函數
  // 函數代碼執行之前(不是函數聲明之前),就已經確定了this的值
  console.log(this);
  // 函數代碼執行之前(不是函數聲明之前),就已經確定了arguments的值
  // arguments:函數參數的集合體
  console.log(arguments);
  age = 20;
  console.log(name, age);
  var age;

  bar(100);
  function bar(num) {
    console.log(num);
  }
}

this

this要在執行時才能確定值,定義時無法確認

  • 一個函數後面加(),即爲要執行
  • 在此之前,函數永遠處於定義狀態
var a = {
  name: 'A',
  fn: function () {
    console.log(this.name);
  }
} // 只看到這裏不能確認this到底是什麼
a.fn(); // this === a
a.fn.call({ name: 'B' }); // this === {name: 'B'}
var fn1 = a.fn;
fn1(); // this === window

作爲構造函執行

function Foo(name) {
  // this = {};
  this.name = name;
  // return this;
}
var f = new Foo('zhangsan');

作爲對象屬性執行

var obj = {
  name: 'A',
  printName: function () {
    console.log(this.name); // this === obj
  }
}
obj.printName();

作爲普通函數執行

function fn() {
  console.log(this); // this === window
}
fn();

call apply bind

function fn1(name, age) {
  alert(name);
  alert(age);
  console.log(this);  // this === window
}

// call表示{x:100}爲this,'zhangsan'爲第1個參數,20爲第2個參數,最常用
fn1.call({ x: 100 }, 'zhangsan', 20);

// apply將後面當做數組,相當於fn1.apply({ x: 100 }, ['zhangsan', 20]);
fn1.apply({ x: 100 }, 'zhangsan', 20);

// 使用bind修改默認this,.bind必須是函數表達式
var fn2 = function (name, age) {
  alert(name);
  alert(age);
  console.log(this);
}.bind({ y: 200 })
fn2('zhangsan', 20)

作用域

  • 沒有塊級作用域
  • 但有函數全局作用域

目的

  • 封裝變量
  • 收斂權限
  • 在函數外面不可能修改掉函數中定義的變量的值,以保證數據的安全不被污染
// 沒有塊級作用域
if (true) {
  // 在外面定義和定義在if語句塊中是一樣的
  // 儘量不要在塊中定義變量,容易使程序不易讀
  var name = 'zhangsan';
}
console.log(name); // 'zhangsan'

// 但有函數和全局作用域
var a = 100;
function fn() {
  var a = 200;
  // 函數中也有a,則使用函數中的a = 200
  // 函數中的變量值,外面是改不了的
  // 所以框架的第三方庫就採用將變量定義在函數中的方法來防止變量被污染,與外面隔絕
  console.log('fn', a);
}
console.log('global', a);// 全局 a = 100
fn();  // 函數 a = 200

作用域鏈

是什麼

去父級作用域取值,根據調用回到變量定義或函數聲明的地方的父作用域

目的

查找自由變量

組合

當前作用域沒有定義的變量,即“自由變量”

var a = 100
function fn() {
  var b = 200;
  // a即爲自由變量
  console.log(a); // 去父級作用域取值,變量定義或函數聲明時的父作用域
  console.log(b)
}
fn();
var a = 100;
function F1() {
  var b = 200;
  function F2() {
    var c = 300;
    // a是自由變量
    // 父級F1中a未定義,再在F1父級中找a,a = 100
    console.log(a);
    // b是自由變量
    console.log(b);
    console.log(c);
  }
  F2();
}
F1();

閉包使用場景

函數作爲返回值

function F1() {
  // a是F1局部變量
  var a = 100;
  // 返回一個函數(函數作爲返回值)
  return function () {
    // a是個自由變量
    console.log(a); // 去父級作用域取值,聲明時的父作用域
  }
}
var f1 = F1();
// a是全局變量,不會影響到函數中定義的局部變量
var a = 200;
// 不是看此處執行時的作用域,而是回到其定義處的作用域
f1(); // 100

函數作爲參數傳遞

function F1() {
  // a是F1局部變量
  var a = 100;
  // 返回一個函數(函數作爲返回值)
  return function () {
    // a是個自由變量
    console.log(a); // 去父級作用域取值,聲明時的父作用域
  }
}
var f1 = F1();

function F2(fn) {
  var a = 200;
  fn(); // 此處爲執行作用域
}

F2(f1);

題目解答

  1. 說一下變量提升的理解
  • 變量定義
  • 函數聲明(注意和函數表達式的區別)
  1. 說明this幾種不同的使用場景
  • 構造函數
  • 對象屬性
  • 普通函數
  • call apply bind
  1. 創建10個<a>標籤,點擊的時候彈出來對應的序號

錯誤寫法

var i, a
for (i = 0; i < 10; i++) {
  a = document.createElement('a');
  a.innerHTML = i + '<br>';
  a.addEventListener('click', function (e) {
    e.preventDefault();
    // i是自由變量,要去父作用域獲取值,無塊級作用域(for),即要找全局作用域
    alert(i);
  })
  document.body.appendChild(a);
}

正確寫法

var i;
for (i = 0; i < 10; i++) {
  // 自執行函數,不用調用,只要定義完成,立即執行的函數
  (function (i) {
    // 函數作用域
    var a = document.createElement('a');
    a.innerHTML = i + '<br>';
    a.addEventListener('click', function (e) {
      e.preventDefault();
      // i是自由變量,要去父作用域獲取值,無塊級作用域(for),即要找全局作用域
      alert(i);
    })
    document.body.appendChild(a);
  })(i)
}
  1. 如何理解作用域
  • 自由變量
  • 作用域鏈,即自由變量的查找
  • 閉包的兩個場景
  1. 實際開發中閉包的應用
  • 閉包實際應用主要用於封裝變量,收斂權限
  • 閉包的意義:你在isFirstLoad()函數外面,根本不可能修改掉_list的值,以保證數據的安全不被污染
function isFirstLoad() {
  // _list表示是私有的
  var _list = [];

  return function (id) {
    if (_list.indexOf(id) >= 0) {
      return false;
    } else {
      _list.push(id);
      return true;
    }
  }
}

var firstLoad = isFirstLoad();
firstLoad(10); // true
firstLoad(10); // false
firstLoad(20); // true
firstLoad(20); // false

四、異步和單線程


  1. 同步和異步的區別是什麼?分別舉例
  2. 一個關於setTimeout的筆試題
  3. 前端使用異步的場景有哪些

什麼是異步(對比同步)

判斷有沒有阻塞

  • 異步:無阻塞,我走我的,走完之後回來再說,等着執行,但是不卡在那兒,等着但不閒着
console.log(100)
setTimeout(function () {
  console.log(200) // 未阻塞,沒有在這兒停頓1s並打印200
}, 1000)
console.log(300)
// 100 300 200
  • 同步:有阻塞,會阻塞下面代碼的執行
console.log(100)
alert(200) // 1秒後點擊確認
console.log(300)

前端使用異步的場景

在可能發生等待的情況,等待過程中不能像alert一樣阻塞程序運行,因此,所有的等待情況都需要異步

  • 定時任務:setTimeout、setInterval
  • 網絡請求:Ajax請求(請求Google CDN),動態<img>加載
// Ajax請求demo
console.log('start');
$.get('./data1.json', function (data1) {
  console.log(data1);
});
console.log('end');
// start end 等待Ajax執行
// <img>加載demo
console.log('start');
var img = document.createElement('img');
img.onload = function () {
  console.log('loaded');
};
img.src = '/xxx.png';
console.log('end');
// start end 等待圖片加載
  • 事件綁定
    可執行多次,可以點擊再點擊,而前2個場景只能執行1次
// 事件綁定demo
console.log('start');
document.getElementById('btn1').addEventListener('click', function () {
  alert('clicked');
});
console.log('end');
// start end 等待元素點擊事件發生

異步和單線程的實現原理

執行順序demo

console.log(100)
setTimeout(function () {
  console.log(200)
})
console.log(300)
  1. 執行第一行,打印100
  2. 執行setTimeout後,傳入setTimeout的函數會被暫存起來,不會立即執行
    單線程的特點,不能同時幹兩件事
  3. 執行最後一行,打印300
  4. 待所有程序執行完,處於空閒狀態時,會立馬看有沒有暫存起來的任務要執行
  5. 發現暫存起來的setTimeout中的函數無需等待時間,就立即來過來執行

執行順序原理

  • 所有異步中的函數都會被拿出去暫時不執行,讓它們等待
  1. 所有的異步都是有函數的
  2. 只是暫存,並不排成隊列
  • 所有同步任務執行完後,要看邊上有沒有等待的程序在執行
  • 所有異步等待的任務,同時判斷是否滿足以下條件,不管排隊或者代碼書寫先後
  1. 是否有等待時間
  2. 發送的請求是否正確返回
  3. 綁定事件是否發生
  • 若有,則被封禁,等待事件發生時解禁執行異步任務
  • 若無,則一直處於解禁狀態,直接執行等待的異步任務

什麼是單線程

  • 單線程一次只能幹一件事,只能一個一個任務排隊來,不能同時執行兩個任務
  • JavaScript是單線程的,但是又不能讓程序阻塞卡頓,所以必須異步

題目解答

  1. 同步和異步的區別是什麼?分別舉例
  • 同步會阻塞代碼執行,而異步不會
  • alert是同步,setTimeout是異步
  1. 一個關於setTimeout的筆試題
console.log(1)
setTimeout(function () {
  console.log(2)
}, 0)
console.log(3)
setTimeout(function () {
  console.log(4)
}, 1000)
console.log(5)
// 1 3 5 2 1s後打印4
  1. 前端使用異步的場景有哪些
  • 定時任務:setTimeout、setInverval
  • 網絡請求:Ajax請求、動態<img>加載
  • 事件綁定

五、其它(如日期、Math、各種常用API)


  1. 獲取2017-06-10格式的日期
  2. 獲取隨機數,要求是長度一致的字符串格式
  3. 寫一個能遍歷對象和數組的通用forEach函數

日期

// Date是個構造函數
// now是個屬性,但也是個函數
// 所以Date.now()也是個函數
// 1552272178876
// 獲取當前時間毫秒數
Date.now();
// Mon Mar 11 2019 10:42:17 GMT+0800 (CST)
// 會自動執行toString(),轉化爲字符串格式
var dt = new Date();
dt.getTime(); // 獲取毫秒數
dt.getFullYear(); // 年
dt.getMonth(); // 月(0-11)從0開始的,比較特殊,需要+1
dt.getDate(); // 日(0-31)
dt.getHours(); // 小時(0-23)
dt.getMinutes(); // 分鐘(0-59)
dt.getSeconds(); // 秒(0-59)

Math

  • 獲取隨機數Math.random()
  • 返回0-1之間的一個小數,位數不確定,一般很長,一般不會重複
  • 常用於清除緩存

數組API

forEach:遍歷數組中所有元素

var arr = ['a', 'b', 'c'];
arr.forEach(function(item, index) { // 值,索引
  // 遍歷數組的所有元素
  // item對應"a", "b", "c"
  // index對應0, 1, 2
  console.log(index, item);
  // 0 "a"
  // 1 "b"
  // 2 "c"
})

every:判斷所有元素是否都符合條件

返回true或false

var arr = [1, 2, 3];
var result = arr.every(function(item, index) {
  // 用來判斷所有的數組元素,都滿足一個條件
  if (item < 4) {
    return true;
  }
})
console.log(result); // true

some:判斷是否有至少一個元素符合條件

返回true或false

var arr = [1, 2, 3];
var result = arr.some(function(item, index) {
  // 用來判斷所有的數組元素,只要有一個滿足條件即可
  if (item < 2) {
    return true;
  }
})
console.log(result); // true

sort:排序

  • 改變原數組
  • 可對真實數據大小進行排序而不是ACSII碼
var arr = [1, 4, 2, 3, 5];
var arr2 = arr.sort(function(a, b) {
  // 從小到大
  return a - b;
  // 從大到小
  return b - a;
})
console.log(arr2);

map:對元素重新組裝

  • 不改變原數組
  • 返回符合條件的數組
var arr = [1, 2, 3, 4];
var arr2 = arr.map(function(item, index) {
  // 將元素重新組裝,並返回
  return '<b>' + item + '</b>';
})
console.log(arr2);

fileter:過濾符合條件的元素

  • 不改變原數組
  • 返回符合條件的數組
var arr = [1, 2, 3];
var arr2 = arr.filter(function(item, index) {
  // 通過某一個條件過濾數組
  if (item >= 2) {
    return true;
  }
})
console.log(arr2);

對象API

for in:遍歷對象中所有屬性

var obj = {
  x: 100,
  y: 200,
  z: 300
}
var key;
for (key in obj) { // key就是obj的屬性名,即x, y, z
  // 注意這裏的hasOwnProperty,在講原型鏈時候講過
  if (obj.hasOwnProperty(key)) {
    console.log(key, obj[key]);
  }
}
// x 100
// y 200
// z 300

題目解答

  1. 獲取2017-06-10格式的日期
function formatDate(dt) {
  if (!dt) {
    dt = new Date();
  }
  var year = dt.getFullYear();
  var month = dt.getMonth() + 1; // 一定要+1
  var date = dt.getDate();
  if (month < 10) {
    // 強制類型轉換
    month = '0' + month;
  }
  if (date < 10) {
    // 強制類型轉換
    date = '0' + date;
  }
  // 強制類型轉換
  return year + '-' + month + '-' + date;
}
var dt = new Date();
var date = formatDate(dt);
console.log(date);
  1. 獲取隨機數,要求是長度一致的字符串格式
// 所有需要統一數字位數的情況都可以使用如下方法
var random = Math.random();
var random = random + '0000000000'; // 後面加10個零
var random = random.slice(0, 10); // 取前10位
console.log(random);
  1. 寫一個能遍歷對象和數組的通用forEach函數
function forEach(obj, fn) {
  var key;
  if (obj instanceof Array) {
    // 準確判斷是不是數組
    obj.forEach(function(item, index)) {
      fn(index, item);
    }
  } else {
    // 不是數組就是對象
    for (key in obj) {
      fn(key, obj[key]);
    }
  }
}

var arr = [1, 2, 3];
// 注意,這裏參數的順序換了,爲了和對象的遍歷格式一致
forEach(arr, function(index, item) {
  console.log(index, item);
})
var obj = {
  x: 100,
  y: 200
}
forEach(arr, function(key, value) {
  console.log(key, value);
})

六、JS-Web-API DOM&BOM


  1. DOM是哪種基本的數據結構?
  2. DOM操作常用API有哪些?
  3. DOM節點的attr和property有何區別?
  4. 如何檢測瀏覽器的類型?
  5. 拆解URL的各個部分

回顧JS基礎知識

特點:表面看來並不能用於工作中開發代碼

  • 內置函數:Object、Array、Boolean、String等
  • 內置對象:Math、JSON等
  • 我們連在網頁彈出一句hello world都不能實現

常說的JS(瀏覽器執行的JS)包含兩部分:JS基礎知識(ECMA262標準)和JS-Web-API(W3C標準)

  • JS基礎知識 :ECMA262標準,只是一個規則
  • JS-Web-API:W3C標準,沒有規定任何JS基礎相關的東西,不管什麼變量類型、原型、作用域和異步,只管定義用於瀏覽器中JS操作頁面的API和全局變量
  • W3C標準中關於JS的規定有:DOM操作、BOM操作、事件綁定、Ajax請求(包括http協議)等
  • NodeJS因爲是基於JS所以符合ECMA262標準,但是其服務於服務器端,沒有window、document等,而是有network、service等,不符合W3C標準

全面考慮,JS內置的全局函數和對象有哪些?

  • 之前講過的Object、Array、Boolean、String、Math、JSON
  • 剛剛提到的window、document
  • 所有未定義的全局變量,如navigator.userAgent

DOM本質

XML是一種可擴展的描述語言,可以描述任何結構化的數據

  • 數據結構:樹
  • HTML是XML的一種特殊類型

DOM:瀏覽器把拿到的HTML代碼,結構化一個瀏覽器能識別並且JS可操作的一個模型

  • Document 文檔
  • Object 對象
  • Modal 模型

DOM節點操作

獲取DOM節點

QuerySelector/QuerySelectorAll和getElementById/getElementsByClassName的區別

// div1、divList、containerList、pList都是JS對象
var div1 = document.getElementById('div1'); // 元素
var divList = document.getElementByTagName('div'); // 集合
console.log(divList.length);
console.log(divList[0]);

var containerList = document.getElementByClassName('.container'); // 集合
var pList = document.querySelectorAll('p'); // 集合

property

  • 文檔直接修改,查看源碼即可看到修改
  • 修改的是JS對象的標準屬性,有關JS的屬性
var pList = document.querySelectorAll('p');// 集合
var p = pList[0];
console.log(p.style.width); // 獲取樣式
p.style.width='100px';  // 修改樣式
console.log(p.className); // 獲取class
p.className='p1'; // 修改class

// 獲取nodeName和nodeType
console.log(p.nodeClass)
console.log(p.nodeType)

在這裏插入圖片描述

Attribute

  • 文檔直接修改,查看源碼即可看到修改
  • 修改的是HTML代碼文檔內的標籤,有關文檔內標籤的屬性
  • setAttribute原本沒有的標籤屬性會自動添加
var pList = document.querySelectorAll('p'); // 集合
var p = pList[0];
p.getAttribute('data-name');
p.setAttribute('data-name', 'imooc');
p.getAttribute('style');
p.setAttribute('style', 'font-size:30px;');

DOM結構操作

針對樹的操作

獲取父元素

var div1 = document.getElementById("div1");
var parent = div1.parentElement;

獲取子元素

在使用childNodes獲取子元素時,換行也會算作1個text,計爲1個Node
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

var div1 = document.getElementById("div1");
var child = div1.childNodes;
console.log(child[0].nodeType) // text 3
console.log(child[1].nodeType) // p 1 標籤都是1
console.log(child[0].nodeName) // text #text
console.log(child[1].nodeName) // p P

新增節點

var div1 = document.getElementById('div1')
// 添加新節點
var p1 = document.createElement('p')
p1.innerHTML = 'this is p1'
div1.appendChild(p1) // 添加新創建的元素

// 移動已有節點
var p2 = document.getElementById('p2')
div1.appendChild(p2)

刪除節點

var div1 = document.getElementById('div1');
var child = div1.childNodes;
div1.removeChild(child[0]); // 可能看不到效果,因爲刪除的child[0]可能是1個因換行引起的text Node

BOM

  • Browser 瀏覽器
  • Object 對象
  • Model 模型

navigator

var ua = navigator.userAgent;
var isChrome = ua.indexof('Chrome');
console.log(isChrome);

screen

console.log(screen.width);
console.log(screen.height);

location

console.log(location.href); // 整個url
location.protocol; //協議:http or https
location.host; // 域名
location.pathname;  // 路徑
location.search; // ?後的參數
location.hash // #後面是哈希

history

history.back(); // 返回
history.forward(); // 前進

題目解答

  1. DOM是哪種基本的數據結構?

  1. DOM操作常用API有哪些?
  • 獲取DOM節點以及節點的property和Attribute
  • 獲取父節點、子節點
  • 新增節點和刪除節點
  1. DOM節點的attr和property有何區別?
  • property只是一個JS對象的屬性的修改和獲取
  • Attribute是對HTML標籤屬性的修改和獲取
  1. 如何檢測瀏覽器的類型?
var ua = navigator.userAgent;
var isChrome = ua.indexof('Chrome');
console.log(isChrome);
  1. 拆解URL的各個部分
console.log(location.href); // 整個url
location.protocol; //協議:http or https
location.host; // 域名
location.pathname;  // 路徑
location.search; // ?後的參數
location.hash // #後面是哈希

七、事件


  1. 編寫一個通用的事件監聽函數
  2. 描述事件冒泡過程
  3. 對於一個無限下拉加載圖片的頁面,如何給每個圖片綁定事件

通用事件綁定

關於低版本IE兼容性問題

  • IE低版本使用attachEvent,和W3C標準不一樣
  • IE低版本使用量非常少,很多網站早已不支持
var btn = document.getElementById('btn1');
btn.addEventListener('click', function (event) {
  console.log('clicked');
})
// 封裝
function bindEvent(elem, type, fn) {
  elem.addEventListener(type, fn);
}
var a = document.getElementById('link1');
bindEvent(a, 'click', function (e) {
  e.preventDefault(); // 阻止默認行爲,比如:阻止a標籤的跳轉
  alert('clicked');
})

事件冒泡

  • 順着DOM的順序結構,底層葉節點的點擊事件會一層一層根據順序結構往其父元素上觸發
  • 點擊p1後往上冒泡:觸發p1的click事件 -> 觸發div1的click事件 -> 觸發body的click事件
<body>
  <div id="div1">
    <p id="p1">激活</p>
    <p id="p2">取消</p>
    <p id="p3">取消</p>
    <p id="p4">取消</p>
  </div>
  
  <div id="div2">
    <p id="p5">取消</p>
    <p id="p6">取消</p>
  </div>
</body>

<script>
var p1 = document.getElementById('p1');
var body = document.body;
bindEvent(p1, 'click', function (e) {
  e.stopPropagation(); // 阻止冒泡
  alert('激活');
})

bindEvent(body, 'click', function (e) {
  alert('取消');
})
</script>

代理

是什麼

事件冒泡的應用:通過事件冒泡機制,在元素的上層增加事件綁定機制,代理到每個元素上

目的

代碼簡潔,減少瀏覽器內存佔用

<body>
  <div id="div1">
    <a href="#">a1</a>
    <a href="#">a2</a>
    <a href="#">a3</a>
    <a href="#">a4</a>
    <!-- 會隨時新增更多a標籤 -->
  </div>
</body>

<script>
  var div1 = document.getElementById('div1');
  div1.addEventListener('click', function (e) {
    var target = e.target;
    if (target.nodeName === 'A') {
      alert(target.innerHTML);
    }
  })
</script>

完善版本

function bindEvent(elem, type, selector, fn) {
  if (fn == null) {
    fn = selector;
    selector = null;
  }
  elem.addEventListener(type, function (e) {
    var target;
    if (selector) {
      target = e.target;
      if (target.matches(selector)) {
        fn.call(target, e); // this = target
      } else {
        fn(e);
      }
    }
  })
}
// 使用代理
bindEvent(a, 'click', 'a', function (e) {
  console.log(this.innerHTML);
})

// 不使用代理
bindEvent(a, 'click', function (e) {
  console.log(this.innerHTML);
})

題目解答

  1. 編寫一個通用的事件監聽函數
  1. 描述事件冒泡過程

DOM樹形結構 -> 事件冒泡 -> 阻止冒泡 -> 冒泡的應用(代理)

  1. 對於一個無限下拉加載圖片的頁面,如何給每個圖片綁定事件

使用代理

八、Ajax & 跨域


  1. 手動編寫一個Ajax,不依賴第三方庫
  2. 跨域的幾種實現方式

XMLHttpRequest

  • IE低版本使用ActiveXObject,和W3C標準不同
var xhr = new XMLHttpRequest();
xhr.open('GET', '/app', false); // false 異步
xhr.onreadystatechange = function () {
  // 這裏的函數異步執行,可參考之前JS基礎中的異步模塊
  if (xhr.readyState == 4) {
    if (xhr.state == 200) {
      alert(xhr.responseText)
    }
  }
}
xhr.send(null)

狀態碼說明

xhr.readyState == 4

狀態碼 狀態 說明
0 未初始化 還沒有調用send()方法
1 載入 已調用send()方法,正在發生請求
2 載入完成 send()方法執行完成,已接收到全部相應內容
3 交互 正在解析相應內容
4* 完成 響應內容解析完成,可以在客戶端調用了

xhr.status == 200

狀態碼 說明
2xx 表示成功處理請求,如200
3xx 需要重定向,瀏覽器直接跳轉
4xx 客戶端請求錯誤,如404
5xx 服務端錯誤

跨域

什麼是跨域

瀏覽器有同源策略,不允許Ajax訪問其他域接口

跨域條件

  • 協議
  • 域名
  • 端口(HTTP默認80;HTTPS默認443)

有一個不同就算跨域

但是有三個標籤允許跨域加載資源

  • <img src = xxx>用於打點統計,統計網站可能是其他域,防盜鏈
  • <link href = xxx><script src = xxx>可以使用CDN,CDN的也是其他域
  • <script src = xxx>script可以用於JSONP

注意事項:

  • 所有跨域請求都必須經過信息提供方允許
  • 如果未經允許即可獲取,那是瀏覽器同源策略出現漏洞

JSONP

實現原理

加載 http://codeing.m.imooc.com/classindex.html

  • 不一定服務器端真正有一個classindex.html
  • 服務器可以根據請求,動態生成一個文件,返回
  • 同理於<script src = 'http://coding.m.imooc.com/api.js'>

例如你的網站要跨域訪問慕課網的一個接口

<script>
  window.callback = function (data) {
    // 這裏是我們跨域得到的信息
    console.log(data)
  }
</script>
<script src="http://coding.m.imooc.com/api.js"></script>

服務器端設置http header

跨域趨勢,簡潔方法,服務端設置

response.setHeader('Access-Control-Allow-Origin',"http://a.com")
response.setHeader('Access-Control-Allow-Headers','X-Requestd-Width')
response.setHeader('Access-Control-Allow-Method','PUT,POST,GET,DELETE,OPTIONS')
response.setHeader('Access-Control-Allow-Credentials','true') // 接受寬裕的cookie

題目解答

  1. 手動編寫一個Ajax,不依賴第三方庫
  1. 跨域的幾種實現方式
  • JSONP
  • 服務器端設置http header

九、存儲


  1. 請描述下cookie、sessionStorage和localStorage的區別

cookie

  • 本身用於客戶端和服務端通訊
  • 但它有本地存儲的功能,於是就被借用了
  • 使用document.cookie = xxx獲取修改即可

缺點

  • 存儲量太小,4kb
  • 所有http/Ajax請求都帶着,影響獲取資源效率
  • API簡單,需要封裝才能用document.cookie

localStorage

  • HTML5專門爲存儲而設計,最大容量5M
  • API簡單易用
  • iOS safari隱藏模式下,localstorage.getItem會報錯,建議統一使用try-catch封裝
localStorage.setItem(key, value);
localStorage.getItem(key);

sessionStorage

  • HTML5專門爲存儲而設計,最大容量5M
  • API簡單易用
  • 瀏覽器關了會清零
sessionStorage.setItem(key, value);
sessionStorage.getItem(key);

題目解答

  1. 請描述下cookie、sessionStorage和localStorage的區別
  • 容量
  • 是否會攜帶到Ajax中
    cookie會攜帶,sessionStorage和localStorage不會攜帶
  • API易用性

十、開發環境


關於開發環境

  • 面試官放通過開發環境瞭解面試者的經驗
  • 開發環境最能體現工作產出效率
  • 會以聊天的形式爲主,而不是出具體的問題
  • IDE(開發工具,寫代碼的效率)
  • Git(代碼版本管理,多人協作開發)
  • JS模塊化
  • 打包工具
  • 上線回滾的流程

IDE

  • webstorm
  • sublime
  • vscode
  • atom
  • 插件

Git

  • 正式項目都需要代碼版本管理
  • 大型項目需要多人協作開發
  • Git和linux是一個作者
  • 網絡Git服務器,如 coding.netgithub.com
  • 一般公司代碼非開源,都有自己的Git服務器
  • 搭建Git服務器無需瞭解太多

Git的基本操作必須很熟練

命令 說明
git status 查看狀態
git diff 查看兩個文件的不同
git checkout xxx (file name) 發現自己改錯了,需要還原
git add . 將所有修改的東西全部囊括進來
git commit -m “xxx” 修改的內容提交到本地倉庫,-m代表添加的備註
git push origin master 提交到遠程倉庫
git pull origin master 別人修改代碼,從遠程下載已修改的代碼
git clone 克隆新的項目
git branch 查看當前分支
git checkout -b xxx 新建一個分支
git checkout xxx (branch name) 切換到一個已有的分支
git merge xxx 合併分支

在這裏插入圖片描述

echo "# test" >> README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin [email protected]:kxbk100/test.git
git push -u origin master

模塊化

不使用模塊化

  • 依賴層級引用關係
  • 代碼中的函數必須是全局變量,才能暴露給使用方面,所以全局變量污染,不清楚各個文件間的依賴關係
  • a.js知道要引用a-util.js,但是不知道其還需要依賴util.js

util.js

function getFormatDate(date, type) {
  // type === 1 返回 2017-06-15
  // type === 2 返回 2017年6月15日
  // ...
}

a-util.js

function aGetFormatDate(date) {
  // 要求返回 2017年6月15日 格式
  return getFormatDate(date, 2);
}

a.js

var dt = new Date();
console.log(aGetFormatDate(dt));

date.html

  • 順序不能顛倒
  • 3個文件之間是強依賴關係
<script src="util.js"></script>
<script src="a-util.js"></script>
<script src="a.js"></script>

使用模塊化

  • 只往外s輸出1個函數
  • 在另一個文件中再接收
  • 直接<script src="a.js"></script> ,其他的根據依賴關係自動引用
  • 前2個函數,沒必要做成全局變量,不會帶來污染和覆蓋

util.js

export {
  getFormatDate: function(date, type) {
    // type === 1 返回 2017-06-15
    // type === 2 返回 2017年6月15日
    // ...
  }
}

a-util.js

var getFormatDate = require('util.js');
export {
  aGetFormatDate: function (date) {
    // 要求返回 2017年6月15日 格式
    return getFormatDate(date, 2)
  }
}

a.js

var aGetFormatDate = require('a-util.js');
var dt = new Date();
console.log(aGetFormatDate(dt));

AMD

  • 異步模塊定義
  • require.js
  • 全局定義define函數
  • 全局定義require函數
  • 依賴js會自動、異步加載,不使用就不加載,提升性能
  • return一個對象
  • 只有先define才能被require

util.js

define(function () {
  return {
    getFormateDate: function (date, type) {
      if (type == 1) {
        return '2017-06-15'
      }
      if (type == 2) {
        return '2017年6月15日'
      }
    }
  }
})

a-util.js

define(['./util.js'], function (util) {
  return {
    aGetFormatDate: function(date) {
      return util.getFormatDate(date, 2);
    }
  }
});

a.js

define(['./a-util.js'], function(aUtil) {
  return {
    printDate: function (date) {
      console.log(aUtil.aGetFormatDate);
    }
  }
}) 

main.js

require(['./a.js'], function (a) {
  var date = new Date();
  a.printDate(date)
})

main.html

<script src="/require.min.js" data-main="./main.js"></script> // 定義程序入口

a.js

define(['./util.js'], function (util) {
  return {
    aGetFormateDate: function (date) {
      return util.getFormateDate();
    }
  }
})

main.js

define(['./a.js'], function (a) {
  return {
    printDate: function (date) {
      console.log(autil.aGetFormateDate(date))
    }
  }
})

CommonJS

  • nodejs模塊化規範,現在被大量用於前端
  • 前端開發依賴的插件和庫,都可以從npm中獲取
  • 構建工具的高度自動化,使得使用npm的成本非常低
  • CommonJS不會異步加載JS,而是同步一次性加載進來
  • exports輸出出一個東西,var require接收一個東西

util.js

module.exports = {
  getFormateDate: function (date, type) {
    if (type == 1) {
      return '2017-06-15'
    }
    if (type == 2) {
      return '2017年6月15日'
    }
  }
}

a-uril.js

var util = require('util.js');
module.export = {
  aGetFormateDate: function (date) {
    return util.getFormateDate(date, 2);
  }
}

AMD和CommonJS的使用場景

  • 需要異步加載,用AMD
  • 不需要異步加載JS,用CommonJS
  • 使用npm之後使用CommonJS

構建工具(對模塊化的代碼打包和壓縮)

構建工具

  • grunt(沒人用)
  • gulp
  • fis3
  • webpack

構建的意義

  • 模塊化打包
  • 支持CommonJS
  • CommonJS受nodeJS支持
  • 後端放到前端來用需要兼容
  • webpack封裝了很多方法,支持通過CommonJS的方式來運行在前端

安裝 -> 配置 -> 處理一個簡單事例 -> 得到結果

安裝nodeJS

  1. 安裝Node.js
  2. 進入文件目錄
  3. 初次使用安裝http-server sudo npm install http-server -g
  4. 使用http-server -p 8881生成服務
    這個服務只能針對靜態頁面的編輯,nodejs或者php的修改無法使用

安裝webpack

  1. 進入文件目錄
  2. 初始化環境npm init
  3. 自動生成package.json文件

在這裏插入圖片描述
4. 安裝包npm install webpack --save-dev-dev表示僅用於開發環境
5. 安裝包npm install jquery --save,任何環境都需要
6. 卸載包npm uninstall moment --save

配置webpack

  1. 新建webpack.config.js,與index.html和package.json同級
var path = require('path');
var webpack = require('webpack');

module.exports = {
  context: path.resolve(__dirname, './src'), // __dirname:前端目錄
  entry: {
    app: './app.js'
  },
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'bundle.js'
  }
}
  1. 新建src文件夾,在其中創建app.js入口文件
  2. 在package.json的script中新增
"start": "webpack" // 將start指定爲webpack
  1. 在index.html中,引入bundle.js
  2. 訪問頁面

使用jQuery

  1. 在app.js中添加
var $ = require('jquery'); // 它會從package.json中的dependencies中查找安裝的jQuery
  1. 自己寫模塊可以根據相對路徑獲取
var aUtil = require('./a-util.js')
  1. npm start打包

壓縮jQuery

  1. 安裝UglifyJS Webpack Plugin
$ npm install uglifyjs-webpack-plugin --save-dev
  1. 修改webpack.config.json如下
var path = require('path');
var webpack = require('webpack');
var uglifyJsPlugin = require('uglifyjs-webpack-plugin');

module.exports = {
  context: path.resolve(__dirname, './src'), // __dirname:前端目錄
  entry: {
    app: './app.js'
  },
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'bundle.js'
  },
  //壓縮js
  optimization: {
    minimizer: [
      new uglifyJsPlugin({
        uglifyOptions: {
          compress: false
        }
      })
    ]
  }
}
  1. npm start打包

上限回滾流程

介紹

  • 是非常重要的開發環節
  • 各個公司的具體流程不同
  • 由專門的工具負責系統完成,我們無需關心細節
  • 如果沒有參與過,面試時也要說出要點
  • 只講要點,具體實現無法講解

上線和回滾的基本流程

上線

  • 將測試完成的代碼提交到git版本庫的master分支
  • 將當前服務器的代碼全部打包並記錄版本號(1.0),備份
  • 將master分支的代碼提交到服務器覆蓋到線上服務器,生成新版本號(1.1)

回滾

  • 將當前服務器的代碼打包並記錄版本號(1.1),備份
  • 將備份的上一個版本號解壓(1.0),覆蓋到線上服務器,並生成新的版本號(1.2)

linux基本命令

  • 服務器使用Linux居多,server版,只有命令行
  • 測試環境要匹配線上環境,因此也是Linux
  • 經常需要登錄測試機來自己配置,獲取數據

常用命令

  • mkdir a
  • ls
  • ll
  • cd a
  • pwd 查看路徑
  • rm -rf a
  • vi a.js
    輸入:i
    保存:esc :w
    退出:esc :q
    保存並退出:esc :wq
  • cat a.js 查看文件
  • cp a.js a1.js 拷貝
  • mv a1.js src/a1.js
  • rm a.js

十一、運行環境


  1. 從輸入url到得到html的詳細過程
  2. window.load和DOMContentLoaded的區別

頁面加載過程

  • 瀏覽器就可以通過訪問鏈接來得到頁面的內容
  • 通過繪製和渲染,顯示出頁面的最終的樣子

加載資源的形式

加載一個資源的過程

  1. 瀏覽器根據DNS服務器得到域名的IP地址
  2. 向這個IP的機器發送http請求
  3. 服務器收到處理並返回http請求
  4. 瀏覽器得到返回內容

瀏覽器渲染頁面的過程

  1. 根據HTML結構生成DOM Tree
  2. 根據CSS生成CSSOM
  3. 將DOM和CSSOM整合形成Render Tree(渲染樹)
  4. 根據Render Tree開始渲染和展示
  5. 遇到<script>時,會執行並阻塞渲染
    因爲JS會改變DOM結構及內容,所以兩者不能同時進行
  • 將CSS放於head中,加載完CSS後瀏覽器直接知道規則,在渲染body中的元素時,已將CSS考慮進去渲染
  • 將CSS放於body尾部,元素先按照默認加載,然後再根據CSS進行改變,性能較差
  • 將script放於body尾部,可以拿到所有DOM標籤和結構,不會阻塞body上面的元素的渲染,性能較優
  • <p><img src="XXX" /></p>img DOM元素會順序生成,但圖片src異步加載

性能優化

原則

  • 多使用內存、緩存或者其他方法
  • 減少CPU計算、減少網絡請求、減少IO操作(前端不考慮)

從哪裏入手

加載頁面和靜態資源

靜態資源的壓縮合並(打包合併+代碼壓縮)

  • 手動合併效率低、會出錯,一般用構建工具合併
  • 3個文件需要發3個請求,每個請求都會耗費很多時間
  • 1個文件只需發送1個請求
<script src="a.js"></script>
<script src="b.js"></script>
<script src="c.js"></script>
// 打包合併後
<script src="abc.js"></script>

靜態資源緩存

  • 通過鏈接名稱控制緩存,<script src=“abc_1.js”></script>
  • 只有內容改變的時候,鏈接名稱纔會改變,<script src="abc_2.js"></script>

使用CDN

  • 不同地域的資源優化
  • 讓資源加載更快

使用SSR後端渲染

  • 現在Vue React提出了這樣的概念
  • 其實jsp php asp都屬於後端渲染
  • 數據直接輸出到HTML中

頁面渲染

CSS放前面,js放後面

懶加載

  • 圖片懶加載、下拉加載更多
  • 給src賦值一個很小的圖
  • 真正的圖片放在一個data後面
  • 用的時候再把data屬性賦值到src中
  • 加快頁面渲染速度
<img id="img1" src="preview.png" data-realsrc="abc.png" />
<script>
  var img1 = document.getElementById('img1');
  img1.src = img1.getAttribute('data-realsrc');
</script>

減少DOM操作

  • 緩存DOM查詢,減少DOM查詢,對DOM查詢做緩存
// 未緩存DOM查詢
var i;
for (i = 0; i < document.getElementsByTagName('p').length; i++) {
  // TODO
}

// 緩存了DOM查詢
var pList = document.getElementsByTagName('p');
var i;
for (i = 0; i < pList.length; i++) {
  // TODO
}
  • 合併DOM插入,減少DOM操作,多個操作儘量合併在一起執行
var listNode = document.getElementById('list');
// 創建1個片段
var frag = document.createDocumentFragment();
var x, li;
for (x = 0; x < 10; x++) {
  li = document.createElement('li');
  li.innerHTML = "List item" + x;
  // 插入片段
  frag.appendChild(li);
}
// 最後將片段直接插入正文
listNode.appendChild(frag);

事件節流

  • 合併頻繁操作
  • 很快的連着的操作,快速打字先不觸發
var textaarea = document.getElementById('text');
var timeoutId;
textaarea.addEventListener('keyup', function () {
  if (timeoutId) {
    clearTimeout(timeoutId);
  }
  timeoutId = setTimeout(function () {
    // 觸發change事件
    console.log("用戶停止打字,開始觸發事件")
  }, 100);
})

儘早執行操作

window.addEventListener('load', function () {
  // 頁面全部加載完之後纔會執行,包括圖片、視頻等
})

document.addEventListener('DOMContentLoaded', function () {
  // DOM渲染完即可執行,此時圖片、視頻還可能沒有加載完
  // jQuery、zepto均使用此方法
})

安全性

XSS跨站請求攻擊

  • 在新浪博客寫一篇文章,同時偷偷插入一段<script>
  • 攻擊代碼中,獲取cookie,發送到自己的服務器
  • 發佈博客,有人查看博客內容
  • 會把查看者的cookie發送到攻擊者的服務器

預防

  • 前端替換關鍵字,例如替換<&lt;>&gt;(配合)
  • 後端替換(建議)

CSRF跨站請求僞造

  • 你已登錄一個購物網站,正在瀏覽器商品
  • 該網站付費接口是 xxx.com/pay?id=100 ,但是沒有任何驗證
  • 然後你收到一封郵件,隱藏着<img src=xxx>
  • 你查看郵件的時候,就已經悄悄的付費購買了

預防

  • 增加驗證流程,如輸入指紋、密碼、短信驗證碼等

面試技巧

簡歷

  • 簡潔明瞭,重點突出項目經歷和解決方案
  • 把個人博客放在簡歷中,並且定期維護更新博客
  • 把個人的開源項目放在簡歷中,並維護開源項目
  • 簡歷千萬不要造假,要保持能力和經歷上的真實性

過程中

  • 如何看待加班?加班就像借錢,救急不救窮
  • 千萬不可挑戰面試官,不要反銬面試官
  • 學會給面試官驚喜,但不要太多
  • 遇到不會回答的問題,說出你知道的就可以
  • 談談你的缺點:說說你最近正在學什麼就可以了

可能對React不是很瞭解,最近正在學React,大約1個月後就能做出1個React的網站

題目解答

  1. 從輸入url到得到html的詳細過程
  • 瀏覽器根據DNS服務器得到域名的IP地址
  • 向這個IP的機器發送http請求
  • 服務器收到處理並返回http請求
  • 瀏覽器得到返回內容
  1. window.load和DOMContentLoaded的區別
window.addEventListener('load', function () {
  // 頁面全部加載完之後纔會執行,包括圖片、視頻等
})

document.addEventListener('DOMContentLoaded', function () {
  // DOM渲染完即可執行,此時圖片、視頻還可能沒有加載完
  // jQuery、zepto均使用此方法
})
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章