目录
继承的方法
https://juejin.im/post/5bcb2e295188255c55472db0 原型链继承 组合继承 构造函数继承 class实现继承
class
class的继承
下面看用class实现继承的例子。注意几点:
- 用extends关键字创建子类
- 在子类的构造函数里访问
this
的属性之前,要调用super()
- 用super.XX访问基类的方法、属性
- 使用
new
构造了 Student类的一个实例。 它会调用之前定义的构造函数,创建一个Student类型的新对象,并执行构造函数初始化它
class Test {
insName: string;
constructor(name: string) {
this.insName = name;
console.log(name, this.insName);
}
}
class Person {
insName: string;
insAge: number;
constructor(name: string, age: number) {
this.insName = name;
this.insAge = age;
console.log("person", this.insName, name);
}
showName() {
console.log("Person", this.insName, this.insAge);
}
}
class Student extends Person {
insGender: string;
constructor(name: string, age: number, gender: string) {
super(name, age); //调用父类的 constructor(name: string, age: number)
this.insGender = gender; // 子类只有调用super()之后才可以使用this关键字
console.log("student", this.insName, this.insAge, this.insGender, name);
super.showName();
}
}
ngOnInit() {
const test = new Test("lake");
const student = new Student("lake", 19, "female");
}
打印:
super 关键字
如果属性定义在父类的原型对象上,super
就可以取到。
在子类普通方法中通过super
调用父类的方法时,方法内部的this
指向当前的子类实例。
Object.getPrototypeOf()
Object.getPrototypeOf
方法可以用来从子类上获取父类。
Object.getPrototypeOf(ColorPoint) === Point // true
Object.getPrototypeOf(ColorPoint) === Object //false 要直接继承的 间接的不算
因此,可以使用这个方法判断,一个类是否继承了另一个类。
构造函数
// es5
function Keith(height) {
this.height = height;
}
//es6
class Point {
constructor(height) {
this.height = height;
}
toString() {
return '233'
}
}
typeof Point // 'function'
Point === Point.prototype.constructor // true
上面代码中,Keith
就是构造函数,它提供模板,用来生成对象实例。(扩展:生成对象实例的几种方法)
构造函数的特点:
a:构造函数的函数名的第一个字母通常大写。
b:函数体内使用this关键字,代表所要生成的对象实例。
c:生成对象的时候,必须使用new命令来调用构造函数。
constructor
方法是类的构造函数,是一个默认方法,通过 new
命令创建对象实例时,自动调用该方法。一个类必须有 constructor
方法,如果没有显式定义,一个默认的 consructor
方法会被默认添加。所以即使你没有添加构造函数,也是会有一个默认的构造函数的。一般 constructor
方法返回实例对象 this
,但是也可以指定 constructor
方法返回一个全新的对象,让返回的实例对象不是该类的实例。类的所有方法都定义在类的prototype
属性上面。
实例的属性除非显式定义在其本身(即定义在this
对象上),否则都是定义在原型上(即定义在class
上)。
在类的实例上调用方法,其实就是调用原型上的方法。
es6的构造函数用关键字constructor来实现。可以通过this(和java/C#一样代表对象实例的成员访问)关键字来访问当前类体中的属性和方法。
class student{ //定义student类
name:string; //定义类的属性
constructor(myname:string){ //定义构造函数
this.name=myname;
}
study(){ //定义类的方法
//定义该方法所要实现的功能
}
}
// http://cw.hubwiz.com/card/c/55b724ab3ad79a1b05dcc26c/1/4/4/
new操作符原理
new命令的作用,就是执行一个构造函数,并且返回一个对象实例。使用new
命令时,它后面的函数调用就不是正常的调用,而是依次执行下面的步骤。
a:创建一个空对象,作为将要返回的对象实例。
b:将空对象的原型指向了构造函数的prototype属性。
c:将空对象赋值给构造函数内部的this关键字。
d:开始执行构造函数内部的代码。
也就是说,构造函数内部,this指向的是一个新生成的空对象,所有针对this的操作,都会发生在这个空对象上。构造函数之所谓构造函数,意思是这个函数的目的就是操作一个空对象(即this对象),将其构造为需要的样子。
属性表达式
类的属性名,可以采用表达式。
let methodName = 'getArea';
class Square {
constructor(length) {
// ...
}
[methodName]() {
// ...
}
}
上面代码中,Square
类的方法名getArea
,是从表达式得到的。
静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static
关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
new.target属性
new
是从构造函数生成实例对象的命令。ES6 为new
命令引入了一个new.target
属性,该属性一般用在构造函数之中,返回new
命令作用于的那个构造函数。如果构造函数不是通过new
命令或Reflect.construct()
调用的,new.target
会返回undefined
,因此这个属性可以用来确定构造函数是怎么调用的。
function Person(name) {
if (new.target === Person) {
this.name = name;
} else {
throw new Error('必须使用 new 命令生成实例');
}
}
var person = new Person('张三'); // 正确
var notAPerson = Person.call(person, '张三'); // 报错
上面这段代码确保构造函数只能通过new命令调用
super关键字(作为对象,作为函数)
在 子类的constructor
中必须调用 super
方法去继承父类的this对象。在 super()
执行时,它指向的是 子类 B 的构造函数,而不是父类 A 的构造函数。也就是说,super()
内部的 this
指向的是 B。
super
在普通方法之中,指向 A.prototype
,所以 super.c()
就相当于 A.prototype.c()
。【A:父类;B:子类】
通过 super
调用父类的方法时,super
会绑定子类的 this
。
存取器
在“类”的内部可以使用get
和set
关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。通过getters/setters来截取对对象成员的访问,能帮助你有效的控制对对象成员的访问。
当尝试设置属性时,set
语法将对象属性绑定到要调用的函数。
下面来看如何把一个简单的类改写成使用 get
和 set
。 首先,我们从一个没有使用存取器的例子开始。
class Employee {
fullName: string;
}
const employee = new Employee();
employee.fullName = "lake Go";
console.log(employee.fullName);
// 打印:lake Go。
使用存取器之后:
let passcode = "Passcode";
class EmployeeFillter {
private _fullName: string;
get fullName() {
return this._fullName;
}
set fullName(value: string) {
if (passcode && passcode === "Passcode") {
this._fullName = value;
} else {
console.log("Error: Unauthorized update of employee!");
}
}
}
const employeeFillter = new EmployeeFillter();
employeeFillter.fullName = "lake go by fillter";
console.log(employeeFillter.fullName);
// 打印:lake go by fillter。
把类当作接口
类定义会创建两个东西:类的实例类型和一个构造函数。 因为类可以创建出类型,所以你能够在允许使用接口的地方使用类。
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};
作用域 作用域链
作用域决定了代码区块中变量和其他资源的可见性。
作用域:全局作用域,函数作用域,块级作用域(es6)
块级作用域
块级作用域可通过新增命令let和const声明,所声明的变量在指定块的作用域外无法被访问。
特点:
-
let/const 声明并不会被提升到当前代码块的顶部
-
禁止重复声明
-
循环中的绑定块作用域的妙用
ngOnInit() {
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
}
ngOnInit() {
var x = 11;
var obb = {
x: 222,
y: {
x: 333,
obc: function () {
var x = 111;
var obj = {
x: 22,
say: function () {
console.log(this.x);
},
};
obj.say();
},
},
};
obb.y.obc();
}
// 打印22。因为es5里函数的this的指向有2种。当函数定义在全局,this指向全局作用域;
// 当函数作为对象的方法时,this指向当前这个对象。
如果say()函数从 say: function () {console.log(this.x);} 变为 say: function () {console.log(x);} 时候,那么obb.y.obc();打印为111(而不是22);注释掉x = 111,那么obb.y.obc();打印为11(而不是333)。 ==》作用域链
当在内部函数中,需要访问一个变量的时候,首先会访问这个函数本身的变量对象,是否有这个变量。如果没有,则会继续沿作用域往上查找,直到全局作用域。如果在某个变量对象中找到则使用该变量对象中的变量值。变量的查找:是沿着作用域链来实现的。作用域和作用域链在这个变量被定义的时候决定的,和执行顺序无关。内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境的任何变量和函数。
数组的扩展
扩展运算符(spread)是三个点(...
),它将一个数组转为用逗号分隔的参数序列。
用扩展运算符代替apply方法
由于扩展运算符可以展开数组,所以不再需要apply
方法,将数组转为函数的参数了。
// ES5 的写法
function f(x, y, z) {
// ...
}
var args = [0, 1, 2];
f.apply(null, args);
// ES6的写法
function f(x, y, z) {
// ...
}
let args = [0, 1, 2];
f(...args);
扩展运算符的运用
求数组中最大值,通过push
函数将一个数组添加到另一个数组的尾部,扩展数组,合并数组等。https://es6.ruanyifeng.com/#docs/array#Array-from 【例子请见这个博客】
杂
在代码块内,使用let
命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
上面的函数有两个代码块,都声明了变量n
,运行后输出 5。这表示外层代码块不受内层代码块的影响。如果两次都使用var
定义变量n
,最后输出的值才是 10。
ES6 新增了let
命令,用来声明变量。它的用法类似于var
,但是所声明的变量,只在let
命令所在的代码块内有效。
const
声明一个只读的常量。一旦声明,常量的值就不能改变。对于const
来说,只声明不赋值,就会报错。const
的作用域与let
命令相同:只在声明所在的块级作用域内有效。
从ES6开始,JavaScript引入了解构赋值,可以同时对一组变量进行赋值。
数组的解构赋值:ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []
不完全解构:即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。
let [x, y] = [1, 2, 3];
x // 1
y // 2
let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4
解构赋值允许指定默认值。
let [foo = true] = [];
foo // true
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
默认值可以引用解构赋值的其他变量,但该变量必须已经声明。
let [x = 1, y = x] = []; // x=1; y=1
let [x = 1, y = x] = [2]; // x=2; y=2
let [x = 1, y = x] = [1, 2]; // x=1; y=2
let [x = y, y = 1] = []; // ReferenceError: y is not defined
上面最后一个表达式之所以会报错,是因为x
用y
做默认值时,y
还没有声明。
let { foo: baz } = { foo: "aaa", bar: "bbb" };
baz // "aaa"
foo // error: foo is not defined
上面代码中,foo
是匹配的模式,baz
才是变量。真正被赋值的是变量baz
,而不是模式foo
。
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。undefined
和null
无法转为对象,所以对它们进行解构赋值,都会报错。
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。
function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
参数变量是默认声明的,所以不能用let
或const
再次声明。
function foo(x = 5) {
let x = 1; // error
const x = 2; // error
}
参考