天天看點

尚矽谷JS進階筆記JS進階1、資料類型2、記憶體3、函數4、this5、分号問題6、原型 prototype7、變量提升與函數提升8、執行上文9、作用域和作用域鍊10、循環周遊加監聽11、閉包12、對象建立模式13、原型鍊繼承

目錄

  • JS進階
  • 1、資料類型
    • 1.1、分類
    • 1.2、判斷
    • 1.3、類型對象和執行個體對象的差別
    • 1.4、`undefined` 和 `null` 的差別
  • 2、記憶體
    • 2.1、資料
    • 2.2、什麼是記憶體
    • 2.3、練習
  • 3、函數
    • 3.1、函數調用
    • 3.2、回調函數
    • 3.3、IIFE
  • 4、this
  • 5、分号問題
  • 6、原型 `prototype`
    • 6.1、什麼是原型對象
    • 6.2、顯式原型屬性和隐式原型屬性
    • 6.3、原型鍊
    • 6.4、構造函數/原型/實體對象的關系
    • 6.5、結論
    • 6.6、原型鍊屬性問題
    • 6.7.instanceof
    • 6.8、面試題
  • 7、變量提升與函數提升
  • 8、執行上文
    • 8.1、代碼分類
    • 8.2、 全局執行上下文
    • 8.3、函數執行上下文
    • 8.4、執行上下文棧
    • 8.5、面試題
  • 9、作用域和作用域鍊
    • 9.1、分類
    • 9.2、作用
    • 9.3、作用域與執行上下文的差別
    • 9.4、面試題
  • 10、循環周遊加監聽
  • 11、閉包
    • 11.1、如何産生閉包
    • 11.2、常見閉包
    • 11.3、作用
    • 11.4、生命周期
    • 11.4、應用
    • 11.5、閉包的缺點
    • 11.6、面試題
  • 12、對象建立模式
  • 13、原型鍊繼承

JS進階

1、資料類型

1.1、分類

  • 基本類型
    • String

    • Number

    • boolean

    • undefined

    • null

  • 對象類型
    • Object

    • Function

    • Array

1.2、判斷

  • typeof

    • null

      ->

      object

    • Array

      ->

      object

    • 其餘都可以判斷
  • instanceof

    判斷對象的具體類型
  • ===

  • ==

var a;
console.log(a, typeof(a)); //undefined, 'undefined'
a = 'aitguigu';
console.log(typeof(a));// 'string' 
a = null 
console.log(typeof(a));// 'objcet'
           

1.3、類型對象和執行個體對象的差別

function Person(name, age) { // 構造函數 類型對象
  this.name = name;
  this.age = age;
}
var p = new Person(); // 根據類型創見的執行個體對象
           

1.4、

undefined

null

的差別

  • undefined

    定義未指派
  • null

    指派并定義且值為

    null

2、記憶體

2.1、資料

  • 特點:可傳遞、可運算

2.2、什麼是記憶體

  • 記憶體中存儲兩種資料:位址值、内部存儲的資料
  • 記憶體分類
    • 堆:對象
    • 棧:全局變量、局部變量、函數名
    尚矽谷JS進階筆記JS進階1、資料類型2、記憶體3、函數4、this5、分号問題6、原型 prototype7、變量提升與函數提升8、執行上文9、作用域和作用域鍊10、循環周遊加監聽11、閉包12、對象建立模式13、原型鍊繼承

2.3、練習

var obj1 = {name: 'Tom'};
var obj2 = obj1;
obj2.age = 12;
console.log(obj1.age);// 12
function fn(obj) {
    obj.name = 'A';
}
fn(obj1);
console.log(obj2.name); // 'A'

var a = {age: 12};
var b = a;
a = {name: 'BOB', age: 13};
b.age = 14;
console.log(b.age, a.name, a.age);// 14, 'BOB', 13;
function fn2(obj) {
    obj = {age: 15};
}
fn2(a);
console.log(a.age); // 13
           

3、函數

3.1、函數調用

  • 直接調用
  • 通過對象調用
  • new 調用
  • call() 和 apply() 可以讓任意函數成為指定對象的方法
var obj = {};
function test() {
    this.name = 'atguigu';
}
test.call(obj);
console.log(obj.name);// atguigu
           

3.2、回調函數

  • 定義且沒有調用并最終執行的函數
  • DOM

    事件回調函數
  • 定時器回調函數
  • AJAX

    請求回調函數
  • 生命周期回調函數

3.3、IIFE

  • 隐藏實作
  • 不會污染外部命名空間
  • 可以編寫JS子產品
(function() {// 匿名函數自調用,向外暴露一個全局函數
  var a = 1;
  function test() {
    console.log(a);
  }
  window.$ = function() {
    return {
        test: test    
    }
 } 
})();
$().test();
           

4、this

  • 任何函數本質上都是通過某個對象來調用
  • 所有函數内部都有一個變量

    this

    ,值是調用函數的對象,如果沒有指定,就是

    window

  • test()

    window

  • p.test()

    p

  • new test()

    新建立的對象
  • p.call(obj)

    obj

function Person() {
  getThis = function() {
    console.log(this);
  }
}
var p = new Person();
p.getThis(); // this 是p
var b = p.getThis;
b(); // this 是 window
           

5、分号問題

  • ()

    前要加分号
  • []

    前要加分号
  • JS檔案頭部沒有加分号,合并會導緻問題

6、原型

prototype

6.1、什麼是原型對象

  • 每個函數都有一個

    prototype

    屬性,它預設指向一個

    Object

    空對象,即原型對象
    • prototype

      中有一個屬性

      constructor

      ,指向函數對象
    • 空對象是指的沒有自定義屬性
尚矽谷JS進階筆記JS進階1、資料類型2、記憶體3、函數4、this5、分号問題6、原型 prototype7、變量提升與函數提升8、執行上文9、作用域和作用域鍊10、循環周遊加監聽11、閉包12、對象建立模式13、原型鍊繼承
console.log(Date.prototype, typeof Date.prototype)// Object
function fun(){}
fun.prototype.test = function(){}
console.log(fun.prototype) // Object
console.log(Date.prototype.constructor == Date) // true
           

6.2、顯式原型屬性和隐式原型屬性

  • 每個函數都有一個顯式原型屬性

    prototype

  • 每個執行個體對象都有一個隐式原型屬性

    proto

  • 對象的隐式原型的值為其對應構造函數的顯式原型的值
function Fn() { // 内部語句 Fn.prototype = {}
}
var fn = new Fn();// 内部語句 fn.__proto___ = Fn.prototype
console.log(fn.__proto__===Fn.prototype); // true
           
尚矽谷JS進階筆記JS進階1、資料類型2、記憶體3、函數4、this5、分号問題6、原型 prototype7、變量提升與函數提升8、執行上文9、作用域和作用域鍊10、循環周遊加監聽11、閉包12、對象建立模式13、原型鍊繼承

6.3、原型鍊

  • 别名:隐式原型鍊
  • 通路一個屬性時
    • 先從自身屬性中查找,找到并傳回
    • 如果沒有再沿着

      __proto__

      這條鍊向上查找,找到并傳回
    • 如果最終沒找到,傳回

      undefined

尚矽谷JS進階筆記JS進階1、資料類型2、記憶體3、函數4、this5、分号問題6、原型 prototype7、變量提升與函數提升8、執行上文9、作用域和作用域鍊10、循環周遊加監聽11、閉包12、對象建立模式13、原型鍊繼承

6.4、構造函數/原型/實體對象的關系

尚矽谷JS進階筆記JS進階1、資料類型2、記憶體3、函數4、this5、分号問題6、原型 prototype7、變量提升與函數提升8、執行上文9、作用域和作用域鍊10、循環周遊加監聽11、閉包12、對象建立模式13、原型鍊繼承
  • Foo

    是一個函數對象,是以擁有

    prototype

    屬性,預設指向

    Foo

    函數對象的原型對象
  • Foo

    同時也是一個執行個體對象,

    var Foo = new Function();

    是以擁有

    __proto__

    屬性,指向

    Function

    函數對象的原型對象
  • Function

    是一個函數對象,是以擁有

    prototype

    屬性與

    Foo

    __proto__

    屬性都指向

    Function

    函數對象的原型對象
  • Function

    同時也是一個執行個體對象,

    var Function = new Function();

    是以擁有

    __proto__

    屬性,指向

    Function

    函數的原型對象
  • Object

    是一個函數對象,是以擁有

    prototype

    屬性,預設指向

    Object

    函數對象的原型對象
  • Object

    同時也是一個執行個體對象,

    var Object = new Function();

    是以擁有

    __proto__

    屬性,指向

    Function

    函數對象的原型對象
  • Function

    的原型對象是

    Object

    的原型對象的執行個體

6.5、結論

  • 結論1:函數的顯式原型指向的對象預設是空

    Object

    執行個體對象(

    Object

    除外)
  • 結論2:所有函數都是

    Function

    的執行個體
  • 結論3:

    Object

    的原型對象是原型鍊的盡頭
// 驗證
console.log(Object.prototype instanceof Object) //false
console.log(Function.prototype instanceof Object) // true
console.log(Function.prototype === Function.__proto__) //true
console.log(Object.prototype.__proto__) // null
           

6.6、原型鍊屬性問題

  • 讀取對象屬性時會自動在原型鍊中查找
  • 設定對象屬性值時,不會自動查找原型鍊,如果目前對象中沒有此屬性,直接添加
  • 方法一般定義在原型中,屬性一般通過構造函數定義在對象本身上
// 讀取對象屬性時會自動在原型鍊中查找
function Fn() {}
Fn.prototype.a = 'xxx'
var fn = new Fn();
console.log(fn.a) // xxx

// 設定對象屬性值時,不會自動查找原型鍊,如果目前對象中沒有此屬性,直接添加
var fn2 = new Fn()
fn2.a = 'yyy'
console.log(fn2.a, fn.a) // yyy, xxx

// 方法一般定義在原型中,屬性一般通過構造函數定義在對象本身
function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.setAge = function (age) {
    this.age = age; // this這個值仍然是指向它原本屬于的對象,而不是從原型鍊上找到它時,它所屬于的對象。
}
var person = new Person('xs', 21);
person.setAge(22);
console.log(person);
var person2 = new Person('ws', 31);
person2.setAge(33);
console.log(person2);
console.log(person2.__proto__ === person.__proto__);// true
           

6.7.instanceof

  • 表達式

    A instanceof B

    • A

      執行個體對象
    • B

      構造函數
  • 如果

    B

    函數的顯式原型對象在

    A

    對象的原型鍊上,傳回

    true

    ,否則傳回

    false

function Foo(){}
var f1 = new Foo()
console.log(f1 instanceof Foo)
console.log(f1 instanceof Object)
console.log(Object instanceof Function) // true
console.log(Object instanceof Object); // true
console.log(Function instanceof Function) // true
console.log(Function instanceof Object) // true
console.log(Object instanceof Foo) // false
           

6.8、面試題

// 1
function A () {}
A.prototype.n = 1
var b = new A()
A.prototype = {
    n: 2,
    m: 3
}
var c = new A()
console.log(b.n, b.m, c.n, c.m) // 1, undefined, 2, 3

// 2
var F = function F() {}
Object.prototype.a = function() {
  console.log('a')
}
Function.prototype.b = function() {
  console.log('b')
}
var f = new F()
f.a() // a 
f.b() // 報錯
F.a() // a
F.b() // b
           

7、變量提升與函數提升

  • 通過

    var

    定義(聲明)的變量,在定義語句之前就可以通路的到,值為

    undefined

  • 通過

    function

    聲明的函數,在之前就可以調用,值為函數定義(對象)
var a = 3
function fn() {
  console.log(a)
  var a = 4;
}
fn() // undefined
           

8、執行上文

8.1、代碼分類

  • 全局代碼
  • 函數代碼(局部代碼)

8.2、 全局執行上下文

  • 在執行全局代碼前,将

    window

    确定為全局執行上下文
  • 對全局資料進行預處理
    • var

      定義的全局變量指派為

      undefined

      ,添加為

      window

      屬性
    • function

      聲明的全局函數指派并添加為

      window

      方法
    • this

      指派為

      window

  • 開始執行全局代碼

8.3、函數執行上下文

  • 在調用函數時,準備執行函數體之前,建立對應的函數執行上下文對象
  • 對局部資料進行預處理
    • 形參變量進行實參指派添加為執行上下文的屬性
    • arguments

      實參清單指派添加為執行上下文的屬性
    • var

      定義的局部變量指派為

      undefined

      添加為上下文屬性
    • function

      聲明的函數指派并添加為上下文屬性
    • this

      指派為調用函數對象
  • 開始執行函數體代碼

8.4、執行上下文棧

  • 在全局代碼執行前,JS引擎會建立一個棧來存儲管理所有的執行上下文對象
  • 在全局執行上下文

    window

    确定後,将其添加到棧中(壓棧)
  • 在函數執行上下文建立後,将其添加到棧中(壓棧)
  • 在目前函數執行完成後,将棧頂的對象移除(出棧)
  • 當所有代碼執行完後,棧中隻剩下

    window

尚矽谷JS進階筆記JS進階1、資料類型2、記憶體3、函數4、this5、分号問題6、原型 prototype7、變量提升與函數提升8、執行上文9、作用域和作用域鍊10、循環周遊加監聽11、閉包12、對象建立模式13、原型鍊繼承
var a = 10
var bar = function (x) {
    var b = 5
    foo(x + b)
}
var foo = function (y) {
    var c = 5
    console.log(a + c + y)
}
bar(10)
           

8.5、面試題

// 輸出結果是什麼 一共幾個執行上下文對象 5個
console.log("global begin: " + i) // undefined
var i = 1
foo(1)
function foo(i) {
    if (i == 4)  return
    console.log("foo begin: " + i) // 1 2 3
    foo(i + 1)
    console.log("foo end: " + i); // 3 2 1
}
console.log("global end: " + i); // 1

// 2  
// 先執行變量提升,再執行函數提升
function a() {} 
var a; 
console.log(typeof a); // function

// 3
if (!(b in window)) {
    var b = 1;
}
console.log(b) // undefined

// 4 
var c = 1
function c(c) {
  console.log(c) 
} 
c(2) // c is not a function
           

9、作用域和作用域鍊

  • 靜态的(相對于上下文對象),編寫代碼時候已經确定

9.1、分類

  • 全局作用域
  • 函數作用域
  • 沒有塊作用域(ES6有了)

9.2、作用

  • 隔離變量,不同作用域下同名變量不會有沖突

9.3、作用域與執行上下文的差別

  • 建立
    • 全局作用域之外,每個函數都會建立自己的作用域,作用域在函數定義時就已經确定,而不是在函數調用時
    • 全局上下文是在全局作用域确定後,JS代碼馬上執行前建立
    • 函數上下文是在調用函數時,函數體代碼執行前建立
  • 性質
    • 作用域是靜态的,隻要函數定義好就不會變化
    • 執行上下文是動态的,調用函數時建立,函數結束後銷毀
  • 聯系
    • 執行上下文(對象)從屬于所在作用域
    • 全局上下文對應全局作用域,函數上下文對應函數作用域

9.4、面試題

// 1 
var x = 10;
function fn() {
    console.log(x);
}
function show(f) {
    var x = 20;
    f()
}
show(fn) // 10
// 2
var fn = function () {
    console.log(fn)
}
fn() //函數

//3
var obj = {
    fn2 : function () {
        console.log(fn2)
    }
}
obj.fn2() // Uncaught ReferenceError: fn2 is not defined
           

10、循環周遊加監聽

var btns = document.getElementsByTagName('button');
// 寫法1 錯誤
for(var i = 0, length = btns.length; i < length; i++) {
  var btn = btns[i]
   btn.onclick = function() {
      alert(i + 1)
   }
}
// i 是全局作用域
console.log(i)
// 寫法2 
for(var i = 0, length = btns.length; i < length; i++) {
  var btn = btns[i]
  btn.index = i 
  btn.onclick = function() {
      alert(this.index + 1)
  }
}
console.log(i)
// 寫法3 利用了閉包
for(var i = 0, length = btns.length; i < length; i++) {
  (function(i) {
    var btn = btns[i]
    btn.onclick = function() {
        alert(i + 1)
    }
  })(i)
}
console.log(i)
           
<button>按鈕1</button>
<button>按鈕2</button>
<button>按鈕3</button>
           

11、閉包

11.1、如何産生閉包

  • 當一個嵌套的内部(子)函數引用了嵌套的外部(父)函數的變量(函數)時,就産生了閉包
  • 閉包是嵌套的内部函數
  • 包含被引用變量(函數)的對象
  • 注意:閉包存在于嵌套的内部函數
  • 産生閉包的條件
    • 函數嵌套
    • 内部函數引用了外部函數的資料(變量/函數)
  • 注意:
    • 建立閉包是再調用外部函數時建立
    • 參數和變量不會被垃圾回收機制回收
      • 垃圾回收機制,函數沒有被引用執行完以後這個函數的作用域就會被銷毀,如果一個函數被其他變量引用,這個函數的作用域将不會被銷毀(簡單來說就是函數裡面的變量會被儲存下來,可以了解成全局變量)
function fn1() {
    var a = 2
    function fn2() {
        console.log(a);
    }
}
fn1()
           

11.2、常見閉包

  • 将函數作為另外一個函數的傳回值
  • 将函數作為實參傳遞給另一個函數
function fn1() {
    var a = 2
    function fn2() {
        a++;
        console.log(a);
    }
    return fn2
}
var f = fn1();
f()  // 3
f() // 4

function showDelay(msg, time) {
    setTimeout(function () {
        alert(msg)
    }, time)
}
showDelay(1, 1000)

function fn1() {
    var a = 2
    function fn2() {
        a++;
        console.log(a);
    }
    function fn3() {
        a--;
        console.log(a);
    }
    return fn3
}
var f = fn1();
f()  // 1
f() // 0
           

11.3、作用

  • 延長了局部變量的生命周期,使用函數内部的變量在函數執行完後,仍存活在記憶體中
  • 讓函數外部可以操作(讀寫)到函數内部的資料(變量/函數)

11.4、生命周期

  • 産生:在嵌套内部函數定義執行時就産生了,不是在調用時
  • 銷毀:在嵌套的内部函數成為垃圾對象時
function fn1() {
    var a = 2
    function fn2() {
        a++;
        console.log(a);
    }
    return fn2
}
var f = fn1();
f = null
           

11.4、應用

  • 定義JS子產品
    • 具有特定功能的JS檔案
    • 将所有的資料和功能都封裝在一個函數中
    • 隻向外暴露一個包含n個方法的對象或函數
    • 子產品的使用者,隻需要通過子產品暴露的對象調用方法來實作對應功能
function myModel() {
    var msg = 'aaa'
    function doSomething() {
        console.log('doSomething()' + msg.toUpperCase())
    }
    function doOtherthing() {
        console.log('doOtherthing()')
    }
    return {
        doSomething : doSomething,
        doOtherthing : doOtherthing
    }
}
           
<script type="text/javascript" src="modelDemo.js"></script>
<script>
    var fn = myModel();
    console.log(fn.doSomething()); // doSomething() AAA
    console.log(fn.doOtherthing()); // undefined
</script>
           
  • 方法2
(function (w) {
    var msg = 'aaa'
    function doSomething() {
        console.log('doSomething()' + msg.toUpperCase())
    }
    function doOtherthing() {
        console.log('doOtherthing()')
    }
    w.myModel = {
        doSomething : doSomething,
        doOtherthing : doOtherthing
    }
})(window)
           
<script type="text/javascript" src="modelDemo2.js"></script>
<script>
    var fn = window.myModel;
    console.log(fn.doSomething());
    console.log(fn.doOtherthing());
</script>
           

11.5、閉包的缺點

  • 函數執行完後,函數的局部變量沒有釋放,占用記憶體時間會邊長
  • 容易造成記憶體洩露
    • 意外的全局變量
// 1
function fn() {
  a = 3
  console.log(a)
}
fn()

// 2
var a = setInterval(function() {
  console.log('----')
}, 1000)
// clearInterval(a)

// 閉包
           
  • 解決:
    • 盡量避免閉包
    • 及時釋放

11.6、面試題

// 面試題 1
var name =  'The Window'
var object = {
    name: 'myObject',
    getNameFunc: function () {
        return function () {
            return this.name
        }
    }
}
alert(object.getNameFunc()()); // The Window

// 面試題 2
var name2 = 'The Window'
var object2 = {
    name2: 'myObject',
    getNameFunc: function() {
      var that = this; 
      return function() {
        return that.name2; // 閉包
      }
    }
}
alert(object2.getNameFunc()())// myObject 

// 面試題 3 
function fun(n, o) {
    console.log(o);
    return {
        fun: function (m) {
            return fun(m,n)
        }
    }
}
var a = fun(0);// undefined
a.fun(1); // 0
a.fun(2); // 0
a.fun(3); // 0
var b = fun(0).fun(1).fun(2).fun(3);//undefined,1,2,3
var c = fun(0).fun(1);// undefined, 1
c.fun(2); // 1
c.fun(3); // 1 
           

12、對象建立模式

  • 構造函數式建立
    • 場景:初始時不确定對象内部資料
    • 缺點:語句太多
  • 字面量式建立
    • 場景:初始時對象内部資料确定
    • 缺點:如果建立多個對象,有重複代碼
  • 工廠模式建立
    • 場景:需要建立多個對象
    • 缺點:對象沒有一個具體的類型,都是Object類型
  • 自定義構造函數模式
    • 場景:需要建立多個類型确定的對象
    • 缺點:資料相同,浪費記憶體
  • 構造函數 + 原型 的組合模式
    • 場景:屬性在函數中初始化,方法添加到原型上
    • 缺點:需要建立多個類型确定的對象
// 1 
var obj = new Object();
obj.name = 'Joke';
obj.age = '18';
// 2
var obj2 = {name: 'Tom', age: 12}
function createPerson(name, age) {
  var obj = {
    name: name,
    age: age,
  }
  return obj;
}
// 3
var obj3 = createPerson('HHH', 22);
var obj4 = createPerson('GGG', 23);
// 4
function Person(name, age) {
    this.name = name;
    this.age = age;
}
// 5 
Person.prototype.setName = function (name) {
    this.name = name;
}
var person = new Person('AAA', 45);
console.log(obj);
console.log(obj2);
console.log(obj3);
console.log(obj4);
person.setName('張三');
console.log(person)
           

13、原型鍊繼承

  • 子類型的原型為父類型的執行個體
  • 借用構造函數
    • 在子類型構造函數中通用

      call()

      調用父類型構造函數
  • 原型鍊 + 借用構造函數的組合繼承
    • 利用原型鍊實作對父類型對象的方法繼承
    • 利用

      super()

      借用父類型建構函數初始化相同屬性
// 方式一
// 父類型
function Supper() {
    this.supProp = 'supper'
}
Supper.prototype.showSupperProp = function () {
    console.log("s >>> " + this.supProp);
}
// 子類型
function Sub() {
    this.subProp = 'sub'
}
// Sub.prototype = new Supper()
Sub.prototype.showSubProp = function () {
    console.log(this.subProp);
}
var sub = new Sub();
sub.showSupperProp() // Uncaught TypeError: sub.showSupperProp is not a function
           
尚矽谷JS進階筆記JS進階1、資料類型2、記憶體3、函數4、this5、分号問題6、原型 prototype7、變量提升與函數提升8、執行上文9、作用域和作用域鍊10、循環周遊加監聽11、閉包12、對象建立模式13、原型鍊繼承
// 方式二 
function Person(name, age) {
    this.name = name;
    this.age = age;
}
function Student(name, age, price) {
    Person.call(this, name, age)
    this.price = price;
}
var student = new Student('zs', 23, 299); // Student {name: 'zs', age: 23, price: 299}
           
// 方法三
function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.setName = function (name) {
    this.name = name
}
function Student(name, age, price) {
    Person.call(this, name, age)
    this.price = price;
}
Student.prototype = new Person();
Student.constructor = Student;
Student.prototype.setPrice = function (price) {
    this.price = price;
}
var student = new Student('zs', 29, 30);
student.setName('sss');
console.log(student); // Student {name: 'sss', age: 29, price: 30}
           

繼續閱讀