js面向?qū)ο?/h1>

title: js面向?qū)ο?br> date: 2017年8月17日 18:58:05
updated: 2017年8月27日 09:27:20


面向?qū)ο?/h1>

JavaScript 中的數(shù)據(jù)類型

值類型和引用類型復制

var foo = 'bar'  // 存的是值
var bar = foo

var obj = {  // obj 存的是地址
  foo: bar
}

var obj1 = obj // obj1 中存儲的是和 obj 一樣的地址

obj.foo = 'baz' // 地址一樣, 指向的內(nèi)用一樣, 所以修改的是同一個對象
console.log(obj, obj1)
值傳遞和引用傳遞01.png
  • 基本類型數(shù)據(jù): undefined null Boolean Number String 直接按值存放
    基本類型在內(nèi)存中占據(jù)固定大小的空間, 被保存在內(nèi)存中
    從一個變量向另一個變量復制基本類型的值 -- 復制的是值的副本

  • 引用類型數(shù)據(jù): 變量保存的是一個指針, 這個指針地址指向堆內(nèi)存中的數(shù)據(jù).
    引用類型的值是對象, 保存在內(nèi)存
    保存引用類型的變量保存的并不是對象本身, 而是一個指向該對象的指針
    從一個變量向另一個變量復制引用類型的值的時候, 復制的是引用指針, 因此兩個變量指向的是同一個對象.

值類型和引用類型參數(shù)傳遞

var a = 123
var b = {
  foo: 'bar'
}

function f(a, b) {
  a = 456  // var 形參a = 實參a 復制值
  b.foo = 'baz'  // var 形參b = 實參b 復制引用
  b = {   // b 中的地址指向新的對象 與 之前的對象斷開連接
    foo: 'bbb'
  }
}

f(a, b)
console.log(a, b)  // 123, Object{foo: 'bbb'}
  • 基本類型數(shù)據(jù): 按值傳遞
  • 引用類型數(shù)據(jù): 按引用傳遞

深拷貝與淺拷貝

  • 淺拷貝

    當拷貝對象時, 如果屬性時對象或者數(shù)組, 這時候傳遞的也只是一個地址. 兩者的屬性值指向同一內(nèi)存空間.

    var a = {
        key1:"11111"
    }
    function copy(p) {
        var c = {}
        for (var i in p) {
          c[i] = p[i]
        }
        return c
    }
    a.key2 = ['小輝','小輝']
    var b = copy(a)
    b.key3 = '33333'
    alert(b.key1)     //11111
    alert(b.key3)    //33333
    alert(a.key3)    //undefined
    alert(a.key2)    // ['小輝','小輝']
    alert(b.key2)    // ['小輝','小輝']
    b.key2.push("大輝")
    alert(b.key2)    //小輝,小輝校赤,大輝
    alert(a.key2)    //小輝掠归,小輝憔足,大輝
    
  • 深拷貝

    不希望拷貝前后的對象之間有關聯(lián), 那么這個時候就會用到深拷貝.

    function isObject(obj) {
      return Object.prototype.toString.call(obj) === '[object Object]'
    }
    
    function isArray(obj) {
      return Object.prototype.toString.call(obj) === '[object Array]'
    }
    
    // 利用遞歸實現(xiàn)深拷貝對象復制
    function extend(target, source) {
      for(var key in source) {
        // 判斷如果當前遍歷項 source[key] 是一個數(shù)組轩缤,則先讓 target[key] = 數(shù)組
        // 然后遍歷 source[key] 將其中的每一項都復制到 target[key] 中
        if (isArray(source[key])) {
          target[key] = []
          // 遍歷 source[key] 復制到 target[key] 中
          extend(target[key], source[key])
        } else if (isObject(source[key])) {
          target[key] = {}
          extend(target[key], source[key])
        } else {
          target[key] = source[key]
        }
      }
    }
    

類型檢測

  • typeof
  • instanceof -- 引用類型推薦使用
  • Object.prototype.toString.call()

javascript執(zhí)行過程

  • 預解析

    • 全局預解析

      • 所有變量個函數(shù)聲明都會提前
      • 同名的函數(shù)和變量函數(shù)的優(yōu)先級高
    • 函數(shù)內(nèi)部預解析

      • 所有的變量, 函數(shù)形參 都會參與預解析
  • 執(zhí)行

面向?qū)ο?- 創(chuàng)建對象

簡單方式

直接通過 new Object() 創(chuàng)建

var person = new Object()
person.name = 'Jack'
person.age = 18

person.sayHello = function(){
  console.log('Hello ' + this.name)
}

字面量的方式創(chuàng)建

var person = {
  name: 'jack'
  age: 18
  sayHello: function(){
    console.log('Hello ' + this.name)
  }
}

存在的問題: 生成多個 person 的實例對象, 代碼過于冗余, 重復性太高

工廠函數(shù)

function createPerson (name, age) {
  return {
    name: name
    age: age
    sayHello: function(){
      console.log('Hello ' + this.name)
    }
  }
}

var p1 = createPerson('xiaoqiang', 28)
var p2 = createPerson('xiaogang', 18)

通過工廠模式解決了創(chuàng)建多個相似對象代碼冗余的問題, 但是也帶來了新的問題 -- 使用工廠模式無法判斷對象的類型, 與字面量沒什么不同, 都是 Object

構(gòu)造函數(shù)

function Person (name, age) {
  this.name = name
  this.age = age
  this.sayHello = function(){
    console.log('Hello ' + this.name)
  }
}

var p1 = new Person('xiaoqiang', 18)
p1.sayHello()

var p2 = new Person('xiaogang', 28)
p2.sayHello()

構(gòu)造函數(shù) Person() 與 工廠函數(shù) createPerson() 的不同:

  • 沒有顯示的創(chuàng)建對象
  • 直接將屬性和方法賦給了 this 對象
  • 沒有 return 語句
  • 函數(shù)名是大寫的 Person

構(gòu)造函數(shù)與普通函數(shù)的不同

  • 構(gòu)造函數(shù)與普通函數(shù)類似, 不同的是約定 -- 構(gòu)造函數(shù)采用 Pascal 命名 (首字母大寫)
  • 調(diào)用構(gòu)造函數(shù)采用 new 構(gòu)造函數(shù)名()
  • 調(diào)用構(gòu)造函數(shù)返回的就是新創(chuàng)建的對象, 不需要寫 return
    • return 簡單類型 仍返回新創(chuàng)建的對象
    • return 引用類型 返回引用
    function Person(name, age){
      this.name = name
      this.age = age
      // return
      // return 0
      // return '123'
      // return true
      // return false
      // return [1,2,3]
      // return {}
    }
    
    var  p = new Person()
    console.log(p)
    
    // 沒有 return -- 返回 new Person
    // return -- 返回 new Person
    // return 0 -- 返回 new Person
    // return '123' -- 返回 new Person
    // return true -- 返回 new Person
    // return false -- 返回 new Person
    // return [1,2,3] -- 返回 [1,2,3]
    // return {} -- 返回 {}
    
  • 在構(gòu)造函數(shù)內(nèi)部使用 this 來表示新創(chuàng)建的對象, 使用方式 this.XXX = XXX

創(chuàng)建 Person 的實例, 需要使用 new 操作符. 這種方式調(diào)用構(gòu)造函數(shù)會經(jīng)歷以下4個階段:

1.創(chuàng)建一個新對象
2.將構(gòu)造函數(shù)的作用域賦給新對象 -- this 指向新對象
3.執(zhí)行構(gòu)造函數(shù)中的代碼
4.返回新對象

// 構(gòu)造函數(shù)就是普通函數(shù)
// 好處:
//  1. 創(chuàng)建對象的代碼更優(yōu)雅了
//  2. 可以判斷類型了垦搬,可以很方便的判斷實例和構(gòu)造函數(shù)之間的關系
function Person(name, age) {
  // 如果使用 new 操作符調(diào)用該函數(shù)网严,則:
  //    1. 先創(chuàng)建一個對象 var instance = {}
  //    2. 然后將內(nèi)部的 this 指向 instance   this = instance
  //    3. 用戶通過操作 this 從而操作 instance
  //    4. 在方法代碼的最后將 this 或者 instance 作為返回值默認返回
  this.name = name
  this.age = age
  this.sayHello = function () {
    console.log('helloi ' + this.name)
  }

  // 在函數(shù)的結(jié)尾處會將 this 返回识樱,也就是 instance
  // return this
}

function Car() { }

// 普通函數(shù)調(diào)用,內(nèi)部 this 指向 window
// Person('小明', 15)

// 使用 new 操作符調(diào)用函數(shù)就叫做構(gòu)造函數(shù)調(diào)用
var p1 = new Person('小明', 18)
var p2 = new Person('小剛', 15)
p1.sayHello()
p2.sayHello()

console.log(p1.constructor === Car)
console.log(p2.constructor === Person)

// instanceof 操作符也可以用來判斷實例與構(gòu)造函數(shù)的關系
// constructor 和 instanceof 都可以用來判斷實例與構(gòu)造函數(shù)的關系
// 但是更建議使用 instanceof
console.log(p1 instanceof Person)

構(gòu)造函數(shù)和實例對象的關系

每一個實例對象中都有一個 constructor 屬性, 改屬性指向創(chuàng)建該實例的構(gòu)造函數(shù).

console.log(p1.constructor === Person)    //true
console.log(p2.constructor === Person)    //true
console.log(p1.constructor === p2.constructor)    //true
p1 instanceof Peson // true
p2 instanceof Peson // true
  • 構(gòu)造函數(shù)是根據(jù)具體事物抽象出來的模板
  • 實例對象時根據(jù)抽象出來的構(gòu)造函數(shù)模板得到的具體函數(shù)
  • 每一個實例對象都有一個 constructor 屬性, 改屬性指向創(chuàng)建該實例的構(gòu)造函數(shù)
  • 可以通過實例的 constructor 屬性來判斷實例與構(gòu)造函數(shù)之間的關系

構(gòu)造函數(shù)的問題

function Person(name, age) {
  this.name = name
  this.age = age
  this.type = type
  this.sayHello = function () {
    console.log('helloi ' + this.name)
  }
}

var p1 = new Person('xiaoqiang', 16)
var p2 = new Person('xiaogang', 18)

每實例化一個對象, typesayHello 都是一樣的內(nèi)容, 每生成一個實例, 都會多占用一些內(nèi)存,

p1.sayHello === p2.sayHello // false

對于這種問題, 需要將共享的函數(shù)定義到構(gòu)造函數(shù)外部:

function Person(name, age) {
  this.name = name
  this.age = age
  this.type = type
  this.sayHello = sayHello
}
function sayHello = function () {
  console.log('helloi ' + this.name)
}
p1.sayHello === p2.sayHello // true

雖然解決了內(nèi)存問題, 但是有多個共享函數(shù)又會造成命名沖突的問題.
解決辦法: 將多個函數(shù)放入一個對象中來避免全局命名沖突:

var fns = {
  sayHello: function () { },
  showAge: function () { }
}
function Person(name, age) {
  this.name = name
  this.age = age
  this.sayHello = fns.sayHello
  this.showAge = fns.showAge
}
var p1 = new Person('lpz', 18)
var p2 = new Person('Jack', 16)

console.log(p1.sayHello === p2.sayHello) // => true
console.log(p1.sayAge === p2.sayAge) // => true

原型

更好的解決辦法 prototype

javascript規(guī)定: 每一個構(gòu)造函數(shù)內(nèi)部都有一個 prototype 屬性, 該屬性指向另一個對象. 這個對象的所有屬性和方法, 都會被構(gòu)造函數(shù)的實例繼承.

我們可以把所有對象實例需要共享的屬和方法直接定義在 prototype 對象上.

function Person (name, age) {
  this.name = name
  this.age = age
}

console.log(Person.prototype)

Person.prototype.type = 'human'

Person.prototype.sayHello = function(){
  console.log('hello ' + this.name)
}

var p1 = new Person(...)
var p2 = new Person(...)

console.log(p1.sayHello === p2.sayHello)  // true

所有實例的 type 屬性和 sayHello 方法, 指向的都是同一內(nèi)存地址 -- prototype 對象

構(gòu)造函數(shù)震束、實例怜庸、原型三者之間的關系

每創(chuàng)建一個函數(shù), 系統(tǒng)就會為這個函數(shù)自動分配一個 prototype 指針, 指向他的原型對象. 這個原型對象包含兩個部分( constructor__proto__ ) 其中 constructor 指向函數(shù)自身

function Person(){ }
console.log(Person.prototype.constructor === Person)  // true

通過構(gòu)造函數(shù)得到的實例對象中只有 __proto__ 屬性, 所有的實例都指向自己構(gòu)造函數(shù)的原型.

p.__proto__ === Person.prototype  // true

__proto__ 屬性里面有構(gòu)造器 constructor__proto__
constructor 指向原型所屬的構(gòu)造函數(shù)
__proto__ 指向Object的原型

proto.png
關系.png

面向?qū)ο?- 基本特性

  • 抽象性
    -- 只有在具體的環(huán)境中對象才可以表示具體的事物
    -- 而在程序設計中實際只考慮對象的目標數(shù)據(jù)
  • 封裝性
    -- 將具體的操作步驟打包起來
    -- 使用時無需關心具體的實現(xiàn)過程, 知道怎樣使用即可
  • 繼承性
    -- 繼承在 OOP 中的表現(xiàn)就是擴展. 在原有的對象的基礎上, 添加一些新的東西得到新的對象, 這個新的對象就繼承自原有的對象.
  • 多態(tài)性
    -- 調(diào)用同一個方法, 根據(jù)傳入的參數(shù)不同, 得到不同的結(jié)果

面向?qū)ο?- Error 對象

異常的概念

在代碼運行的過程中, 得到與預期不同的結(jié)果

處理異常

語法

try {
  // 需要判斷的代碼
} catch (e) {
  // 異常的處理
}

異常對象的傳遞

代碼出現(xiàn)異常, 那么異常后面的代碼不再執(zhí)行, 將錯誤傳遞給調(diào)用該函數(shù)的函數(shù), 直至傳到最頂層.

如果有 try - catch 那么出現(xiàn)異常后會執(zhí)行 catch 中異常處理的代碼

異常對象

在出現(xiàn)異常的時候, 異常出現(xiàn)的位置以及異常的類型, 內(nèi)容等數(shù)據(jù)都會被封裝起來, 以一個對象的形式傳遞給 catch 語句中的參數(shù) e ,用戶可以使用 throw 異常對象 拋出異常, 或者 new Error(e)得到異常信息.

try {
  // 需要判斷的代碼
} catch (e) {
  console.log(new Error(e))
  throw e
}

面向?qū)ο?- DOM對象

HTML中所有的節(jié)點都是對象

<body>
  你好, 今天<i>天氣很好</i>.
<body>

其中:
"你好, 今天" 是一個對象
i 標簽也是一個對象
"添加很好" 也是一個對象

面向?qū)ο?- 繼承

原型繼承

對象 p 中沒有 sayHello 方法, 因為構(gòu)造函數(shù) Person 中什么都沒有
但是 p 連接到原型中了, 因此 p 就可以調(diào)用 sayHello 方法
這就是原型繼承, p 沒有, 但是 p 繼承自原型對象, 所以 p 有了

  • 一般的方法

    所有的方法寫在原型中

    function Person ( name, age, gender ) {
      this.name = name
      this.age = age
      this.gender = gender
    }
    Person.prototype.sayHello = function () {
      console.log( '你好, 我是 ' + this.name )
    }
    
  • 替換原型

    function Person ( name, age, gender ) {
      this.name = name
      this.age = age
      this.gender = gender
    }
    Person.prototype = {
      constructor: Person // 最好手動添加 constructor 的指向
      sayHello: function () { },
      walk:function () { }
    }
    var p = new Person()
    p.sayHello()
    
  • 不推薦的寫法

    function Person (name, age, gender) {
      this.name = name
      this.age = age
      this.gender = gender
    }
    Person.prototype.sayHello = function () {
      console.log( '你好, 我是 ' + this.name )
    }
    function Teacher (name, age, gender){
      Person.call(this, name, age, gender)
    }
    Teacher.prototype = Person.prototype // 不推薦 修改 Teacher.prototype 會修改所有繼承自 Person 的對象的原型方法
    
  • 推薦的寫法

    Teacher.prototype = new Person()
    // TODO: 執(zhí)行修改 Teacher.prototype 操作
    
  • Object.create() 方法

    Object.create(proto [, propertiesObject ]) 是ES5中提出的一種新的對象創(chuàng)建方式,第一個參數(shù)是要繼承的原型垢村,如果不是一個子函數(shù)割疾,可以傳一個null,第二個參數(shù)是對象的屬性描述符嘉栓,這個參數(shù)是可選的宏榕。

    function Car (desc) {
        this.desc = desc
        this.color = "red"
    }
    Car.prototype = {
        getInfo: function() {
          return 'A ' + this.color + ' ' + this.desc + '.'
        }
    }
    //instantiate object using the constructor function
    var car =  Object.create(Car.prototype)
    car.color = "blue"
    alert(car.getInfo()) // A blue undefined.
    

    propertiesObject 參數(shù)的詳細解釋:(默認都為false)

    • writable: 是否可任意寫
    • configurable:是否能夠刪除驰凛,是否能夠被修改
    • enumerable:是否能用 for in 枚舉
    • value:值 訪問屬性
    • get(): 訪問
    • set(): 設置
    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="utf-8" />
    </head>
    <body>
      <script type="text/javascript">
        var obj = {
          a: function () { console.log(100) },
          b: function () { console.log(200) },
          c: function () { console.log(300) }
        }
        var newObj = {}
        newObj = Object.create(obj, {
          t1: {
            value: 'yupeng',
            writable: true
          },
          bar: {
            configurable: false,
            get: function () { return bar },
            set: function (value) { bar = value }
          }
        })
    
        console.log(newObj.a())   // 100
        console.log(newObj.t1)    // yupeng
        newObj.t1 = 'yupeng1'
        console.log(newObj.t1)    // yupeng1
        newObj.bar = 201
        console.log(newObj.bar)   // 201
    
        function Parent() { }
        var parent = new Parent()
        var child = Object.create(parent, {
          dataDescriptor: {
            value: "This property uses this string as its value.",
            writable: true,
            enumerable: true
          },
          accessorDescriptor: {
            get: function () { return "I am returning: " + accessorDescriptor },
            set: function (val) { accessorDescriptor = val },
            configurable: true
          }
        })
    
        child.accessorDescriptor = 'YUPENG'
        console.log(child.accessorDescriptor)   // I am returning: YUPENG
    
        var Car2 = function () {
          this.name = 'aaaaaa'
        } //this is an empty object, like {}
        Car2.prototype = {
          getInfo: function () {
            return 'A ' + this.color + ' ' + this.desc + '.'
          }
        }
    
        var newCar = new Car2()
    
        var car2 = Object.create(newCar, {
          //value properties
          color: { writable: true, configurable: true, value: 'red' },
          //concrete desc value
          rawDesc: { writable: true, configurable: true, value: 'Porsche boxter' },
          // data properties (assigned using getters and setters)
          desc: {
            configurable: true,
            get: function () { return this.rawDesc.toUpperCase() },
            set: function (value) { this.rawDesc = value.toLowerCase() }
          }
        })
        car2.color = 'blue'
        console.log(car2.getInfo())   // A blue PORSCHE BOXTER.
        car2.desc = "XXXXXXXX"
        console.log(car2.getInfo())   // A blue XXXXXXXX.
        console.log(car2.name)        // aaaaaa
      </script>
    </body>
    </html>
    

混合繼承

將原型鏈和借用構(gòu)造函數(shù)混合使用

function SuperType (name) {
  this.name = name
  this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function () {
  window.alert(this.name)
}
function SubType (name, age) {
  SuperType.call(this, name)
  this.age = age
}

// 繼承方法
SubType.prototype = new SuperType()
SubType.prototype.sayAge = function () {
  window.alert(this.age)
}

var instance1 = new SubType('Nicholas', 29)
instance1.colors.push('black')
window.alert(instance1.colors) // red,blue,green,black
instance1.sayName() // Nicholas
instance1.sayAge() // 29

var instance2 = new SubType('Greg', 27)
window.alert(instance2.colors) // red,blue,green
instance2.sayName() // Greg
instance2.sayAge() // 27

更多繼承說明

函數(shù)

函數(shù)也是對象, 所有的函數(shù)都是 Function 的實例.

所有函數(shù)的 __proto__ 都指向 Function.prototype, 包括 Function, 即:

function fn () {}

fn.__proto__ === Function.prototype  // true
Function.__proto__ === Function.prototypr /// true

Function.prototype.__proto__ === Object.prototype

函數(shù)的參數(shù)

length 屬性

在javascript中, 創(chuàng)建了一個函數(shù)就是創(chuàng)建了一個對象. 函數(shù)與一般數(shù)據(jù)一樣使用 -- 賦值, 調(diào)用
函數(shù)作為對象有一個 length 屬性, 改屬性用于描述創(chuàng)建函數(shù)時參數(shù)的個數(shù).

arguments 對象

在調(diào)用函數(shù)的時候, 會給函數(shù)參數(shù), 但在函數(shù)定義的時候, 有時候不確定要傳入多少參數(shù), 所有在調(diào)用時傳入的參數(shù)都會被 arguments 獲取到, 也就是說 -- arguments 中存儲的是參數(shù)的集合

function a () {
  console.log(arguments.length)
}
a()           // 0
a(1)          // 1
a(1,2,3,'4')  // 4

如何判斷調(diào)用時的參數(shù)個數(shù)與函數(shù)定義時的參數(shù)個數(shù)一樣?
函數(shù)名.length === arguments.length

將偽數(shù)組轉(zhuǎn)為數(shù)組
  • 聲明一個空數(shù)組,通過遍歷偽數(shù)組把它們重新添加到新的數(shù)組中

    var list = document.querySelectorAll('li')
    var res = []
    list.forEach(function(item, i){
      res.push(item)
    })
    

    偽數(shù)組 NodeList 沒有 forEach 方法, 但是通過 document.querySelectorAll() 返回的偽數(shù)組有這個方法

  • 使用數(shù)組的slice()方法 它返回的是數(shù)組担扑,使用call或者apply指向偽數(shù)組

    var res = Array.prototype.slice.call(list)
    
    // 模擬 slice 的實現(xiàn)
    Array.prototype.mySlice = function () {
      // 參數(shù)為 0 個, 則從 0 截到最后
      // 參數(shù)為 1 個, 則從 第一個參數(shù)開始的索引 截到最后
      // 參數(shù)為 2 個, 則從 第一個參數(shù)開始的索引 截到第二個參數(shù)截止的索引
      var start = 0
      var end = this.length
    
      arguments.length === 1 && (start = arguments[0])
      arguments.length === 2 && (start = arguments[0], end = arguments[1])
    
      var tmp = []
      for(var i = start; i < end; i++) {
        tmp.push(this[i])
      }
      return tmp
    }
    
  • 使用原型繼承

    list.__proto__ = Array.prototype
    
  • ES6中數(shù)組的新方法 from()

    var res = Array.from(list)
    
  • jq的makeArray()恰响,toArray()方法 它們也可以實現(xiàn)偽數(shù)組轉(zhuǎn)數(shù)組的功能,但是實現(xiàn)的原理并不太一樣

    // core_deletedIds = []
    // core_slice = core_deletedIds.slice
    // core_push = core_deletedIds.push
    
    // makeArray: 使用了數(shù)組的slice方法
    toArray: function () {
      return core_slice.call(this)
    }
    
    // makeArray:使用了push方法
    makeArray: function (arr, result) {
      var ret = result || []
    
      if (arr != null) {
        if (isArraylike(object(arr))) {
          jQuery.merge(ret,
            typeof arr === 'string' ? [arr] : arr
          )
        } else {
          core_push.call(ret, arr)
        }
      }
      return ret
    }
    
callee 屬性
  • callee 返回正在執(zhí)行的函數(shù)本身的引用, 他是 arguments 的一個屬性

    arguments.callee === fn  // true
    
  • callee 有一個 length 屬性, 可以獲得形參的個數(shù). 因此可以用來比較形參與實參個數(shù)是否一致.

    arguments.length === arguments.callee.length
    
  • 可以用來遞歸匿名函數(shù)

    var sum = function(n){
      if (n <= 1) return 1
      else return n + arguments.callee(n - 1)
    }
    

caller

caller 返回一個函數(shù)的引用, 這個函數(shù)調(diào)用了當前的函數(shù)

使用這個屬性要注意:

  • 這個屬性只有在函數(shù)執(zhí)行時才有作用
  • 如果在 javascript 程序中, 函數(shù)是頂層調(diào)用的, 返回 null
var a = function() {
  alert(a.caller)
}
var b = function() {
  a()
}
b()

代碼中, b 調(diào)用了 a, 所以 a.caller 返回的是 b 的引用, 結(jié)果如下:

var b = function() {
  a()
}

如果直接調(diào)用 a() , 輸出結(jié)果為: null

函數(shù)的預解析

在javascript預解析的時候, 同名的函數(shù)與變量以函數(shù)為準.
已經(jīng)預解析過得函數(shù), 在代碼執(zhí)行過程中會略過

console.log(typeof fn) // function

// 在執(zhí)行階段涌献,這里對 fn 重新賦值為 123 了
var fn = 123

// 函數(shù)聲明最大的特性就是:具有函數(shù)提升
function fn() {
  console.log('hello')
}

console.log(typeof fn) // function number

函數(shù)的表達式

函數(shù)的表達式類似于變量賦值, 只有變量提升, 沒有函數(shù)提升, 必須先聲明, 再使用

fn()    // 報錯 VM277:1 Uncaught ReferenceError: fn is not defined
console.log(typeof fn) // undefined 函數(shù)表達式只有變量提升

var fn = function () {
  console.log('hello')
}

fn()    // hello

new Function

執(zhí)行效率低, 很少用.

var add = new Function('x', 'y', 'return x + y')
var ret = add(10, 30)

console.log(ret)

作用域

塊級作用域

所謂的塊, 就是代碼的邏輯機構(gòu), 其中使用 {} 包含的就是語句塊. 例如:

if (true) {
  // 語句1
  // 語句2
  // 語句3
}

這里的 {}就是語句塊

塊級作用域是指: 從變量定義開始, 到變量所在的語句塊結(jié)束, 在這樣一個范圍內(nèi)可以被使用.

在塊級作用域內(nèi) 本塊級的變量可以訪問父級塊內(nèi)的變量, 反之不行.

如果子塊和父塊變量重名, 那么會在定義該變量時隱藏父塊中的變量.在子塊中定義的變量的改變, 不會影響父塊中的變量, 離開子塊后, 父塊中的變量可以繼續(xù)使用.

但是, 在javascript中沒有塊級作用域, 所有聲明的變量的作用據(jù)就是當前函數(shù)范圍內(nèi), 或者全局.

詞法作用域

詞法作用域, 指的是變量的訪問規(guī)則按照詞法定義的規(guī)則進行使用, 也就是只有函數(shù)才可以限定作用域.

訪問變量從當前作用域開始往上進行查找 -- 不是代碼的書寫順序

問題

var condition = true
if (condition) {
  // var foo = 'bar'
  function fn() {
    console.log('hello')
  }
} else {
  function fn() {
    console.log('world')
  }
}
fn()
// 因為在javascript中, 沒有塊級作用域, 所以 {} 內(nèi)聲明的都是全局
// 低版本(IE10以下)瀏覽器會先進性函數(shù)提升 -- 執(zhí)行結(jié)果是 world
// 對于上面的方式胚宦,建議使用函數(shù)表達式來處理就可以了
// 因為函數(shù)表達式?jīng)]有函數(shù)提升
var condition = true
var fn
if (condition) {
  // var foo = 'bar'
  fn = function () {
    console.log('hello')
  }
} else {
  fn = function () {
    console.log('world')
  }
}

fn()

變量的訪問規(guī)則

函數(shù)的作用域鏈以定義時所處的作用域為準, 而不是調(diào)用時.

javascript的執(zhí)行原理

作用域鏈

this

this 的指向, 在調(diào)用的時候才能確定.

調(diào)用方式 非嚴格模式 備注
普通函數(shù)調(diào)用 window 嚴格模式下是 undefined
構(gòu)造函數(shù)調(diào)用 實例對象 原型方法中 this 也是實例對象
對象方法調(diào)用 該方法所屬對象 緊挨著的對象
事件綁定方法 綁定事件對象
定時器函數(shù) window

普通函數(shù)調(diào)用

function a(){
  var user = "追夢子"
  console.log(this.user) //undefined
  console.log(this) //Window
}
a()

a() 相當于 window.a(), 在非嚴格下指向 window

可以理解為 window 對象調(diào)用

對象方法調(diào)用

var o = {
  user:"追夢子",
  fn:function(){
    console.log(this.user)  //追夢子
  }
}
o.fn()

this 的指向是在調(diào)用的時候確定的, 這里的 this 指向?qū)ο?o

var o = {
  user:"追夢子",
  fn:function(){
    console.log(this.user) //追夢子
  }
}
window.o.fn()

這里的 this 指向?qū)ο?o, 因為 o 相當于全局對象 window 的一個屬性.

如果一個函數(shù)中有this,這個函數(shù)中包含多個對象燕垃,盡管這個函數(shù)是被最外層的對象所調(diào)用枢劝,this指向的也只是它上一級的對象, 最終要看是哪個對象"點"出來的

var o = {
  a:10,
  b:{
    a:12,
    fn:function(){
      console.log(this.a) //undefined
      console.log(this) //window
    }
  }
}
var j = o.b.fn
j()

這里的 this 指向并不是 b 對象, 雖然函數(shù) fn 是被對象 b 引用, 但是在將 fn 賦值給 j 的時候并沒有執(zhí)行, 所以在 j 執(zhí)行的時候, this 的最終指向是 window

構(gòu)造函數(shù)調(diào)用

function Fn(){
  this.user = "追夢子"
}
var a = new Fn()
console.log(a.user) //追夢子

構(gòu)造函數(shù)內(nèi)的 this 指向構(gòu)造方法的實例 -- a

創(chuàng)建實例時 new 改變了 this 的指向

使用 new 操作符. 這種方式調(diào)用構(gòu)造函數(shù)會經(jīng)歷以下4個階段:

1.創(chuàng)建一個新對象
2.將構(gòu)造函數(shù)的作用域賦給新對象 -- this 指向新對象
3.執(zhí)行構(gòu)造函數(shù)中的代碼
4.返回新對象

當構(gòu)造函數(shù)內(nèi)有 return 時:

  • return 的值是值類型 不影響 this 指向

  • return 的值是引用類型:

    function fn() {
      this.user = '追夢子'
      return {}  // return 空對象
    }
    var a = new fn
    console.log(a.user)  //undefined
    
    function fn() {
        this.user = '追夢子'
        return function(){}  // 函數(shù)
    }
    var a = new fn
    console.log(a.user) //undefined
    

    如果返回值是一個對象,那么this指向的就是那個返回的對象卜壕,如果返回值不是一個對象那么this還是指向函數(shù)的實例

事件綁定方法

document.getElementById('btn').onclick = function(){
  console.log('btn 被點擊了')
}

事件處理函數(shù)不是用戶來調(diào)用的您旁,而是由系統(tǒng)來調(diào)用

事件處理函數(shù)內(nèi)部的 this 指向 DOM 對象

定時器函數(shù)

setTimeout(function(){
  console.log('時間 +1s')
}, 1000)

當時間到達,系統(tǒng)會去幫你調(diào)用這個函數(shù), 所以轴捎,這個定時器處理函數(shù)中的 this 指向的是 window

demo

function Foo(){
  getName = function(){
    alert(1)
  }
  return this
}

Foo.getName = function (){alert(2)}
Foo.prototype.getName=function () {alert(3)}
var getName = function (){ alert(4)}
function getName(){alert(5)}

// 寫出下面的結(jié)果

// 1 Foo.getName()
// 2 getName()
// 3 Foo().getName()
// 4 new Foo.getName()
// 5 new Foo().getName()
// 6 new new Foo().getName()

點運算符鹤盒、new運算符、函數(shù)執(zhí)行這三者之間的優(yōu)先級:
new A.B(); 的邏輯是這樣的:new A.B ();
點運算符優(yōu)先于new運算符侦副,看起來似乎僅僅如此侦锯。
new A().B(); 的邏輯卻是這樣的:(new A()).B(); 而不是 new (A().B) ();
區(qū)別在于A后面多了一對小括號,這個影響到了優(yōu)先級順序秦驯。

//這兩種寫法是等價的
var d = new A
var d = new A()

//但是下面這兩種是不同的尺碰,不能混淆了:
var d = new A.B(); //new A.B
var d = new A().B(); //new A().B

call & apply

applycall 的方法作用是一模一樣的,都是用來改變方法的 this 關鍵字译隘,并且把方法執(zhí)行亲桥;而且在嚴格模式下和非嚴格模式,對于第一個參數(shù)時null/undefined固耘,這樣的情況下题篷,也是一樣的。

// call在給fn傳遞參數(shù)的時候玻驻,是一個一個傳遞值的 call在給fn傳遞參數(shù)的時候悼凑,是一個一個傳遞值的
fn.call(obj, 100, 200)

// 而apply不是一個個的傳遞,而是把要給fn傳遞的參數(shù)值統(tǒng)一的放在一個數(shù)組中進行操作
// 但是也相當于一個一個的給fn的參數(shù)賦值
fn.apply(obj, [100, 200])

var a = {
  user:"追夢子",
  fn:function(){
      console.log(this.user) //追夢子
  }
}
var b = a.fn
b.call(a)

通過在call方法璧瞬,給第一個參數(shù)添加要把b添加到哪個環(huán)境中户辫,簡單來說,this就會指向那個對象嗤锉。

call方法除了第一個參數(shù)以外還可以添加多個參數(shù)渔欢,如下:

var a = {
  user:"追夢子",
  fn:function(e,ee){
    console.log(this.user) //追夢子
    console.log(e+ee) //3
  }
}
var b = a.fn
b.call(a,1,2)

注意如果call和apply的第一個參數(shù)寫的是null,那么this指向的是window對象

var a = {
  user:"追夢子",
  fn:function(){
    console.log(this) //Window {external: Object, chrome: Object, document: document, a: Object, speechSynthesis: SpeechSynthesis…}
  }
}
var b = a.fn
b.apply(null)

var keith = {
  rascal: 123
}

var rascal = 456

function a() {
  console.log(this.rascal)
}

a() //456
a.call() //456
a.call(null) //456
a.call(undefined) //456
a.call(this) //456
a.call(keith) //123

callapply 可以用來借用其他函數(shù)的方法

var myMath = {
  max: function () {
    var max = arguments[0]
    for (var i = 0; i < arguments.length; i++) {
      if (arguments[i] > max) {
        max = arguments[i]
      }
    }
    return max
  }
}

var arr = [32, 1, 32, 13, 2, 4321, 13, 21, 3]

// 第一個參數(shù)用來指定內(nèi)部 this 的指向
// apply 會把傳遞的數(shù)組或偽數(shù)組展開, 一個一個傳遞到方法內(nèi)部
var max = myMath.max.apply(null, arr)
console.log(max)

bind

call, apply, 和 bind 的區(qū)別:

  • call, apply, 和 bind 都可以改變很熟內(nèi)部的指向
  • call, 函數(shù)apply 在改變 this 指向的同時調(diào)用函數(shù)
    • call 通過 , 作為分隔進行傳參
    • apply 通過傳遞一個數(shù)組或者偽數(shù)組傳遞參數(shù)
  • bind 改變函數(shù)內(nèi)部 this 的指向, 但是他不調(diào)用, 而是返回了一個指定了 this 環(huán)境的新函數(shù)

綁定函數(shù)

bind() 最簡單的用法是創(chuàng)建一個函數(shù), 使這個函數(shù)不論怎么調(diào)用都有同樣的 this 值.

常見的錯誤:

var altwrite = document.write
altwrite("hello")
//1.以上代碼有什么問題
//2.正確操作是怎樣的
//3.bind()方法怎么實現(xiàn)

altwrite 改變了 this 的指向 globalwindow 對象, 導致執(zhí)行時提示非法調(diào)用異常 Uncaught TypeError: Illegal invocation, 正確的使用方法:

altwrite.bind(document)("hello")

或者

altwrite.call(document, "hello")

常見的錯誤就像上面的例子一樣, 講方法從對象中拿出來, 然后調(diào)用, 并且希望 this 指向原來的對象. 如果不做特殊處理, 一般對象會丟失. 使用 bind() 方法可以很漂亮的解決這個問題.

this.number = 9
var module = {
  num: 81,
  getNum: function () {
    return this.num
  }
}

module.getNum()     // 81

var getNum = module.getNum
getNum()    // 9

var boundGetNum = getNum.bind(module)
boundGetNum()     // 81

偏函數(shù)

與 setTimeout 一起使用

一般情況下, setTimeout 的 this 會指向 global 或 window 對象. 當時用類的方法需要 this 指向?qū)嵗? 就可以用 bind() 將 this 綁定到回調(diào)函數(shù)來管理實例.

function Bloomer () {
  this.petalCount = Math.ceil(Math.random() * 12) + 1
}

Bloomer.prototype.bloom = function () {
  window.setTimeout(this.declare.bind(this), 1000)
}

Bloomer.prototype.declare = function () {
  console.log(`我有 ${this.petalCount} 朵花瓣`)
}

綁定函數(shù)作為構(gòu)造函數(shù)

function Point(x, y) {
  this.x = x
  this.y = y
}

Point.prototype.toString = function() {
  return this.x + ',' + this.y
}

var p = new Point(1, 2)
p.toString() // '1,2'


var emptyObj = {}
var YAxisPoint = Point.bind(emptyObj, 0/*x*/)
// 實現(xiàn)中的例子不支持,
// 原生bind支持:
var YAxisPoint = Point.bind(null, 0/*x*/)

var axisPoint = new YAxisPoint(5)
axisPoint.toString() // '0,5'

axisPoint instanceof Point // true
axisPoint instanceof YAxisPoint // true
new Point(17, 42) instanceof YAxisPoint // true

上面例子中Point和YAxisPoint共享原型瘟忱,因此使用instanceof運算符判斷時為true奥额。

捷徑

bind()也可以為需要特定this值的函數(shù)創(chuàng)造捷徑苫幢。

例如要講一個類數(shù)組對象轉(zhuǎn)換為真正的數(shù)組, 可能會寫:

var slice = Array.prototype.slice
// ...
slice.call(arguments)

使用 bind() 的話:

var unboundSlice = Array.prototype.slice
var slice = Function.prototype.call.bind(unboundSlice)
// ...
slice(arguments)

參數(shù)

bind() 可以傳參數(shù), 但是不調(diào)用
bind() 以后得到的新函數(shù)也可以傳參, 但在實際使用的時候, 會把在 bind() 時傳遞的參數(shù)和調(diào)用新函數(shù)傳的參數(shù)進行合并, 然后作為函數(shù)的參數(shù)

高階函數(shù)

高階函數(shù)就是指:

  • 函數(shù)可以當做參數(shù)進行傳遞
  • 函數(shù)可以當做返回值進行返回

作為參數(shù)傳遞

ajax

// callback為待傳入的回調(diào)函數(shù)
var getUserInfo = function(userId, callback) {
  $.ajax("http://xxx.com/getUserInfo?" + userId, function(data) {
    if (typeof callback === "function") {
      callback(data)
    }
  })
}

getUserInfo(13157, function(data) {
  alert (data.userName)
})

Array.prototype.sort
Array.prototype.sort 接收一個函數(shù)作為參數(shù), 這個函數(shù)封裝了數(shù)組元素的排序規(guī)則.

//從小到大排列
[1, 4, 3].sort(function(a, b) {
    return a - b
});
// 輸出: [1, 3, 4]

//從大到小排列
[1, 4, 3].sort(function(a, b) {
    return b - a
});
// 輸出: [4, 3, 1]

作為返回值返回

判斷數(shù)據(jù)類型

// 以前的代碼
function isArray (obj) {
  return Object.prototype.toString.call(obj) === '[object Array]'
}

function isObject (obj) {
  return Object.prototype.toString.call(obj) === '[object Object]'
}
// ...

// 函數(shù)作為返回值的寫法
var isArray = generateCheckTypeFn('[object Array]')
var isObject = generateCheckTypeFn('[object Object]')
var isString = generateCheckTypeFn('[object String]')
var isNumber = generateCheckTypeFn('[object Number]')

function generateCheckTypeFn (type) {
  return function (obj) {
    return Object.propotype.toString.call(obj) === type
  }
}

isArray([])     // true
isNumber(NaN)     // true

高階函數(shù)的其他說明

閉包

閉包就是能夠讀取其它函數(shù)內(nèi)部變量的函數(shù)

由于在 javascript 中, 只有函數(shù)內(nèi)部的子函數(shù)才能讀取局部變量, incident可以把閉包簡單理解為 "定義在函數(shù)內(nèi)部的函數(shù)"

在本質(zhì)上, 閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來的一座橋梁.

如果一個函數(shù)內(nèi)部返回了一個函數(shù)或者多個函數(shù), 而返回的函數(shù)中具有對自己的外層作用域中的成員的讀取或者修改, 那么這個函數(shù)就成為閉包函數(shù).

如何訪問閉包中的數(shù)據(jù):

  • 利用返回值
  • 利用一個對象返回函數(shù)
  • 返回對象
function fn () {
  var foo = 'bar'

  var getFoo = function () {
    return foo
  }

  var setFoo = function (val) {
    foo = val
  }

  return {
    getFoo: getFoo,
    setFoo: setFoo
  }
}

var obj = fn()

console.log(obj.getFoo())

示例代碼:

var arr = [10, 20, 30]

for(var i = 0; i < arr.length; i++) {
  arr[i] = (function (i) {
    return function () {
      console.log(i)
    }
  })(i)
}

arr.forEach(function (item, index) {
  item()
})

示例代碼:

console.log(111)

for(var i = 0; i < 3; i++) {
  // 定時器永遠在普通代碼的最后執(zhí)行
  // 哪怕時間是 0
  setTimeout((function (i) {
    return function () {
      console.log(i)
    }
  })(i),0)
}

console.log(222)

沙箱模式

利用匿名函數(shù)自執(zhí)行保護內(nèi)部成員不被外部修改或者訪問

;(function () {
  var age = 3

  function F () {
  }

  F.prototype.getAge = function () {
    return age
  }

  F.prototype.setAge = function (val) {
    if (val < 18) {
      return console.log('age 不能小于18歲')
    }
    age = val
  }

  window.F = F
})()

var f = new F()
console.log(f.getAge())

思考題1

var name = 'The Window'

var object = {
  name: "My Object",
  getNameFunc: function () {
    return function () {
      return this.name
    }
  }
}

console.log(object.getNameFunc()())

思考題2

var name = 'The Window'

var object = {
  name: "My Object",
  getNameFunc: function () {
    var that = this
    return function () {
      return that.name
    }
  }
}

console.log(object.getNameFunc()())

遞歸

深拷貝


// 判斷類型
function getType(type) {
  return function (obj) {
    return Object.prototype.toString.call(obj) === `[object ${type}]`
  }
}

function isArray(obj) {
  return getType('Array')(obj)
}

function isObject(obj) {
  return getType('Object')(obj)
}
// 循環(huán)拷貝
function deepCopy(target, source) {
  for (var key in source) {
    if (source.hasOwnProperty(key)) {
      var element = source[key];
      if (isArray(element)) {
        target[key] = []
        deepCopy(target[key], element)
      } else if (isObject(element)) {
        target[key] = {}
        deepCopy(target[key], element)
      } else {
        target[key] = element
      }
    }
  }
}

階乘

function factorial(number) {
  if (number < 0) {
    return
  } else if (number < 2) {
    return 1
  } else {
    return number * factorial(number - 1)
  }
}

以下代碼會導致錯誤

var fact = factorial
fact = null
fact(5)

解決辦法: 使用 callee

function factorial(number) {
  if (number < 0) {
    return
  } else if (number < 2) {
    return 1
  } else {
    return number * arguments.callee(number - 1)
  }
}
var fact = factorial
factorial = null
console.log(fact)
fact(5)
`callee` 返回正在執(zhí)行的函數(shù)本身的引用, `arguments.callee === fn  // true`
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者

  • 序言:七十年代末,一起剝皮案震驚了整個濱河市垫挨,隨后出現(xiàn)的幾起案子韩肝,更是在濱河造成了極大的恐慌,老刑警劉巖九榔,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哀峻,死亡現(xiàn)場離奇詭異,居然都是意外死亡哲泊,警方通過查閱死者的電腦和手機剩蟀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來切威,“玉大人育特,你說我怎么就攤上這事∠入” “怎么了缰冤?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長烙无。 經(jīng)常有香客問我锋谐,道長,這世上最難降的妖魔是什么截酷? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮乾戏,結(jié)果婚禮上迂苛,老公的妹妹穿的比我還像新娘。我一直安慰自己鼓择,他們只是感情好三幻,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著呐能,像睡著了一般念搬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上摆出,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天朗徊,我揣著相機與錄音,去河邊找鬼偎漫。 笑死爷恳,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的象踊。 我是一名探鬼主播温亲,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼棚壁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了栈虚?” 一聲冷哼從身側(cè)響起袖外,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎魂务,沒想到半個月后曼验,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡头镊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年蚣驼,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片相艇。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡颖杏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出坛芽,到底是詐尸還是另有隱情留储,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布咙轩,位于F島的核電站获讳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏活喊。R本人自食惡果不足惜丐膝,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望钾菊。 院中可真熱鬧帅矗,春花似錦、人聲如沸煞烫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽滞详。三九已至凛俱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間料饥,已是汗流浹背蒲犬。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留稀火,地道東北人暖哨。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親篇裁。 傳聞我的和親對象是個殘疾皇子沛慢,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容