前端面試題自檢 JS CSS 部分

說在前面

限於這是個自查手冊,回答不那麼太詳細,若是某個知識點下面有連接,本身又沒有深刻了解過的,理應點擊連接或自行搜索深刻了解。javascript

另外兩個部分 :css

JS

類型

JavaScript的簡單數據類型

Number , String , Boolean , Undefined , Null , Symbolhtml

typeof 操做符的返回值

  • number
  • string
  • boolean
  • undefined
  • object
  • function
  • symbol

typeof NaN 返回 number前端

typeof null 時也返回 object ,此爲歷史遺留問題vue

爲何數字和字符串可使用方法?

由於數字和字符串在使用方法時會轉換爲包裝對象java

包裝對象就是其對應的構造函數創造出來的node

(1).toString() // (new Number(1)).toString()
複製代碼

new Number(1) { __proto__: Number , [[PrimitiveValue]] : 1 }react

包裝對象上會有一個內部值 [[PrimitiveValue]],值爲被包裝的原始值git

這個對象在表達式結束後就會被銷燬,因此沒法給字符串/數字上添加屬性es6

0.1 + 0.2 === 0.3 // false ?

0.1 + 0.2 != 0.3背後的原理

  • JS 採用 IEEE 754雙精度64位存儲數據,因此並不是JS獨有這個問題,採用了這個規範的語言都有

  • 64位 :1位符號位,11位指數位,52位尾數位

    JS能表示最大的整數是 2^53 - 1(52個二進制 1 ),而不是 2^52 -1

  • 0.1 和 0.2 在二進制中表現爲無限循環,因此須要在尾數位末尾處進行舍入,被稱爲精度丟失

  • 兩個數相加以後就獲得了十進制小數位末位爲4而不爲0的結果

解決方法

  • toFixed 能夠精確到某一位,捨棄小數位

  • Number.EPSILON ['epsɪlɒn] 是 JS 能表示最小精度 2^(-52)

    const isEquel = (a, b) => Math.abs(a - b) < Number.EPSILON // 相等
    複製代碼
  • 轉換成整數運算

    /** * 精確加法 */
    function add(num1, num2) {
      const num1Digits = (num1.toString().split('.')[1] || '').length;
      const num2Digits = (num2.toString().split('.')[1] || '').length;
      const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits));
      return (num1 * baseNum + num2 * baseNum) / baseNum;
    }
    add(0.1,0.2); // 0.3
    複製代碼

類型轉化與規則

轉到boolean

除了如下 5 種,其餘都被轉爲 true

  • undefined
  • null
  • 0(包括+0和-0)
  • NaN
  • "" 空字符串

toString

toString(10,2) // "1010" 輸入數字時,第二個參數可選爲進制

toString(new Date())  // Wed Jan 20 2021 20:06:24 GMT+0800 (中國標準時間)

toString([1,2]) // "1,2"
複製代碼

不少內置類型的原型上都被重寫了toString方法

判斷類型時,能夠調用Object上的toString方法如數組 ({}).toString.call(x) === '[object Array]'

valueOf

  • null 和 undefined 沒有包裝對象 不能調用valueOf
  • Number Boolean String 的 prototype 上各自實現了一個 valueOf,不過功能同樣,即返回包裝對象內部的[[primitiveValue]]值
  • 各類內置對象類型,除了 Date 原型上實現了 valueOf,返回的是形如:1536416960724 的從 1970 年 1 月 1 日午夜開始計的毫秒數 ;其餘都未自定義即 Object.prototype.valueOf,返回調用者自身。

toPrimitive

用於轉換對象到原始值的內置函數,經過Symbol.toPrimitive能夠覆寫一個對象的轉換到原始值的行爲

它有個參數 hint,通常有兩個可傳入值 "number" "string",內部根據上下文選擇傳入或不傳入

  • "number":先調用 valueOf 再 toString
  • "string":先調用 toString 再 valueOf
  • 非Date對象默認傳入"number",Date對象默認傳入"string";不過實際上都是調用了 toString

關於對象更細節的轉化,請了解 toPrimitive ECMAScript7規範中的ToPrimitive抽象操做

轉到number

  • boolean,true 轉 1 false 轉 0

  • null 轉 0,undefined 轉 NaN

  • string

    • 若是字符串只包含數字(包括十六進制格式「0x」),則將其轉換成對應的十進制。
    • 若是字符串是空,"""\n",返回0。
    • 其餘狀況轉爲 NAN
  • 對象,調用 ToPrimitive 方法,PreferredType 參數爲 "number",即

    ​ 1. 調用 valueOf 方法

    ​ 2.調用 toString 方法

    ​ 3. 轉到 string 的狀況

對象轉換爲數字實例

Number([1]) // 1

​ 1. [1].valueOf() 返回 [1]

​ 2. [1].toSting() 返回 "1"

​ 3. "1" 轉爲 1

Number([1,1]) // NaN

​ 1. [1,1].valueOf() 返回 [1,1]

​ 2. [1,,1].toSting() 返回 "1,1"

​ 3. "1,1" 轉爲 NaN

隱式轉換和valueOf、toString

js將對象轉換爲基本類型時,會調用內部函數 ToPrimitive 進行轉換,

分爲如下兩點

  • 非 Date 類型先 valueOftoString
  • Date 類型先 toStringvalueOf

考慮到這兩種類型實際都是調用了 toStringvalueOf 並未改變輸出 (除包裝類型外),

因此默認對象的隱式轉換都是調用了 toString

[] == false // true []先調用 toString轉化爲"",""取布爾是false
![] // false
{} + 1 // [object Object]1 
複製代碼

符號中的強制類型隱式轉換

a + b 的轉換

表中 object 是非包裝對象(下表能夠不看,看總結便可)

左右值組合\符號 +
string number number -> string
string boolean boolean -> string
string object object -> object.toString()
number boolean boolean -> number
number object number -> string,object -> object.toString()
boolean boolean boolean -> number
boolean object boolean -> string,object -> object.toString()
object object object -> object.toString()
null number null -> 0
null object null -> "null",object -> object.toString()
null boolean null -> 0,boolean -> number
null string null -> "null"
null null 0
undefined number undefined -> NaN
undefined object undefined -> "undefined",object -> object.toString()
undefined boolean undefined -> NaN,boolean -> number
undefined string undefined -> "undefined"
undefined undefined NaN
undefined null NaN

注意 undefined 到 number 轉換爲 NaN,而 null 轉換爲 0

經過上表的排列組合咱們看出:

1.一邊有對象(非包裝)先轉爲字符串(其實是調用 toPrimitive)

特別注意 Date 對象,它也是轉爲字符串

new Date() + 1 // "Fri Mar 19 2021 10:59:08 GMT+0800 (中國標準時間)1"
複製代碼

2.一邊是字符串,另外一邊也轉爲字符串

1 + "1" = "11"

3.一邊是布爾值,另外一邊是數字或布爾值,布爾值轉爲數字

4.一邊是 null 或 undefined,另外一邊是 字符串 或者 數字,跟着另外一邊轉;若都不是,null 或 undefined 先轉爲數字

undefined + true // NaN

null + true // 1
複製代碼

5.不斷從上往下檢索規則,直到兩邊都是字符串或者數字。

注意 +ab + a,對 a的轉換是不同的:+a 是轉換到 number,而 b + a 須要按照上述規則進行轉換後相加

a == b 的轉換

對象(非包裝)比較或者不一樣類型比較時:

​ 1.兩邊是對象對比地址
​ 2.一邊是對象先調用 toString(實際是 toPrimitive)
​ 3.一邊是布爾值轉換爲數字
​ 4.兩邊分別是數字和字符串時,字符串轉換爲數字
​ 5.不斷從上往下檢索規則,直到兩邊類型相同。

其餘狀況

null == undefined // true undefined 和 null 不與其餘假值 `==`

NaN == NaN // false 須要判斷 NaN,應該用 Number.isNaN
複製代碼

實例

  • 'true' == true // false

    1. 符合 3,true 轉爲 1
    2. 符合 4,"true" 轉爲 NaN
    3. NaN == 1 返回false
  • [] == ![] // true

    1. ![] 轉化爲 false:除了 0,"",NaN,undefined,null,其餘轉布爾時都轉爲true,再取反爲false

    2. 符合 2 和 3,[] 轉爲 "",false 轉爲 0

    3. 符合 4 ,"" 轉爲 0

    4. 0 == 0 返回 true

語言內置

關於Symbol

ES6入門教程#Symbol

關於Symbol,使用得很少,它的做用是做爲一個惟一值,做對象的鍵,防止屬性被覆蓋或覆蓋已存在屬性

如手寫 apply,爲了防止覆蓋傳入的函數上的屬性,咱們能夠用Symbol做爲鍵

function myApply(ctx,args = []){
	if(typeof this !== 'function') {
        throw new TypeError('not a function!')
    }
    const symbol = Symbol(0)
	ctx[symbol] = this
    const result = ctx[symbol](...args)
    delete ctx[symbol]
    return result
}
Fuction.prototype.apply = myApply
複製代碼

它第二個做用,它提供咱們訪問內置方法和覆寫內置行爲的可能。

Symbol上儲存着各類內置方法的鍵,

經過重寫類上的迭代器,能夠改變實例使用迭代器的行爲如

class Collection {
  *[Symbol.iterator]() {
    let i = 0;
    while(this[i] !== undefined) {
      yield this[i];
      ++i;
    }
  }
}

let myCollection = new Collection();
myCollection[0] = 1;
myCollection[1] = 2;

for(let value of myCollection) {
  console.log(value);
}
複製代碼

迭代器(iterator)

迭代器是一個對象,它符合如下規範

  • 對象上可訪問 next 函數

  • next 函數 返回 {value,done},value爲本輪迭代的值,done爲布爾值,表示迭代是否結束

  • 迭代器對象能夠經過重複調用next()顯式地迭代。 迭代一個迭代器被稱爲消耗了這個迭代器,由於它一般只能執行一次。 在產生終止值以後,對next()的額外調用應該繼續返回{done:true}。

    var it = makeIterator(['a', 'b']);
    
    it.next() // { value: "a", done: false }
    it.next() // { value: "b", done: false } 最後一個值done爲false,下一輪再next done爲true
    it.next() // { value: undefined, done: true }
    
    function makeIterator(array) {
      var nextIndex = 0;
      return {
        next: function() {
          return nextIndex < array.length ?
            {value: array[nextIndex++], done: false} :
            {value: undefined, done: true};
        }
      };
    }
    複製代碼

迭代器接口(iterator)

ES6 規定,默認的 Iterator 接口部署在數據結構的Symbol.iterator屬性,或者說,一個數據結構只要具備Symbol.iterator屬性,就能夠認爲是「可遍歷的」。

它是一個返回迭代器的函數。

咱們能夠經過 Symbol.interator 訪問

let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();

iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
複製代碼

原生具有 Iterator 接口的數據結構以下。

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函數的 arguments 對象
  • NodeList 對象

生成器函數(generator)

生成器由於出現不久後就被 async 函數取代了,我學習時已經遍及 async 語法了。

不過咱們仍是頗有必要了解它的基礎語法,由於它與迭代器有關,進而與 for of,解構語法等有關;

同時 async 是 generator 函數的語法糖,瞭解了 gennerator 的原理後 async 的原理也就很好理解了。

Generator 函數的語法

生成器和迭代器[MDN]

生成器生成什麼?生成迭代器

生成器函數(generator)function * name{} 是一個返回迭代器的函數,這個迭代器能夠自動維護本身的狀態。

基本用法

  • function 關鍵詞後添加 * ,聲明生成器函數,調用生成器函數後,返回一個迭代器;

  • yield是迭代器調用next 後的執行暫停處,繼續調用next執行到下一個next

  • yield後表達式的結果做爲 next的返回值;

  • next傳入的參數做爲上一個 next暫停處整個yield 表達式的結果

  • 生成器函數最後 return 沒有 yield 的效果,可是它會被保留在後續第一次調用next返回對象的 value

function* f() {
 for (let i=0; i<3; i++){
   if(yield i) yield 10 // 返回並記錄函數狀態
 }
 return 20
}
const iter = f()
iter.next() //第三條 {value:0,done:false}
iter.next() // {value:1,done:false}
iter.next(true) //第四條 {value:10,done:false} 上一個 yield 是 if(yield i) 傳入true,if成功,執行到 yield 10
iter.next(true) // {value:2,done:false} 上一個 yield 是 yield 10 傳入true 無影響
iter.next() //第五條 {value:20,done:true} 返回值 20 被保存了
iter.next() //{value:undefined,done:true}
複製代碼

咱們能夠經過生成器很簡便地寫 iterator 接口

class O {
  constructor(p = []) {
    p.forEach(([key, value]) => (this[key] = value))
  }
  *[Symbol.iterator]() {
    const keys = Object.keys(this)
    for (let i = 0; i < keys.length; i++) {
      yield this[keys[i]]
    }
  }
}
const c = new O([
  ['a', 1],
  ['b', 2],
  ['c', 3]
])
for (let value of c) {
  console.log(value)
}
// 1 2 3
複製代碼

yield * [Iterator] 迭代器委託

將迭代委託給另外一個迭代器

let delegatedIterator = (function* () {
  yield 'Hello!';
  yield 'Bye!';
}());

let delegatingIterator = (function* () {
  yield 'Greetings!';
  yield* delegatedIterator;
  yield 'Ok, bye.';
}());

for(let value of delegatingIterator) {
  console.log(value);
}
// "Greetings!
// "Hello!"
// "Bye!"
// "Ok, bye."
複製代碼

關於拋出錯誤,以及原型上的方法請看 Generator 函數的語法

generator函數的原理

由 switch case 組成的狀態機模型中, 除此以外,利用閉包技巧,保存生成器函數上下文信息。

Regenerator 經過工具函數將生成器函數包裝,爲其添加如 next/return 等方法。同時也對返回的生成器對象進行包裝,使得對 next 等方法的調用,最終進入由 switch case 組成的狀態機模型中。除此以外,利用閉包技巧,保存生成器函數上下文信息。

【轉向 Javascript 系列】深刻理解 Generators

簡單實現

Async / Await / Generator 實現原理

// 生成器函數根據yield語句將代碼分割爲switch-case塊,後續經過切換_context.prev和_context.next來分別執行各個case
function gen$(_context) {
  while (1) {
    switch (_context.prev = _context.next) {
      case 0:
        _context.next = 2;
        return 'result1';

      case 2:
        _context.next = 4;
        return 'result2';

      case 4:
        _context.next = 6;
        return 'result3';

      case 6:
      case "end":
        return _context.stop();
    }
  }
}

// 低配版context 
var context = {
  next:0,
  prev: 0,
  done: false,
  stop: function stop () {
    this.done = true
  }
}

// 低配版invoke
let gen = function() {
  return {
    next: function() {
      value = context.done ? undefined: gen$(context)
      done = context.done
      return {
        value,
        done
      }
    }
  }
} 

// 測試使用
var g = gen() 
g.next()  // {value: "result1", done: false}
g.next()  // {value: "result2", done: false}
g.next()  // {value: "result3", done: false}
g.next()  // {value: undefined, done: true}
複製代碼

async函數 與 generator 函數

Async / Await / Generator 實現原理

咱們知道,async 函數是 generator 函數的語法糖,它們有三點不一樣

  • async/await自帶執行器,不須要手動調用next()就能自動執行下一步
  • async函數返回值是Promise對象,而Generator返回的是生成器對象
  • await可以返回Promise的resolve/reject的值
function run(gen) {
  //把返回值包裝成promise
  return new Promise((resolve, reject) => {
    var g = gen()

    function _next(val) {
      //錯誤處理
      try {
        var res = g.next(val) 
      } catch(err) {
        return reject(err); 
      }
      if(res.done) {
        // 將最後 return 值返回
        return resolve(res.value);
      }
      //res.value包裝爲promise,以兼容yield後面跟基本類型的狀況
      Promise.resolve(res.value).then(
        val => {
          // 遞歸執行_next 達到了自動執行next功能
          _next(val);
        }, 
        err => {
          //拋出錯誤
          g.throw(err)
        });
    }
    _next();
  });
}

function* myGenerator() {
  try {
    console.log(yield Promise.resolve(1)) 
    console.log(yield 2)   //2
    console.log(yield Promise.reject('error'))
  } catch (error) {
    console.log(error)
  }
}

const result = run(myGenerator)     //result是一個Promise
//輸出 1 2 error
複製代碼

原型鏈

如何判斷數組類型?

  • xxx instanceof Array

  • xxx.construtor === Array

  • Array.isArray(xxx) === true

  • Object.prototype.toString.call(xxx) === 'object Array'

描述new的過程

​ 1. 建立一個新對象

​ 2. this 指向這個新對象

​ 3. 執行代碼,即對 this 賦值

​ 4. 返回 this

instanceof的原理

instance instanceof constructor

  • 判斷實例對象 instance__proto__ 與構造函數 constuctorprototype 是否是引用的同一個原型對象

  • 若不是,沿instance的原型鏈繼續向上找

    function myInstanceof(l, r) {
            while (l) {
                if (l.__proto__ == r.prototype) {
                    return true
                }
                l = l.__proto__
            }
            return false
    }
    複製代碼

對象的遍歷方法

  • Object.prototype.entries:返回對象自身自身的全部可枚舉的屬性名和值對的數組。

  • Object.keys():返回對象自身的全部可枚舉的屬性的鍵名。

  • JSON.stringify():只串行化對象自身的可枚舉的屬性。

  • Object.assign(): 忽略enumerablefalse的屬性,只拷貝對象自身的可枚舉的屬性。

特別注意

for in 會遍歷到原型上的屬性,須要配合 hasOwnProperty

for (let key in obj) {        
	if (obj.hasOwnProperty(key)){
		// dosomething
	}
}
複製代碼

繼承

ES5 中幾種繼承方法

JS原型鏈與繼承別再被問倒了

  • 原型鏈繼承

    將子類的原型賦值爲父類實例,缺點是子類不能改變傳入父類構造函數的參數,且當原型鏈中包含引用類型值的原型時,該引用類型值會被全部實例共享;

    Child.prototype = new Parent('parent');
    複製代碼
  • 構造函數內部繼承

    在子類構造函數內call父類函數並傳入this和參數,缺點是隻能繼承父類構造函數內賦予的屬性

    function Child(){
    	Parent.call(this,'yellow'); // 這句代碼就是藉助構造函數實現部分繼承,綁定this並執行父構造函數
    	this.type = 'child';
    }
    複製代碼
  • 組合繼承(結合原型鏈繼承和構造函數繼承)

  • 原型式繼承

    在object()函數內部, 先建立一個臨時性的構造函數, 而後將傳入的對象做爲這個構造函數的原型,最後返回了這個臨時類型的一個新實例.

    function object(o){
    	function F(){}
    	F.prototype = o;
    	return new F();
    }
    var person = {
    	friends : ["Van","Louis","Nick"]
    };
    var anotherPerson = object(person);
    複製代碼
  • 寄生式繼承

    function createAnother(original){
    	var clone = object(original);//經過調用object函數建立一個新對象
    	clone.sayHi = function(){//以某種方式來加強這個對象(加強:爲其添加屬性或方法)
    		alert("hi");
    	};
    	return clone;//返回這個對象
    }
    複製代碼
  • 組合寄生式

    • 第一步,將子類的原型賦值爲一個空對象,這個對象的原型是父類,同時修改 constructor 屬性,這一步的做用是將子類拽到父類的原型鏈上
    • 第二步,同理構造函數內部繼承:在子類構造函數內call父類函數並傳入this和參數
    function extend(subClass,superClass){
    	var prototype = object(superClass.prototype);//建立對象,這個對象的原型是父類的原型
    	prototype.constructor = subClass;//加強對象
    	subClass.prototype = prototype;//指定對象
    }
    function Father(name){
    	this.name = name;
    }
    Father.prototype.sayName = function(){
    	alert(this.name);
    };
    function Son(name,age){
    	Father.call(this,name);//繼承實例屬性,第一次調用Father()
    	this.age = age;
    }
    //Son.prototype = new Father();
    //不建立新的父類實例而是用extend完成
    extend(Son,Father);
    複製代碼

ES6 class

繼承

子類實例繼承父類實例的屬性和方法

  • 子類繼承時,在構造函數內必須調用super方法,執行了父類的構造函數(與 ES5 中構造函數繼承同理)

    class Point {
      constructor(x, y) {
        this.x = x;
        this.y = y;
      }
    }
    
    class ColorPoint extends Point {
      constructor(x, y, color) {
        super(x, y); // 不調用就報錯
        this.color = color;
      }
    }
    複製代碼
  • 原型鏈繼承

  • 構造函數做爲對象, 構造函數的屬性, 即靜態方法繼承

    // 上面兩種都是 extends 後 js 自動完成
    class A {}
    class B extends {}
    
    A.prototype // {} 無屬性
    A.prototype.__proto__ === B.prototype // true 至關於完成了組合寄生式的extends方法
    
    B.__proto__ === A // true 這樣B能夠訪問到A上靜態方法
    複製代碼

做用域

JavaScript是如何支持塊級做用域的

變量提高

  • 變量聲明: 把聲明和賦值拆解 , 聲明提高到做用域最前面 , 賦值保留在原位
  • 函數聲明: 把函數聲明 如同剪切通常, 整個提高到做用域前面(在變量聲明後面).
  • if中變量聲明,聲明會提高;函數聲明轉換爲變量聲明,聲明會提高。

let 和 var 的區別?

  • var 是函數做用域 ,let 是塊級做用域

  • var有變量提高,let無變量提高

    準確得說,let和var的建立是都會提高,可是let在原聲明前是禁止訪問的,這也是形成暫時性死區的緣由

  • let 不能在同一個做用域聲明相同的變量 而 var沒有此限制

  • let 在全局環境聲明不會掛載到window上 而 var會

let的暫時性死區

暫時性死區:指塊級做用域內,某個let聲明的變量,在聲明前的區域,沒法訪問做用域鏈上同名的變量。

JS中一個聲明且賦值語句,它有三種狀態:

​ 1.建立

建立在函數開始執行前就會完成,此時它還沒法被訪問,可是它會攔截函數上下文的訪問並報錯

let 在函數執行前只完成了建立

​ 2. 初始化

初始化後,變量能夠被訪問,此時它的值爲undefined;var 變量在函數執行前就完成建立和初始化了,

let 的初始化在原句處

​ 3. 賦值

varlet 的賦值都在原句處

閉包是什麼?

簡單來講,全部引用了自由變量且不被銷燬的函數就是閉包

自由變量就是既不是函數內參數又不是已聲明的變量

你在實踐中怎麼運用閉包?

  • 解決for循環中setTimeout打印index最終數值一致的問題
  • 回調函數使用閉包改變外部的變量
  • 儲存私有變量
    • 節流、防抖函數
    • React的高階組件

深刻閉包

做用域鏈和閉包:代碼中出現相同的變量,JavaScript引擎如何選擇

自由變量本來歸屬調用棧中 本層或本層如下 的調用上下文,但它不會隨調用上下文而銷燬

編譯器建立一個閉包的步驟以下:

​ 1. 在每一個函數執行前,它會編譯並建立一個執行上下文

​ 2. 若是發現內部有函數定義,會快速掃描這個函數,若是函數使用了自由變量,則斷定這個函數是一個閉包,並建立自由變量所屬的執行上下文的閉包對象,這是個內部對象,存儲在堆空間。

​ 3. 將自由變量掛載到閉包對象,若是後面有使用這個執行上下文的其餘自由變量,也一樣被掛載到這個執行上下文的閉包對象上;一個執行上下文對應一個閉包對象

​ 4. 沒有被內部的函數使用的變量依舊在棧上

爲何閉包會致使內存泄漏?

不被銷燬的閉包,與它相關的閉包對象都不會被銷燬。

若是閉包不被執行,那麼這個對象會一直佔用內存。

同是使用變量,爲什麼閉包就是內存泄漏?

函數中建立變量也是使用變量,閉包使用閉包變量上的變量也是使用變量,二者有着一樣用途且都佔用內存,爲什麼說後者是內存泄漏?

首先,函數執行時建立的變量是執行時才建立,隨調用上下文銷燬而銷燬;而與閉包相關的閉包對象與閉包共生,不管閉包是否執行都佔用着一塊內存。

第二,閉包在執行時,閉包變量會被使用,此時不是內存泄漏;閉包不被執行時,閉包變量沒法被外界訪問且一直佔用內存,那麼就是內存泄漏。

this

this的指向?

  • 全局環境指向window
  • 全局調用函數指向window
  • 對象調用函數指向對象
  • 箭頭函數指向外部的this

以一個變量形式調用時,this 指向window,而不是調用這個函數的上下文的 this

關於 reference 如何影響 this,請看 JavaScript深刻之從ECMAScript規範解讀this

apply、call和bind

這三者的做用?

  • apply和call用於執行一個函數並強制改變其this的指向,差異在於參數的寫法
  • bind基於傳入的參數生成強制綁定this指向的函數

事件循環

爲何要區分宏任務、微任務?

若是不將任務進行劃分,按照隊列方式執行,當大量任務執行時,某些任務的回調遲遲得不到執行(都在隊尾),就會形成應用效果上的卡頓。因此設計者將任務分爲宏任務和微任務,微任務能夠穿插在宏任務中執行。

哪些屬於宏任務、微任務

宏任務:

  • script執行

  • 事件回調

  • setTimeout/setInterval

  • requestAnimationFrame

微任務:

  • promise.then
  • MutationObserver (用於監視dom的改變)

描述一下事件循環過程

執行一個宏任務,而後執行該宏任務中產生的微任務,若是微任務中產生了微任務,那麼這個微任務也會被執行,直到微任務隊列被清空,以後開啓下一輪循環

關於事件循環,更詳細請看

關於Promise和async的考題

async function foo() {
    console.log('foo')
}
async function bar() {
    console.log('bar start')
    await foo()
    console.log('bar end')
}
console.log('script start')
setTimeout(function () {
    console.log('setTimeout')
}, 0)
bar();
new Promise(function (resolve) {
    console.log('promise executor')
    resolve();
}).then(function () {
    console.log('promise then')
})
console.log('script end')
複製代碼

​ 1. 首先在主協程中初始化異步函數foo和bar,碰到console.log打印script start;

​ 2. 解析到setTimeout,初始化一個Timer,建立一個新的task

​ 3. 執行bar函數,將控制權交給協程,輸出bar start,碰到await,執行foo,輸出foo,建立一個 Promise返回給主協程

​ 4. 將返回的promise添加到微任務隊列,向下執行 new Promise,輸出 promise executor,返回resolve 添加到微任務隊列

​ 5. 輸出script end

​ 6. 當前task結束以前檢查微任務隊列,執行第一個微任務,將控制器交給協程輸出bar end

​ 7. 執行第二個微任務 輸出 promise then

​ 8. 當前任務執行完畢進入下一個任務,輸出setTimeout

特別注意 await 非 Promise 的值時,它會隱式建立 Promise 實例並 resolve 這個值

Promise的api

Promise[MDN]

Promise.all(iterable)

這個方法返回一個新的promise對象,該promise對象在iterable參數對象裏全部的promise對象都成功的時候纔會觸發成功,一旦有任何一個iterable裏面的promise對象失敗則當即觸發該promise對象的失敗。這個新的promise對象在觸發成功狀態之後,會把一個包含iterable裏全部promise返回值的數組做爲成功回調的返回值,順序跟iterable的順序保持一致;若是這個新的promise對象觸發了失敗狀態,它會把iterable裏第一個觸發失敗的promise對象的錯誤信息做爲它的失敗錯誤信息。Promise.all方法常被用於處理多個promise對象的狀態集合。

Promise.race(iterable)

首先 race 的返回值是一個 promise;當 iterable 參數裏的任意一個 promise 成功或失敗後,race 將這個 promise 的成功返回值或失敗詳情做爲參數傳入 race 返回的 promise 的 resolve 或 reject 中。

因此數組內的Promise實例,誰執行的快,就繼承誰的執行結果和執行狀態,無論是成功仍是失敗

//race方法 
Promise.race = function(promises){
  return new Promise((resolve,reject)=>{
    for(let i=0;i<promises.length;i++){
      promises[i].then(resolve,reject)
    };
  })
}
//all方法(獲取全部的promise,都執行then,把結果放到數組,一塊兒返回)
Promise.all = function(promises){
  let arr = [];
  let i = 0;
  function processData(index,data){
    arr[index] = data;
    i++;
    if(i == promises.length){
      resolve(arr);
    };
  };
  return new Promise((resolve,reject)=>{
    for(let i=0;i<promises.length;i++){
      promises[i].then(data=>{
        processData(i,data);
      },reject);
    };
  });
}

複製代碼

Promise 符合規範的實現

史上最最最詳細的手寫Promise教程

// 來源 
class Promise{
  constructor(executor){
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    };
    let reject = reason => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    };
    try{
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled,onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
    let promise2 = new Promise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      if (this.state === 'rejected') {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0)
        });
      };
    });
    return promise2;
  }
  catch(fn){
    return this.then(null,fn);
  }
}
function resolvePromise(promise2, x, resolve, reject){
  if(x === promise2){
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  let called;
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      let then = x.then;
      if (typeof then === 'function') { 
        then.call(x, y => {
          if(called)return;
          called = true;
          resolvePromise(promise2, y, resolve, reject);
        }, err => {
          if(called)return;
          called = true;
          reject(err);
        })
      } else {
        resolve(x);
      }
    } catch (e) {
      if(called)return;
      called = true;
      reject(e); 
    }
  } else {
    resolve(x);
  }
}
複製代碼

拓展:vue異步批量更新

Vue源碼詳解之nextTick:MutationObserver只是浮雲,microtask纔是核心!

數據被修改觸發setter函數,修改被watcher收集了,watcher將本身放入待更新的數組,在這次宏任務中的調用了this.$nextTick中的回調函數也被收集入回調的數組中;宏任務結束後,nextTick回調數組在微任務執行,nextTick回調數組中的第一個執行的函數就是Watcher數組先去通知更新vm實例更新,以後就按順序執行被收集的nextTick回調。

調用微任務形式是:Promise.resolve 或 MutationObersever

手寫代碼

手寫EmitEvent

class EventEmitter {
  constructor() {
    this.events = {}
  }
  on(eventName, callback = () => {}, once = false) {
    // const name = !!once ? 'onceEvents' : 'events'
    if (typeof callback !== 'function')
      throw new Error('callback must be a function')
    if (typeof eventName !== 'string')
      throw new Error('eventName must be a string')
    if (this.events[eventName] === undefined) this.events[eventName] = []
    this.events[eventName].push({
      callback,
      once
    })
  }
  once(eventName, callback) {
    this.on(eventName, callback, true)
  }
  off(eventName, callback) {
    if (typeof eventName !== 'string')
      throw new Error('eventName must be a string')
    const nameOfevent = this.events[eventName]
    if (Array.isArray(nameOfevent)) {
      this.events[eventName] =
        typeof callback !== 'function'
          ? []
          : nameOfevent.filter(e => e.callback !== callback)
    }
  }
  emit(eventName, ...args) {
    if (typeof eventName !== 'string')
      throw new Error('eventName must be a string')
    const nameOfevent = this.events[eventName]
    if (Array.isArray(nameOfevent)) {
      for (const e of nameOfevent) {
        e.callback.apply(this, args)
      }
      this.events[eventName] = nameOfevent.filter(e => !e.once)
    }
  }
}
複製代碼

手寫節流 throttle

節流函數的原理:

  • 閉包存儲私有變量lock
  • 函數運行後上鎖,並設置定時器解鎖

複雜版, JavaScript專題之跟着 underscore 學節流

//節流函數


// 簡潔版
function throttle(fn, { interval = 500 } = {}) {
  let lock = false
  return function (...args) {
    if (lock) return false
    lock = true
    setTimeout(() => {
      lock = false
    }, interval)
    return fn.apply(this, args)
  }
}


export function throttle(fn, { interval = 500 } = {}) {
	if (typeof fn != "function") return new Error("類型錯誤");
	const _self = fn;
	let timer,
		firstTime = true; // 是否第一次調用
	return function(...args) {
		const _me = this;
		if (firstTime) {
			fn.apply(_me, args);
			return (firstTime = false);
		}
		if (timer) {
			return false;
		}
		timer = setTimeout(() => {
			clearTimeout(timer);
			timer = null;
			_self.apply(_me, args);
		}, interval);
	};
}
複製代碼

手寫防抖 debounce

防抖函數計時器版原理:

  • 用閉包保存計時器引用 timer
  • 調用時清除計時器
  • 生成新的計時器

記錄前一次運行的時間

複雜版,JavaScript專題之跟着underscore學防抖

// 開始執行方案 只執行第一次
function debounce(fn, { immediate = 500 } = {}) {
  let timestamp = 0
  return function (...args) {
    const pre = timestamp
    timestamp = Date.now()
    // if (!pre) return
    if (timestamp - pre >= immediate) return fn.apply(this, args)
  }
}

// 延遲執行方案 只執行最後一次 且最後一次也延遲
function debounce(fn, delay) {
  let timer = null
  return function (...args) {
    if (timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(() => fn.apply(this, args), delay)
  }
}

// [JavaScript專題之跟着underscore學防抖] https://github.com/mqyqingfeng/Blog/issues/22
function debounce(func, wait, immediate) {

    var timeout;

    return function () {
        var context = this;
        var args = arguments;

        if (timeout) clearTimeout(timeout);
        if (immediate) {
            // 若是已經執行過,再也不執行
            var callNow = !timeout;
            timeout = setTimeout(function(){
                timeout = null;
            }, wait)
            if (callNow) func.apply(context, args)
        }
        else {
            timeout = setTimeout(function(){
                func.apply(context, args)
            }, wait);
        }
    }
}
複製代碼

手寫apply和bind

先實現apply再實現bind

apply

function myApply(ctx,args = []){
	if(typeof this !== 'function') {
        throw new TypeError('not a function!')
    }
    const symbol = Symbol(0)
	ctx[symbol] = this
    const result = ctx[symbol](...args)
    delete ctx[symbol]
    return result
}
Fuction.prototype.apply = myApply
複製代碼

bind

function myBind(ctx,...preArgs){
    const fn = this
    return (...args)=> fn.apply(ctx,[...preArgs,...args])
}
Function.prototype.bind = myBind
複製代碼

手寫curry化

curry化的做用:固定函數參數,減小參數的輸入,參數的私有化;提升函數參數適用性,減小通用性;

  • fn.length能夠獲得原函數的參數個數
  • 經過已接收的參數個數判斷繼續curry仍是執行
  • 注意參數的鏈接
function curry(fn, ...args) {
  // 繼續接受參數而後柯里化
  return args.length < fn.length ? (...params) => {
    return curry(fn, ...args, ...params)
  } : fn(...args)
}
複製代碼

手寫深拷貝

  • 數組和對象類型區分創造而後遞歸下去
  • 原始類型直接返回
// 簡單版
function deepCopy(obj) {
	if (typeof obj == "object") {
		const result = obj.constructor === Array ? [] : {}
		for (let key in obj) {
			if (obj.hasOwnProperty(key)) result[key] = deepCopy(obj[key])
		}
		return result
	} else return obj;
}

// 循環引用如何解決 ?
function deepCopy(obj) {
  // 使用 map 標記對象避免無限循環
  const map = new Map()

  function traverse(obj) {
    if (typeof obj == 'object' && !map.get(obj)) {
      map.set(obj, true)
      const result = obj.constructor === Array ? [] : {}
      for (let key in obj) {
        if (obj.hasOwnProperty(key)) result[key] = traverse(obj[key])
      }
      return result
    } else return obj
  }
  return traverse(obj)
}
複製代碼

手寫virtual Dom 生成真實 Dom節點

思路與深拷貝如出一轍,都是遞歸遍歷

result[key] = deepCopy(obj[key]) 替換成了 el.appendChild(createElement(child))

// 假設虛擬dom的結構
// {
// tag:'div', // 元素標籤
// attrs:{ // 屬性
// class:'a',
// id:'b'
// },
// text:'我是內容', // 文本內容
// children:[] // 子元素
// }

function createElement(virtualDom) {
  const { tag, attrs, text, children } = virtualDom
  const el = document.createElement(tag)
  Object.keys(attrs).forEach(key => el.setAttribute(key, attrs[key]))
  if (text !== null || text !== undefined) el.innerText = text
  for (let child of children) {
    el.appendChild(createElement(child))
  }
  return el
}
複製代碼

手寫new

  • 內置this
  • 設置this原型
  • 執行函數
  • 返回值判斷
// 用於觸發微任務的觸發器類 正常使用setTimeout便可
class MicTaskTrigger {
  constructor(callback = () => {}) {
    this.counter = 1
    this.node = document.createTextNode(String(this.counter))
    this.callback = callback
    this.observer = new MutationObserver(() => {
      this.callback()
    })
    this.observer.observe(this.node, {
      characterData: true
    })
  }
  changeCallback(callback) {
    this.callback = callback
  }
  trigger(callback = () => {}) {
    this.callback = callback
    this.counter = (this.counter + 1) % 2
    this.node.data = String(this.counter)
  }
}

const mic = new MicTaskTrigger() // mic 是用於觸發微任務的觸發器 正常使用setTimeout便可
class MyPromise {
  constructor(fn) {
    // 三個狀態
    this.state = 'pending' // fulfilled rejected
    this.value = undefined
    this.reason = undefined
    this.ResolvedCallbacks = []
    this.RejectedCallbacks = []
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled'
        this.value = value
        mic.trigger(() =>
          this.ResolvedCallbacks.forEach(callback =>
            callback.call(this, this.value)
          )
        )
      }
    }
    let reject = value => {
      if (this.state === 'pending') {
        this.state = 'rejected'
        this.reason = value
        if (this.RejectedCallbacks.length)
          mic.trigger(() => {
            this.RejectedCallbacks.forEach(callback =>
              callback.call(this, this.reason)
            )
          })
        else throw this.reason
      }
    }
    // 自動執行函數
    try {
      fn.call(this, resolve, reject)
    } catch (e) {
      reject(e)
    }
  }
  // then
  then(onFulfilled, onRejected) {
    if (typeof onFulfilled === 'function') {
      // 若是狀態已經肯定了,調用then時直接執行回調
      if (this.state === 'fulfilled')
        return mic.trigger(() => onFulfilled.call(this))
      this.ResolvedCallbacks.push(onFulfilled)
    }
    if (typeof onRejected === 'function') {
      if (this.state === 'rejected')
        return mic.trigger(() => onRejected.call(this))
      this.RejectedCallbacks.push(onRejected)
    }
  }
  catch(onRejected) {
    this.then(null, onRejected)
  }
}

const promise = new MyPromise((resolve, reject) => {
  console.log(111)
  resolve({ data: 100 })
})
promise.then(res => console.log(++res.data))
promise.then(res => console.log(++res.data))
複製代碼

手寫Promise

  • 完成了 異步執行回調,then 和 catch 單次調用傳入回調,狀態固定後調用 then 直接回調;

    沒有鏈式調用,沒有處理返回值是 Promise 的狀況,沒有錯誤冒泡 和 then 內的錯誤處理

  • 狀態

    • state 當前狀態
    • value 調用 resolve 傳入的值,即成功的結果
    • reason 調用 reject 傳入的值,即失敗的結果
    • ResolvedCallbacks 收集成功的回調
    • RejectedCallbacks 收集失敗的回調
  • 構造函數內

    • 定義 resolved 和 rejected,做用是在 pending 狀態下,改變狀態並將回調函數放入事件隊列中

    • 在 try-catch 中執行用戶傳入的函數,並傳入以上兩個函數,交予用戶改變狀態權力

    • catch 中調用 reject

  • then 函數中收集傳入的成功和失敗回調;若是是狀態已定,直接將傳入的回調函數放到隊列中,無需收集

  • catch 執行 this.then(null, onRejected)

// 用於觸發微任務的觸發器類 正常使用setTimeout便可
class MicTaskTrigger {
  constructor(callback = () => {}) {
    this.counter = 1
    this.node = document.createTextNode(String(this.counter))
    this.callback = callback
    this.observer = new MutationObserver(() => {
      this.callback()
    })
    this.observer.observe(this.node, {
      characterData: true
    })
  }
  changeCallback(callback) {
    this.callback = callback
  }
  trigger(callback = () => {}) {
    this.callback = callback
    this.counter = (this.counter + 1) % 2
    this.node.data = String(this.counter)
  }
}

const mic = new MicTaskTrigger() // mic 是用於觸發微任務的觸發器 正常使用setTimeout便可
class MyPromise {
  constructor(fn) {
    // 三個狀態
    this.state = 'pending' // fulfilled rejected
    this.value = undefined
    this.reason = undefined
    this.ResolvedCallbacks = []
    this.RejectedCallbacks = []
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled'
        this.value = value
        mic.trigger(() =>
          this.ResolvedCallbacks.forEach(callback =>
            callback.call(this, this.value)
          )
        )
      }
    }
    let reject = value => {
      if (this.state === 'pending') {
        this.state = 'rejected'
        this.reason = value
        if (this.RejectedCallbacks.length)
          mic.trigger(() => {
            this.RejectedCallbacks.forEach(callback =>
              callback.call(this, this.reason)
            )
          })
        else throw this.reason
      }
    }
    // 自動執行函數
    try {
      fn.call(this, resolve, reject)
    } catch (e) {
      reject(e)
    }
  }
  // then
  then(onFulfilled, onRejected) {
    if (typeof onFulfilled === 'function') {
      // 若是狀態已經肯定了,調用then時直接執行回調
      if (this.state === 'fulfilled')
        return mic.trigger(() => onFulfilled.call(this, this.value))
      this.ResolvedCallbacks.push(onFulfilled)
    }
    if (typeof onRejected === 'function') {
      if (this.state === 'rejected')
        return mic.trigger(() => onRejected.call(this, this.reason))
      this.RejectedCallbacks.push(onRejected)
    }
  }
  catch(onRejected) {
    this.then(null, onRejected)
  }
}

const promise1 = new MyPromise((resolve, reject) => {
  console.log(111)
  setTimeout(() => resolve({ data: 100 }), 5000)
})
promise1.then(res => console.log(++res.data))
promise1.then(res => console.log(++res.data))
複製代碼

數組扁平化

let arr = [[1,2,2], [6,7,8, [11,12, [12,13,[14]]], 10]]
// 原生
arr = arr.flat(infinity)

// 遞歸法
function flatten(arr){
    let res = []
    arr.forEach(item => {
		// 判斷item是否爲數組
        if(Array.isArray(item)) res = res.concat(flatten(item))
        else res.push(item)
    })
    return res
}
複製代碼

setTimeout 實現 setInterval

由於事件循環的機制,setInterval 可能會出現兩次或屢次任務執行間隔遠小於設置的間隔時間的狀況

好比,在設置 setInterval 執行後,執行一個密集計算的任務;第一個時間點,setInterval的一個回調推入宏任務隊列,此時密集計算任務仍未完成;到第二個時間點, setInterval的第二個回調推入宏任務隊列,此時宏任務隊列中,兩個任務是連着的,最終致使兩個任務連續執行而遠小於設置間隔的狀況。

setTimeout實現setInterval原理是setTimeout的回調內遞歸調用,能夠保證兩個任務的執行間隔至少大於設置的間隔。

詳細能夠看 《JavaScript高級程序設計》22.3 高級定時器

// 簡單實現
function mySetInterval(fn, millisec){
  function interval(){
    setTimeout(interval, millisec);
    fn();
  }
  setTimeout(interval, millisec)
}

// 加上執行次數和取消定時器,類寫法
class MySetInterval {
  constructor(fn, { interval, count = Infinity } = {}) {
    this.fn = fn
    this.interval = interval
    this.count = count
    this._count = 0 // 使用計數
    this._isOn = false
    this._timer = null
  }
  _interval() {
    this._timer = setTimeout(() => this._interval(), this.interval)
    this.fn()
    if (++this._count === this.count) this.off()
  }
  on() {
    if (this._isOn) return
    this._isOn = true
    this._timer = setTimeout(() => this._interval(), this.interval)
  }
  off() {
    if (!this._isOn) return
    this._isOn = false
    this._count = 0
    clearTimeout(this._timer)
    this._timer = null
  }
}

const itt = new MySetInterval(()=>console.log(111),{interval:1000,count:5 })
itt.on() // 111 * 5
itt.on() // 111 * 3
itt.off() // 中止後續
複製代碼

手寫響應式

let obj = {}
let input = document.getElementById('input')
let span = document.getElementById('span')
// 數據劫持
Object.defineProperty(obj, 'text', {
  configurable: true,
  enumerable: true,
  get() {
    console.log('獲取數據了')
  },
  set(newVal) {
    console.log('數據更新了')
    input.value = newVal
    span.innerHTML = newVal
  }
})
// 輸入監聽
input.addEventListener('keyup', function(e) {
  obj.text = e.target.value
})
複製代碼

實現 v-show

如何用原生 JS 實現一個最簡單的 v-show 指令?

看到題目不要慌了,考察的仍是上面的響應式

<button onClick="model.isShow = true">顯示</button>
<button onClick="model.isShow = false">隱藏</button>
 
<div v-show="isShow">Hello World!</div>
 
<script> // 第 1 步: 定義數據和視圖 var model = { isShow: false } var view = document.querySelector('div') // 第 2 步: 定義視圖刷新方法 var updateView = function(value) { view.style.display = value ? '' : 'none' } // 第 3 步: 設置初始視圖表現 var directiveKey = view.getAttribute('v-show') updateView(model[directiveKey]) // 第 4 步: 監聽數據變化,而後刷新視圖,達到數據驅動的目的 Object.defineProperty(model, 'isShow', { set: function(val) { updateView(val) } }) </script>
複製代碼

CSS與HTML

如何理解html語義化?

  • 增長代碼可讀性
  • 有利於搜索引擎爬蟲分析
  • 在css加載失敗的狀況下也能呈現完整的頁面結構

CSS選擇器與權重

CSS選擇器的權重詳解

選擇器 表達式或示例 說明 權重
ID選擇器 #aaa 100
類選擇器 .aaa 10
標籤選擇器 h1 元素的tagName 1
屬性選擇器 [title] 詳見這裏 10
相鄰選擇器 selecter + selecter 拆分爲兩個選擇器再計算
兄長選擇器 selecter ~ selecter 拆分爲兩個選擇器再計算
親子選擇器 selecter > selecter 拆分爲兩個選擇器再計算
後代選擇器 selecter selecter 拆分爲兩個選擇器再計算
通配符選擇器 * 0
各類僞類選擇器 如:link, :visited, :hover, :active, :target, :root, :not等 10
各類僞元素 如::first-letter,::first-line,::after,::before,::selection 1
  • 1,0,0,0 > 0,99,99,99。也就是說從左往右逐個等級比較,前一等級相等才日後比。
  • 不管是行間、內部和外部樣式,都是按照這個規則來進行比較。而不是直觀的行間>內部>外部樣式;ID>class>元素。之因此有這樣的錯覺,是由於確實行間爲第一等的權重,因此它的權重是最高的。而內部樣式可能通常寫在了外部樣式引用了以後,因此覆蓋掉了以前的。
  • 在權重相同的狀況下,後面的樣式會覆蓋掉前面的樣式。
  • 通配符、子選擇器、相鄰選擇器等的。雖然權值爲0000,可是也比繼承的樣式優先,0 權值比無權值優先。

盒子模型 border-box 和 content-box的區別?

  • offsetWidth = border + padding + width

  • 當設置 box-sizing:border-box 時,offserWidth = width = border + padding + content,content是剩餘下來的空間

    IE盒子模型默認 border-box

magin疊加

兩個垂直外邊距相遇時,他們將合爲一個外邊距

  • 兄弟節點 margin-top 和 margin-bottom 會疊加

  • 父子節點 margin-top 疊加 或者 margin-bottom 疊加

  • 一個元素沒有內容,內邊距和邊框,它的margin-top 和 margin-bottom 會疊加

    margin-top:20px
    margin-bottom:20px
    疊加後 20px
    複製代碼
  • 上一種狀況下,疊加後的垂直邊距與其餘元素的邊距相遇後也一樣會發生疊加

    如:多個空內容的p標籤發生疊加的狀況

    <p><p>
    <p>1<p>
    <p><p>
    
    這幾個段落最終效果是隻顯示 <p>1<p> 
    由於其餘p標籤無內容,疊加後就消失了
    複製代碼
  • 接第二種狀況,父子節點垂直邊距疊加完後,仍會與父節點的兄弟節點疊加

解決:使用 BFC 包裹兄弟節點中的一個能夠消除疊加的狀況

margin負值問題

  • margin-left、margin-top 爲負,影響自身,自身陷入前面的元素
  • margin-right、margin-bottom 爲負,影響後面的元素,後面的元素陷入自身

BFC

格式化上下文[MDN]

BFC是什麼?它的特色是什麼?

BFC(block format content)塊級格式化上下文,盒模型佈局的CSS渲染模式,指一個獨立的渲染區域或者說是一個隔離的獨立容器。

BFC生成了新的渲染層,它能夠解決同級邊距摺疊的問題,由於他們根本再也不一個層面上。

特色:

  • 屬於同一個BFC的兩個相鄰容器的上下margin會重疊

  • 元素的margin-left與其包含塊的border-left相接觸

  • bfc區域不會與float元素重疊

  • 計算bfc高度時,float元素也會被計入其中

  • bfc區域內的子元素不會影響外部元素

BFC的產生條件?

  • float不爲none
  • position是absolute或者fixed
  • overflow不爲visible
  • display爲flex,inline-block等

absolute和relative定位的依據

  • relative依據自身

  • absolute依據最近的已定位(postion:relative,absolute,fixed)的祖先元素

Flex

Flex 佈局教程:語法篇

Flex 佈局教程:實例篇

flex屬性

  • flex-direction 主軸方向 row column row-reverse column-reverse
  • flex-wrap 換行
  • justify-content 主軸內容如何排布
    • flex-start 開始端對齊
    • flex-end 結束端對齊
    • center 中心端對齊
    • space-between 兩端對齊,項目之間的間隔都相等
    • space-around 每一個項目兩側的間隔相等。因此,項目之間的間隔比項目與邊框的間隔大一倍
  • align-items 交叉軸如何對齊
    • flex-start:交叉軸的起點對齊。
    • flex-end:交叉軸的終點對齊。
    • center:交叉軸的中點對齊。
    • baseline: 項目的第一行文字的基線對齊。
    • stretch(默認值):若是項目未設置高度或設爲auto,將佔滿整個容器的高度。
  • align-content 多行的元素如何對齊
  • self-align 單個item的交叉軸如何對齊 屬性與 align-items同樣

flex:1?

css彈性盒子-------桃園三兄弟之:flex-grow、flex-shrink、flex-basis詳解

flex: 1 分配父盒子的主軸大小,它實際上是三種屬性的簡寫

  • flex-grow: 1;
  • flex-shrink: 1;
  • flex-basis: auto;

如下默認 flex-direaction : row,主軸上的大小爲 寬度

flex-basis

語義是盒子基礎的寬度,

肯定一個子盒子的寬度,優先級級比 width 高,好比 flex-basis:200px;width:100px,優先 flex-basis 的 200px 生效

flex-grow

語義是盒子如何 增大

當父元素的寬度大於子元素寬度之和時,子元素如何分配父元素的剩餘寬度,也就是 會比 basis(基礎) 的大小grow up(增大)

flex:1 能均分父盒子就是 flex-grow 在起做用

公式:

剩餘寬度 = 父級的寬度 - 各個子元素的 flex—basis之和

自身在flex - grow 的佔比 = 自身的 flex-grow /各個子元素 flex-grow 之和

寬度 = flex-basis + 剩餘寬度 * flex-grow佔比

flex-shrink

語義是盒子如何 收縮

當父元素的寬度小於於子元素寬度之和時,子元素如何縮小超出父元素的多餘寬度,也就是 會比 basis(基礎) 的大小shrink(縮小)

多餘寬度 = 各個子元素的 flex—basis之和 - 父級的寬度

flex-shrink 加權 佔比 = 自身的 flex-shrink 的加權 / 各個子元素的 flex-shrink 的加權之和

權重就是 flex-basis,flex-shrink加權 = flex-shrink * flex-basis

公式:寬度 = flex-basis - 多餘寬度 * flex-shrink加權佔比

居中

(水平、垂直、水平垂直) 居中

  • position + margin(適用於有對應的寬高)

    .h{
        position:absolute;
        left:50%;
        margin-left:-25px; /* 盒子寬度的一半 */    
    }
    
    .v{
        position:absolute;
        top:50%;
        margin-top:-25px; /* 盒子高度寬度的一半 */    
    }
    .vh{
        position:absolute;
        top:50%;
        left:50%
        margin-top:-25px;
        margin-left:-25px;
    }
    複製代碼
  • position + tansform

    .h{
        position:absolute;
        left:50%;
        transform:translate(-50%,0);    
    }
    
    .v{
        position:absolute;
        top:50%;
        transform:translate(0,-50%);    
    }
    .vh{
        position:absolute;
        top:50%;
        left:50%
        transform:translate(-50%,-50%);
    }
    複製代碼
  • flex

    .f{
        /* 父盒子 */
        display:flex
    }
    
    .h{
        justify-content:center    
    }
    
    .v{
        align-items:center    
    }
    .vh{
        justify-content:center;
        align-items:center;
    }
    複製代碼

水平居中獨有的兩種

  • margin: 0 auto; 適用於寬度肯定的子盒子

  • 轉換爲行內塊元素

    .h{
      display:inline-block;
      text-align:center;
    }
    複製代碼

垂直居中獨有的

  • line-height 設置爲 height 大小

水平垂直居中獨有的

.vh{
    /* 能夠保證瀏覽器兼容性 */
    position:absolute;
    left:0;
    right:0;
    top:0;
    bottom:0;
    margin:auto;
}
複製代碼

浮動

清除浮動

給後面元素加上

.clear{
	clear:all
}
複製代碼

給父盒子加上

.clear:after{
	clear:all
}
複製代碼

給父盒子加上overflow 觸發bfc(計算bfc高度時,float元素也會被計入其中)

.box{
	overflow:hidden
}
複製代碼

line-height如何繼承?

  • 直接寫大小如:18px或20px,直接繼承

  • 直接寫比例如:1或者1.5,直接繼承

  • 寫百分比時如200%,先換算成父元素line-height大小再繼承此大小而不是繼承百分比

    .f{
    	font-size:20px;
    	line-height:200%;
    }
    .son{
    	font-size:16px;
    }
    /* 子元素的line-height是40px */
    複製代碼

移動端

rem是什麼?

px:絕對像素

em:根據父元素的font-size肯定

rem:根據根元素html的font-size肯定

如何實現響應式?

css響應式

/* 根據屏幕寬度在media query 中設置 html的font-size */
@media only screen and (max-width: 320px){
    html {
        font-size: 5px !important;
    }
}
@media only screen and (min-width: 320px){
    html {
        font-size: 5px !important;
    }
}
@media only screen and (min-width: 384px){
    html {
        font-size: 6px !important;
    }
}
@media only screen and (min-width: 480px){
    html {
        font-size: 7.5px !important;
    }
}
/* 後續代碼rem爲單位時,1rem = 5px */
複製代碼

js動態設置

// 提早執行,初始化 resize 事件不會執行
setRem()
// 原始配置
function setRem () {
  let doc = document.documentElement
  let width = doc.getBoundingClientRect().width
  let rem = width / 75
  doc.style.fontSize = rem + 'px'
}
// 監聽窗口變化
addEventListener("resize", setRem)
複製代碼

vh 和 vw理解

首先理解 屏幕視口高度 [ window.screen.height ] 和 網頁視口高度 [ window.innerHeight ]

前者是整個手機屏幕的高度,後者是去除導航欄等高度以後用於顯示網頁內容的高度;

window.innerHeight = 100vh

xcss

rpx是如何計算的?

小程序編譯後,rpx會作一次px換算。換算是以375個物理像素爲基準,也就是在一個寬度爲375物理像素的屏幕下,1rpx = 1px。

舉個例子:iPhone6屏幕寬度爲375px,共750個物理像素,那麼1rpx = 375 / 750 px = 0.5px。

相關文章
相關標籤/搜索