1. 程式人生 > >前端基礎知識1

前端基礎知識1

今天不知道什麼原因心血來潮,說一說JS中最最最最最基本的概念吧,但是很多人可能不知道喔。
自己有個小計劃就是希望五年內寫一門自己的語言,雖然已經有JS了。此處吹個牛逼,莫怪,莫怪。廢話不多說,還是寫心得吧。

基礎知識

(1)資料型別

  1. 分類
  • 基本(值)型別
    • String: 任意字串
    • Number: 任意的數字
    • boolean: true/false
    • undefined: undefined
    • null: null
  • 物件(引用)型別
    • Object: 任意物件
    • Function: 一種特別的物件(可以執行)
    • Array: 一種特別的物件(數值下標, 內部資料是有序的)
  1. 判斷
  • typeof:
    • 可以判斷: undefined/ 數值 / 字串 / 布林值 / function
    • 不能判斷: null與object object與array
  • instanceof:
    • 判斷物件的具體型別
  • ===
    • 可以判斷: undefined, null

(2)資料 變數 記憶體

  1. 什麼是資料?
  • 儲存在記憶體中代表特定資訊的’東東’, 本質上是0101…
  • 資料的特點: 可傳遞, 可運算
  • 一切皆資料
  • 記憶體中所有操作的目標: 資料
    • 算術運算
    • 邏輯運算
    • 賦值
    • 執行函式
  1. 什麼是記憶體?
  • 記憶體條通電後產生的可儲存資料的空間(臨時的)
  • 記憶體產生和死亡: 記憶體條(電路版)>通電>產生記憶體空間==>儲存資料==>處理資料==>斷電==>記憶體空間和資料都消失
  • 一塊小記憶體的2個數據
    • 內部儲存的資料
    • 地址值
  • 記憶體分類
    • 棧: 全域性變數/區域性變數
    • 堆: 物件
  1. 什麼是變數?
  • 可變化的量, 由變數名和變數值組成
  • 每個變數都對應的一塊小記憶體, 變數名用來查詢對應的記憶體, 變數值就是記憶體中儲存的資料
  1. 記憶體,資料, 變數三者之間的關係
  • 記憶體用來儲存資料的空間
  • 變數是記憶體的標識

問題: 在js呼叫函式時傳遞變數引數時, 是值傳遞還是引用傳遞

  • 理解1: 都是值(基本/地址值)傳遞
  • 理解2: 可能是值傳遞, 也可能是引用傳遞(地址值)
<script type="text/javascript">
  var a = 3
  function fn (a) {
    a = a +1
  }
  fn(a)
  console.log(a)

  function fn2 (obj) {
    console.log(obj.name)
  }
  var obj = {name: 'Tom'}
  fn2(obj)
</script>

問題: JS引擎如何管理記憶體?

  1. 記憶體生命週期
  • 分配小記憶體空間, 得到它的使用權
  • 儲存資料, 可以反覆進行操作
  • 釋放小記憶體空間
  1. 釋放記憶體
  • 區域性變數: 函式執行完自動釋放
  • 物件: 成為垃圾物件==>垃圾回收器回收
<script type="text/javascript">
  var a = 3
  var obj = {}
  obj = undefined

  function fn () {
    var b = {}
  }

  fn() // b是自動釋放, b所指向的物件是在後面的某個時刻由垃圾回收器回收

</script>

關於物件

  1. 什麼是物件?
  • 多個數據的封裝體
  • 用來儲存多個數據的容器
  • 一個物件代表現實中的一個事物
  1. 為什麼要用物件?
  • 統一管理多個數據
  1. 物件的組成
  • 屬性: 屬性名(字串)和屬性值(任意)組成
  • 方法: 一種特別的屬性(屬性值是函式)
  1. 如何訪問物件內部資料?
  • .屬性名: 編碼簡單, 有時不能用
  • [‘屬性名’]: 編碼麻煩, 能通用

問題: 什麼時候必須使用[‘屬性名’]的方式?

  1. 屬性名包含特殊字元: - 空格
  2. 屬性名不確定
<script type="text/javascript">
  var p = {}
  //1. 給p物件新增一個屬性: content type: text/json
  // p.content-type = 'text/json' //不能用
  p['content-type'] = 'text/json'
  console.log(p['content-type'])

  //2. 屬性名不確定
  var propName = 'myAge'
  var value = 18
  // p.propName = value //不能用
  p[propName] = value
  console.log(p[propName])
</script>

函式

  1. 什麼是函式?
  • 實現特定功能的n條語句的封裝體
  • 只有函式是可以執行的, 其它型別的資料不能執行
  1. 為什麼要用函式?
  • 提高程式碼複用
  • 便於閱讀交流
  1. 如何定義函式?
  • 函式宣告
  • 表示式
  1. 如何呼叫(執行)函式?
  • test(): 直接呼叫
  • obj.test(): 通過物件呼叫
  • new test(): new呼叫
  • test.call/apply(obj): 臨時讓test成為obj的方法進行呼叫

回撥函式

  1. 什麼函式才是回撥函式?
    1). 你定義的
    2). 你沒有調
    3). 但最終它執行了(在某個時刻或某個條件下)
  2. 常見的回撥函式?
  • dom事件回撥函式 ==>發生事件的dom元素
  • 定時器回撥函式 ===>window
  • ajax請求回撥函式
  • 生命週期回撥函式
<script type="text/javascript">
  document.getElementById('btn').onclick = function () { // dom事件回撥函式
    alert(this.innerHTML)
  }
  //定時器
    // 超時定時器
    // 迴圈定時器
  setTimeout(function () { // 定時器回撥函式

    alert('到點了'+this)
  }, 2000)
</script>

IIFE

  1. 理解
  • 全稱: Immediately-Invoked Function Expression
  • 中文名:立即執行函式表示式
  1. 作用
  • 隱藏實現
  • 不會汙染外部(全域性)名稱空間
  • 用它來編碼js模組
<script type="text/javascript">
  (function () { //匿名函式自呼叫
    var a = 3
    console.log(a + 3)
  })()
  var a = 4
  console.log(a)

  ;(function () {
    var a = 1
    function test () {
      console.log(++a)
    }
    window.$ = function () { // 向外暴露一個全域性函式
      return {
        test: test
      }
    }
  })()

  $().test() // 1. $是一個函式 2. $執行後返回的是一個物件

</script>

this

  1. this是什麼?
  • 任何函式本質上都是通過某個物件來呼叫的, 如果沒有直接指定就是window.
  • 所有函式內部都有一個變數this
  • 它的值是呼叫函式的當前物件
  1. 如何確定this的值?
  • test(): window
  • p.test(): p
  • new test(): 新建立的物件
  • p.call(obj): obj

舉例說明吧:

<script type="text/javascript">
  function Person(color) {
    console.log(this)
    this.color = color;
    this.getColor = function () {
      console.log(this)
      return this.color;
    };
    this.setColor = function (color) {
      console.log(this)
      this.color = color;
    };
  }

  Person("red"); //this是誰? window

  var p = new Person("yello"); //this是誰? p

  p.getColor(); //this是誰? p

  var obj = {};
  p.setColor.call(obj, "black"); //this是誰? obj

  var test = p.setColor;
  test(); //this是誰? window

  function fun1() {
    function fun2() {
      console.log(this);
    }

    fun2(); //this是誰? window
  }
  fun1();
</script>

函式高階

原型

  1. 函式的prototype屬性(圖)
  • 每個函式都有一個prototype屬性, 它預設指向一個Object空物件(即稱為: 原型物件)
  • 原型物件中有一個屬性constructor, 它指向函式物件
  1. 給原型物件新增屬性(一般都是方法)
  • 作用: 函式的所有例項物件自動擁有原型中的屬性(方法)
<script type="text/javascript">

  // 每個函式都有一個prototype屬性, 它預設指向一個Object空物件(即稱為: 原型物件)
  console.log(Date.prototype, typeof Date.prototype)
  function Fun () {//alt + shift +r(重新命名rename)

  }

  console.log(Fun.prototype)  // 預設指向一個Object空物件(沒有我們的屬性)

  // 原型物件中有一個屬性constructor, 它指向函式物件
  console.log(Date.prototype.constructor===Date)
  console.log(Fun.prototype.constructor===Fun)

  //給原型物件新增屬性(一般是方法) ===>例項物件可以訪問
  Fun.prototype.test = function () {
    console.log('test()')
  }
  var fun = new Fun()
  fun.test()

</script>

顯式原型 和 隱式原型

  1. 每個函式function都有一個prototype,即顯式原型(屬性)
  2. 每個例項物件都有一個__proto__,可稱為隱式原型(屬性)
  3. 物件的隱式原型的值為其對應建構函式的顯式原型的值
  4. 記憶體結構(圖)
  5. 總結:
  • 函式的prototype屬性: 在定義函式時自動新增的, 預設值是一個空Object物件
  • 物件的__proto__屬性: 建立物件時自動新增的, 預設值為建構函式的prototype屬性值
  • 程式設計師能直接操作顯式原型, 但不能直接操作隱式原型(ES6之前)
<script type="text/javascript">

  // 每個函式都有一個prototype屬性, 它預設指向一個Object空物件(即稱為: 原型物件)
  console.log(Date.prototype, typeof Date.prototype)
  function Fun () {//alt + shift +r(重新命名rename)

  }

  console.log(Fun.prototype)  // 預設指向一個Object空物件(沒有我們的屬性)

  // 原型物件中有一個屬性constructor, 它指向函式物件
  console.log(Date.prototype.constructor === Date)
  console.log(Fun.prototype.constructor === Fun)

  //給原型物件新增屬性(一般是方法) ===>例項物件可以訪問
  Fun.prototype.test = function () {
    console.log('test()')
  }
  var fun = new Fun()
  fun.test()

</script>

原型鏈

1. 原型鏈(圖解)

  • 訪問一個物件的屬性時,
    • 先在自身屬性中查詢,找到返回
    • 如果沒有, 再沿著__proto__這條鏈向上查詢, 找到返回
    • 如果最終沒找到, 返回undefined
  • 別名: 隱式原型鏈
  • 作用: 查詢物件的屬性(方法)

2. 建構函式/原型/實體物件的關係(圖解)

3. 建構函式/原型/實體物件的關係2(圖解)

    <script type="text/javascript">
      // console.log(Object)
      //console.log(Object.prototype)
      console.log(Object.prototype.__proto__)
      function Fn() {
        this.test1 = function () {
          console.log('test1()')
        }
      }
      console.log(Fn.prototype)
      Fn.prototype.test2 = function () {
        console.log('test2()')
      }
    
      var fn = new Fn()
    
      fn.test1()
      fn.test2()
      console.log(fn.toString())
      console.log(fn.test3)
      // fn.test3()
    
    
      /*
      1. 函式的顯示原型指向的物件預設是空Object例項物件(但Object不滿足)
       */
      console.log(Fn.prototype instanceof Object) // true
      console.log(Object.prototype instanceof Object) // false
      console.log(Function.prototype instanceof Object) // true
      /*
      2. 所有函式都是Function的例項(包含Function)
      */
      console.log(Function.__proto__===Function.prototype)
      /*
      3. Object的原型物件是原型鏈盡頭
       */
      console.log(Object.prototype.__proto__) // null
    
    </script>

原型鏈_屬性問題

  1. 讀取物件的屬性值時: 會自動到原型鏈中查詢

  2. 設定物件的屬性值時: 不會查詢原型鏈, 如果當前物件中沒有此屬性, 直接新增此屬性並設定其值

  3. 方法一般定義在原型中, 屬性一般通過建構函式定義在物件本身上

<script type="text/javascript">

  function Fn() {

  }
  Fn.prototype.a = 'xxx'
  var fn1 = new Fn()
  console.log(fn1.a, fn1)

  var fn2 = new Fn()
  fn2.a = 'yyy'
  console.log(fn1.a, fn2.a, fn2)

  function Person(name, age) {
    this.name = name
    this.age = age
  }
  Person.prototype.setName = function (name) {
    this.name = name
  }
  var p1 = new Person('Tom', 12)
  p1.setName('Bob')
  console.log(p1)

  var p2 = new Person('Jack', 12)
  p2.setName('Cat')
  console.log(p2)
  console.log(p1.__proto__===p2.__proto__) // true
</script>

變數提升和函式提升

1. 變數宣告提升

  • 通過var定義(宣告)的變數, 在定義語句之前就可以訪問到
  • 值: undefined

2. 函式宣告提升

  • 通過function宣告的函式, 在之前就可以直接呼叫
  • 值: 函式定義(物件)

3. 問題: 變數提升和函式提升是如何產生的?
自己想吧,哈哈哈哈!

<script type="text/javascript">
  console.log('-----')
  /*
  面試題 : 輸出 undefined
   */
  var a = 3
  function fn () {
    console.log(a)
    var a = 4
  }
  fn()

  console.log(b) //undefined  變數提升
  fn2() //可呼叫  函式提升
  // fn3() //不能  變數提升

  var b = 3
  function fn2() {
    console.log('fn2()')
  }

  var fn3 = function () {
    console.log('fn3()')
  }
</script>

函式執行上下文

1. 程式碼分類(位置)

  • 全域性程式碼
  • 函式(區域性)程式碼

2. 全域性執行上下文

  • 在執行全域性程式碼前將window確定為全域性執行上下文
  • 對全域性資料進行預處理
    • var定義的全域性變數==>undefined, 新增為window的屬性
    • function宣告的全域性函式==>賦值(fun), 新增為window的方法
    • this==>賦值(window)
  • 開始執行全域性程式碼

3. 函式執行上下文

  • 在呼叫函式, 準備執行函式體之前, 建立對應的函式執行上下文物件(虛擬的, 存在於棧中)
  • 對區域性資料進行預處理
    • 形參變數==>賦值(實參)==>新增為執行上下文的屬性
    • arguments==>賦值(實參列表), 新增為執行上下文的屬性
    • var定義的區域性變數==>undefined, 新增為執行上下文的屬性
    • function宣告的函式 ==>賦值(fun), 新增為執行上下文的方法
    • this==>賦值(呼叫函式的物件)
  • 開始執行函式體程式碼
<script type="text/javascript">
  function fn(a1){
    console.log(a1);  //2
    console.log(a2);  //undefine
    a3();  //a3
    console.log(this);  //window
    console.log(arguments); // [2,3,...........]為陣列
    var a2 = 3;
    
  function a3(){
       console.log("a3");
    }
  }
  fn(2,3);
  console.log(a1, window.a1)
  window.a2()
  console.log(this)

  var a1 = 3
  function a2() {
    console.log('a2()')
  }
  console.log(a1)
</script>

執行上下文棧

  1. 在全域性程式碼執行前, JS引擎就會建立一個棧來儲存管理所有的執行上下文物件
  2. 在全域性執行上下文(window)確定後, 將其新增到棧中(壓棧)
  3. 在函式執行上下文建立後, 將其新增到棧中(壓棧)
  4. 在當前函式執行完後,將棧頂的物件移除(出棧)
  5. 當所有的程式碼執行完後, 棧中只剩下window

舉個例子:

<script type="text/javascript">
  console.log('gb: '+ i)
  var i = 1
  foo(1)
  function foo(i) {
    if (i == 4) {
      return
    }
    console.log('fb:' + i)
    foo(i + 1) //遞迴呼叫: 在函式內部呼叫自己
    console.log('fe:' + i)
  }
  console.log('ge: ' + i)
</script>

1. 依次輸出什麼?
gb: undefined
fb: 1
fb: 2
fb: 3
fe: 3
fe: 2
fe: 1
ge: 1
2. 整個過程中產生了幾個執行上下文?
5


執行上下文棧中遇到的坑

<script type="text/javascript">

  /*
   測試題1:  先執行變數提升, 再執行函式提升
   */
  function a() {}
  var a
  console.log(typeof a) // 'function'


  /*
   測試題2:
   */
  if (!(b in window)) {
    var b = 1
  }
  console.log(b) // undefined

  /*
   測試題3:
   */
  var c = 1
  function c(c) {
    console.log(c)
  }
  c(2) // 報錯

</script>

作用域

1. 理解

  • 就是一塊"地盤", 一個程式碼段所在的區域
  • 它是靜態的(相對於上下文物件), 在編寫程式碼時就確定了

2. 分類

  • 全域性作用域
  • 函式作用域
  • 塊作用域(ES6有了)

4. 作用

  • 隔離變數,不同作用域下同名變數不會有衝突
<script type="text/javascript">
/*  //沒塊作用域
  if(true) {
    var c = 3
  }
  console.log(c)*/

  var a = 10,
    b = 20
  function fn(x) {
    var a = 100,
      c = 300;
    console.log('fn()', a, b, c, x)
    function bar(x) {
      var a = 1000,
        d = 400
      console.log('bar()', a, b, c, d, x)
    }

    bar(100)
    bar(200)
  }
  fn(10)
</script>

作用域與執行上下文

  1. 區別1
  • 全域性作用域之外,每個函式都會建立自己的作用域,作用域在函式定義時就已經確定了。而不是在函式呼叫時
  • 全域性執行上下文環境是在全域性作用域確定之後, js程式碼馬上執行之前建立
  • 函式執行上下文是在呼叫函式時, 函式體程式碼執行之前建立
  1. 區別2
  • 作用域是靜態的, 只要函式定義好了就一直存在, 且不會再變化
  • 執行上下文是動態的, 呼叫函式時建立, 函式呼叫結束時就會自動釋放
  1. 聯絡
  • 執行上下文(物件)是從屬於所在的作用域
  • 全域性上下文環境==>全域性作用域
  • 函式上下文環境==>對應的函式使用域
<script type="text/javascript">
  var a = 10,
    b = 20
  function fn(x) {
    var a = 100,
      c = 300;
    console.log('fn()', a, b, c, x)
    function bar(x) {
      var a = 1000,
        d = 400
      console.log('bar()', a, b, c, d, x)
    }

    bar(100)
    bar(200)
  }
  fn(10)
</script>

作用域鏈

  1. 理解
  • 多個上下級關係的作用域形成的鏈, 它的方向是從下向上的(從內到外)
  • 查詢變數時就是沿著作用域鏈來查詢的
  1. 查詢一個變數的查詢規則
  • 在當前作用域下的執行上下文中查詢對應的屬性, 如果有直接返回, 否則進入2
  • 在上一級作用域的執行上下文中查詢對應的屬性, 如果有直接返回, 否則進入3
  • 再次執行2的相同操作, 直到全域性作用域, 如果還找不到就丟擲找不到的異常
<script type="text/javascript">
  var a = 1
  function fn1() {
    var b = 2
    function fn2() {
      var c = 3
      console.log(c)
      console.log(b)
      console.log(a)
      console.log(d)
    }
    fn2()
  }
  fn1()
</script>