本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2021-05-21
TypeScript 只存在于程序设计阶段,在运行前,会被编译成JavaScript代码,所以TypeScript的运行时就是JavaScript的运行时,编译之后的代码会在服务端(node.js)或客户端(浏览器)执行,然后就可能遇到运行时的问题;
运行时环境是开发一个应用首先考虑到的事情,一旦编译完成,TypeScript就可能被不同的JavaScript引擎执行,主要就是浏览器,也可能是Node.js或RingoJS环境中运行的服务端程序或桌面应用。
所以需要谨慎使用那些仅在特定运行时环境才可用的变量,例如,浏览器中的 DOM 和 BOM 对象,又比如Node.js中的process。
另外一点需要把同一 JavaScript 运行时的不同版本考虑在内,我们的应用常常需要兼容多个浏览器或多个版本的 Node.js。
处理这些问题的比较好的实践就是添加一个逻辑,在使用一个特性前,先检测一个这个特性是否存在,有一个很好用的库,Modernizr
一个帧是一个连续的工作单元,在JavaScript函数被调用时,运行时环境就会在栈中创建一个帧,帧里面保存了特殊函数的参数和局部变量。当函数返回时,帧就被栈中推出。
function foo(b) {
var a = 12;
return a + b + 35;
}
function bar(x) {
var m = 4;
return foo(m * x);
}
bar(21);
// 当bar被执行时,运行时就会创建一个包含bar的参数和所有局部变量的帧,会被添加到栈顶。
// bar 中调用了 foo,那么就又会创建一个新的帧添加到栈顶
// 然后 foo 执行完毕,栈顶部的帧就被移除
// 之后 bar 执行完毕,对应的帧也同样被移除
// 如果 foo 中又调用了 bar,就会形成死循环,导致栈溢出错误
栈包含了一个信息在执行时的所有步骤(帧),栈的数据结构是一个后进先出的对象集合,因此新的帧,总是被添加在最上面。也因此事件循环会从上至下地处理栈中的帧,单帧所依赖的其他的帧,将会被添加在此帧的上面,以保证它从栈中可以获取到依赖的信息。
队列中包含一个待执行信息的列表,每一个信息都于一个函数相互联系,当栈为空时,队列中的一条信息就会被取出并且处理。处理的过程为调用该信息所关联的函数,然后将此帧添加到栈的顶部,当栈再次为空时,本次信息处理即视为结束;
堆是一个内存存储空间,它不关注内部存储的内容的保存顺序,堆中保存了所有正在被使用的变量和对象。同时也保存了一些当前作用域已经不会再使用到,但还没有被垃圾回收的帧。
并发是指同一时间有两个或更多的操作一起执行,由于运行时单线程的原因,事件循环内的信息都是线性执行的,每当一个函数被调用,队列中就被加入一个新的信息,如果栈是空的,那么函数就会立即执行,当所有的帧都被加入栈中之后,栈便从上至下一个个清楚(执行)这些帧,最后栈被清空,然后下一个信息将会被处理。
使用事件循环的好处是执行顺序是非常容易预测且容易追踪的,另一个好处就是,在事件循环内可以进行非阻塞 I/O 操作,意味着当一个应用在等待 I/O 操作的执行结果时,它还可以处理其他事情,比如处理用户输入。
事件循环的一个特点是,当一个信息需要大量的事件来处理时,应用会变得无响应,一个好的做法是,保持每个信息尽量简短,可能的话,将一个信息函数分割为多个小函数。
在JavaScript中,this的值通常由它所属的函数被调用的方式来决定,它的值不能在执行时通过赋值操作来设置,并且同一个函数以不同的方式被调用,其this值也可能不同;
全局上下文的this: 在全局中this操作符总是指向全局对象,在浏览器中,window对象即是全局对象;
函数上下文中的this:函数中的this操作符,指向函数的调用者。
call、apply、bind:可以使用这些方法来设置函数内部的this操作符的值,funcA.apply(真正的调用者, args)
一旦使用bind方法为一个函数内的this操作符进行了绑定,那么就不能再apply或者call去再次覆盖它。 在构造函数中,this指向对象的原型
1、可以在类方法中访问类属性
// 类
function Math() {}
// 类属性
Math.PI = '3.1415926';
// 类方法
Math.fn = function (radius) {
return radius * radius * this.PI;
// this 指向 Math
}
2、可以在实例方法中访问类属性
// 类
function Math() {}
// 类属性
Math.PI = '3.1415926';
// 实例方法
Math.prototype.fn = function (radius) {
// 使用 this.constructor (返回对象构造函数的引用)访问类属性
return radius * radius * this.constructor.PI;
// 因为 this 指向 new 出来的实例。 实例上没有 constructor
// 所以 this.constructor == Math.prototype.constructor
// Math.prototype.constructor = Math
}
3、不可以在类属性或者方法中访问实例属性或方法;
function Math() {
// 实例属性
this.PI = '3.1415';
}
// 类方法
Math.fn = function (radius) {
return radius * radius * this.PI; // this.PI是 undefined
// this 指向 Math
}
TypeScript 实现的 extends
var __extends = this.__extends || function(d, b) {
for(var p in b) {
// for in 遍历对象的实例,会迭代对象的实例属性
// 当遍历一个构造函数时,将会迭代类属性
// 这里场景用来遍历一个构造函数(类),即 子类 继承 父类的类属性和方法
if(b.hasOwnProperty(p)) {
d[p] = b][p];
}
}
// 以下就是一个原型式继承 让子类原型对象 继承 父类的原型对象
function __() {
this.constructor = d; // 修复constructor的指向
}
__.prototype = b.prototype;
d.prototype = new __();
// new __().__proto__ == b.prototype
}
当实际转换 extends 关键字的时候
class A extends B {
email: string = 'abc@123.com';
// public age 相当于就是 this.age = age;
constructor (name: string, public age: number) {
super(name)
}
}
// 转换成类似以下代码,就完成了extends关键字的寄生式组合继承
var A = /** @class */ (function (_super) {
__extends(A, _super); // 原型式继承
function A(name, age) {
var _this = _super.call(this, name) || this; // 调用父类的构造函数
_this.age = age;
_this.email = 'abc@123.com';
return _this;
}
return A;
}(B));
// counter.ts
class Counter {
private static _COUNTER = 0;
constructor() {};
private _changeBy(val: number) {
Counter._COUNTER += val;
}
public increment() {
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(val) {
this._changeBy(1);
}
Counter.prototype.value = function(val) {
return Counter._COUNTER;
}
Counter._COUNTER = 0; // 静态变量被声明成了类属性,而不是实例属性,因为它可以被所有实例共享。
return Counter;
})()
闭包函数可以访问到在创建的字面作用域上持续存在的变量,这些变量并不是函数原型或函数体的一部分,而是闭包上下文中的一部分,由于我们不能直接访问闭包上下文,那么该上下文中的变量就可以用来模拟私有成员。
TypeScript由于性能问题,并没有在运行时使用闭包模拟私有成员,不管我们添加和删除private修饰符,生成的JavaScript都不会有变化,这意味着在运行时,私有成员变成了公开成员,但是如果我们视图访问一个私有成员,TypeScript 编译器会在编译时抛错。
但是,使用闭包来模拟私有成员是完全可行的。
function makeCounter() {
// 闭包上下文
var _COUNTER = 0;
function changeBy(val) {
_COUNTER += val;
}
function Counter() {};
Counter.prototype.increment = function () {
};
Counter.prototype.value = function () {
return _COUNTER;
};
return new Counter();
}
当调用makeCounter
函数时,一个新的闭包上下文被创建,所以每个实例都有独立的上下文(_COUNTER
变量和 changeBy
方法),由于_COUNTER
和changeBy
不能被直接访问,所以可以说其为私有成员。
var c1 = makeCounter();
console.log(c1.value); // 0
c1.increment();
console.log(c1.value); // 1
console.log(c1._COUNTER); // undefined;
c1.changeBy(); // error, changeBy is not function