Learning TypeScript 0x4 运行时

环境

文档对象模型是用于动态访问和更新页面中视图结构的接口,独立于平台和语言。模型的数据可以进一步处理和修改,并且修改会反映到当前视图中。

 

 帧(frame)

一个帧是一个连续的工作单元。帧为栈中的小块。

当一个JS函数被调用时,运行时环境就会在栈中创建一个帧。帧中保存了特殊函数的参数和局部变量。当函数返回时,帧就被从栈中推出。

栈 stack

栈包含了一个信息在执行时的所有步骤(帧)。栈的数据结构为一个后进先出的对象集合。

队列 queue

队列中包含一个待执行信息的列表,每一个信息都与一个函数相互联系。当栈为空时,队列中的一条信息就会被取出并且处理。处理的过程为调用该信息所关联的函数,然后将此帧添加到栈的顶部。当栈再次为空时,本次信息处理即视为结束。

堆 heap

堆是一个内存存储空间,它不关注内部存储的内容的保存顺序。堆中保存了所有正在被使用的变量和对象。同时也保存了一些当前作用域不再会被用到,但是还没有被垃圾回收的帧。

事件循环

运行时执行在一个单线程中,所以不能达到真正意义上的并发。事件循环内的信息是线性执行的。

优点:

执行顺序是非常容易预测且容易追踪的;在事件循环内可以进行非阻塞I/O操作。

缺点:

当一个信息需要大量时间来处理时,应用会变得无响应。(较好的解法是,保持每个信息处理尽量简短,可能的话,将一个信息函数分割为多个小函数)

this

全局上下文中的this

在全局上下文中,this总是指向全局对象。如浏览器中,window对象即是全局对象。

函数上下文中的this

值由函数被调用的方式决定。在非严格模式下,简单调用一个函数,内部的this值指向全局对象。

function f1() {
    return this
}
f1() === window // true

严格模式下,内部的this值会指向undefined

console.log(this) // window
function f2() {
    'use strict'
    return this
}
console.log(f2()) // undefined
console.log(this) // window

如果函数是以实例方法的形式被调用,this操作符将会指向该实例

var p = {
    age: 37,
    getAge: function() {
        return this.age // this指向类实例
    }
}
console.log(p.getAge()) // 37

使用原型声明的方法也一样

function Person() {}
Person.prototype.age = 37
Person.prototype.getAge = function() {
    return this.age
}
var p = new Person()
p.age // 37
p.getAge() // 37

当一个函数被作为构造函数(使用new关键字)调用时,this操作符将指向正在被构造的对象

function Person() {
    this.age = 37
}
var p = new Person()
console.log(p.age) // 37

call、apply和bind

call和apply方法非常相似,它们都让我们先设置函数中的this操作符的值,并且执行这个函数。区别:apply以数组的形式接受传递给函数的参数,而call则是以单个分开参数的形式。

class Person {
    public name : string
    public surname : string
    
    constructor(name : string, surname : string) {
        this.name = name
        this.surname = surname
    }
    public greet(city : string, country : string) {
        // 使用this操作符来访问实例的name和surname属性
        var msg = `Hi,my name is ${this.name} ${this.surname}`
        msg += `I'm from ${city} (${country})`
        console.log(msg)
    }
}

var person = new Person('remo', 'jansen')
person.greet('Seville', 'Spain') // 输出
person.greet.call(person, 'seville', 'spain') // 等效
person.greet.apply(person, ['seville', 'spain']) // 等效

// 如果将发放内部的this指向其他值,那么在greet方法内,访问不到name和surname属性
person.greet.call(null, 'seville', 'spain')
person.greet.apply(null, ['seville', 'spain'])

// call和apply方法只在我们的确希望函数内的this操作符指向其他值时才有意义
var valueOfThis = { name : 'anakin', surname : 'skywalker' }
person.greet.call(valueOfThis, 'mos espa', 'tatooine') // 正常
person.greet.apply(valueOfThis, ['mos espa', 'tatooine']) // 正常

bind方法也可以设置this操作符的值,但不执行它

调用函数的bind方法时,它返回了一个和原函数具有相同函数体和作用域的新函数,但是内部的this指向的值已经被永久地改变为传递给bind方法的第一个参数,不论之后这个函数如何被调用都不会变。

原型

运行时的继承系统使用的是原型继承模型,在一个原型继承模型中,并没有类,对象直接继承自对象。但是,可以用原型来模拟对象。

class Person {
    public name : string
    public surname : string
    public age : number = 0
    constructor(name: string, surname: string) {
        this.name = name
        this.surname = surname
    }
    greet() {
        var msg = `Hi! my name is ${this.name} ${this.surname}`
        msg += `I'm ${this.age}`
    }
}

运行时

var Person = (function() {
    function Person(name, surname) {
        this.age = 0
        this.name = name
        this.surname = surname
    }
    Person.prototype.greet = function() {
        var msg = `Hi! my name is ${this.name} ${this.surname}`
        msg += `I'm ${this.age}`
    }
    return Person
})()

实例属性与类属性的对比

类的属性和方法的值是在他们的实例间共享的。类的属性和方法也被称为静态属性和方法。

function Person(name, surname) {
    //实例属性
    this.name = name
    this.surname = surname
}
var me = new Person('remo', 'jansen')
me.email = 'remo.jansen@qq.com')

for(var property in me) {
    console.log(`property:${property},value: ${me[property]}`)
}
// property : name, value: 'remo'
// property : surname, value: 'jansen'
// property : email, value: 'remo.jansen@qq.com'

类方法常被作为工具函数使用,为其提供参数,经过计算,然后返回一个结果

function MathHelper() {/**/}
//类方法
MathHelper.areaOfCircle = function(radius){
    return radius * radius * this.PI
}
//类属性
MathHelper.PI = 3.1415926

可以在类方法和实例方法中访问类属性,但不能在类属性或类方法中访问实例属性和方法。当需要从实例方法中访问类属性和方法时,可以使用原型的constructor属性来取得。

function MathHelper() {/**/}
//类属性
MathHelper.PI = 3.1415926
//实例方法
MathHelper.prototype.areaOfCircle = function(radius){
    return radius * radius * this.constructor.PI
}

由于原型的constructor属性返回了对象构造函数的引用,所以我们可以在areaOfCircle中通过原型访问到PI

this === MathHelper.prototype // true

基于原型的继承

class SuperHero extends Person {
    public superpower : string
    constructor(name : string, surname : string, superpower : string) {
        super(name, surname)
        this.superpower = superpower
    }
    userSuperPower() {
        return `I'm using my ${this.superpower}`
    }
}

编译原理

// 得到一个包含了子类d和父类b的所有属性的对象
var __extends = this.__extends || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]
    function __() {
        this.constructor = d
    }
    __.prototype = b.prototype
    d.prototype = new __()
}

编译结果

var SuperHero = (function(_super){
    __extends(SuperHero, _super)
    function SuperHero(name, surname, superpower) {
        _super.call(this, name, surname)
        this.superpower = superpower
    }
    SuperHero.prototype.userSuperPower = function() {
        return `I'm using my ${this.superpower}`
    }
    return SuperHero
})(Person)

原型链

尝试访问一个对象的属性或方法时,运行时将会搜索对象自身原型上的属性和方法,如果没有找到,那么运行时将会继续沿着对象的继承树,在被继承对象的原型中继续搜索。

访问对象的原型

  • Person.prototype
  • Person.getPrototypeOf(person)
  • person.__prototype__

new操作符

运行时不会遵从基于类的继承模型。当使用new操作符时,运行时会创建一个新对象,并让其继承自Person类的原型。

闭包

闭包是只想独立变量(可以理解为在创建的字面作用域上持续存在的变量)的函数。在闭包中定义的函数会记住他创建时的函数。

function makeArmy() {
    var shooters = []
    for(var i = 0; i < 10; i++){
        var shooter = function() {
            console.log(i)
        }
        shooters.push(shooter)
    }
    return shooters
}
var army = makeArmy()
army[0]() // 10
army[5]() // 10

 赋值给shooter的函数都是闭包。其包含自身的函数定义并保存了makeArmy的作用域环境。创建除了10个闭包,但是他们共享一个环境。当shooter函数被执行时,函数中的循环已经执行完毕,i的值也改为10.

一个解决方案是使用更多的闭包

function makeArmy() {
    var shooters = []
    for(var i = 0; i < 10; i++){
        (function(i){
            var shooter = function() {
                console.log(i)
            }
            shooters.push(shooter)
        })(i)
    }
    return shooters
}
var army = makeArmy()
army[0]() // 10
army[5]() // 10

闭包和静态变量

当一个变量在一个闭包环境中被声明后,它可以在一个类的不同实例间共享,即这个变量表现得像一个静态变量

class Counter {
    private static _COUNTER = 0
    constructor() {}
    private _changeBy(val) {
        Counter._COUNTER += val
    }
    public increment() {
        this._changeBy(1)
    }
    public decrement() {
        this._changeBy(-1)
    }
    public value() {
        return Counter._COUNTER
    }
}

编译结果

var Counter = (function() {
    function Counter() {}
    Counter.prototype._changeBy = function(val) {
        Counter._COUNTER += val
    }
    Counter.prototype.increment = function() {
        this._changeBy(1)
    }
    Counter.prototype.decrement = function() {
        this._changeBy(-1)
    }
    Counter.prototype.value = function() {
        return Counter._COUNTER
    }
    Counter._COUNTER = 0
    return Counter
})()

用闭包模拟静态属性。Counter构造函数自身就是一个闭包的一部分。所以,所有的Counter类的实例都共享同一个闭包上下文。即这个上下文中的counter变量和changeBy函数会表现得如单例一样。

var Counter = (function() {
    // 闭包上下文
    var _COUNTER = 0
    function chaneBy(val) {
        _COUNTER += val
    }
    function Counter() {}
    Counter.prototype.increment = function() {
        changeBy(1)
    }
    Counter.prototype.decrement = function() {
        chaneBy(-1)
    }
    Counter.prototype.value = function() {
        return _COUNTER
    }
    return Counter
})()

闭包和私有成员

由于不能直接访问闭包上下文,那么该上下文中的变量和成员就可以用来模拟私有成员。使用这种方法的好处是闭包会在运行时阻止对他们的访问。

TS在运行时并不会模拟私有变量。如果我们试图访问一个私有成员,那么TS编译器会在编译时报错。当时在运行时,私有变量会编程公有变量,可访问。

可以使用闭包模拟私有变量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值