环境
文档对象模型是用于动态访问和更新页面中视图结构的接口,独立于平台和语言。模型的数据可以进一步处理和修改,并且修改会反映到当前视图中。
帧(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编译器会在编译时报错。当时在运行时,私有变量会编程公有变量,可访问。
可以使用闭包模拟私有变量。