4.类与继承:
类
class Person {
constructor(name){
console.log(`构造函数执行了,${name}`)
}
}
let p1= new Person('jona')
constructor方法是类的构造函数是默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个默认的constructor方法会被添加。所以即使你没有添加构造函数,也是有默认的构造函数的。一般constructor方法默认返回实例对象this,但是也可以指定constructor方法返回一个全新的对象,让返回的实例对象不是该类的实例。
class Person {
constructor(name){
console.log(`构造函数执行了,${name}`)
this.name=name
}
showName(){
return `名字为${this.name}`
}
}
let p1= new Person('jona')
console.log(p1.showName)
showName函数不会自动执行,只有调用的时候才会执行,类内的方法只能用类的实例来调用。
ES6的类,完全可以看作构造函数的另一种写法。
console.log(typeof Person);//function
console.log(Person === Person.prototype.constructor);//true
上面代码表明,类的数据类型就是函数,类本身就指向构造函数。
console.log(Person.prototype);//输出的是一个对象
Person.prototype = {
getName(){
return '张三';
},
getAge(){
return '12';
}
};
构造函数的prototype属性,在ES6的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面,通过以上方法方式可以覆盖类中的方法,当然定义类的时候也可以通过这个方式添加方法。
继承
// 父类
class Person {
constructor(name){
console.log(`构造函数执行了,${name}`)
this.name=name
}
showName(){
return `名字为${this.name}`
}
}
let p1= new Person('jona')
console.log(p1.showName)
// 子类
class children extends Person{
constructor(agrs){
super(ags) //super的参数列表就是父类constructor的参数列表
}
showName (){
super.showName()//调用父级的方法也是用super
}
}
let p2 = new children('子类')
console.log(p2.name)
继承用extends,当继承后需要用super()来接收父类的constructor构造函数,否则报错,当new一个子类的时候先把参数传入子类构造函数再通过super()将父类的构造函数引入,就可以调用父类
子类自己定义的属性一定要定义在super之后,并使用this指向。在子类的构造函数中, 只有调用super之后, 才可以使用this关键字, 否则会报错。 这是因为子类实例的构建, 是基于对父类实例加工, 只有super方法才能返回父类实例。
super 关键字
super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。
第一种情况,super作为函数调用时,代表父类的构造函数。
ES6 要求,子类的构造函数必须执行一次super函数。
class A {}
class B extends A {
constructor() {
super();
}
}
上面代码中,子类B的构造函数之中的super(),代表调用父类的构造函数。这是必须的,否则 JavaScript 引擎会报错。
注意,super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B,因此super()在这里相当于A.prototype.constructor.call(this)。
class A {
constructor() {
console.log(new.target.name);//新对象名称
}
}
class B extends A {
constructor() {
super();
}
}
new A() // A
new B() // B
上面代码中,new.target指向当前正在执行的函数。可以看到,在super()执行时,它指向的是子类B的构造函数,而不是父类A的构造函数。也就是说,super()内部的this指向的是B。
作为函数时,super()只能用在子类的构造函数之中,用在其他地方就会报错。
class A {}
class B extends A {
m() {
super(); // 报错
}
}
上面代码中,super()用在B类的m方法之中,就会造成句法错误。
第二种情况,super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}
let b = new B();
上面代码中,子类B当中的super.p(),就是将super当作一个对象使用。这时,super在普通方法之中,指向A.prototype,所以super.p()就相当于A.prototype.p()。
这里需要注意,由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的。
class A {
constructor() {
this.p = 2;
}
}
class B extends A {
get m() {
return super.p;
}
}
let b = new B();
b.m // undefined
上面代码中,p是父类A实例的属性,super.p就引用不到它。
如果属性定义在父类的原型对象上,super就可以取到。
class A {}
A.prototype.x = 2;
class B extends A {
constructor() {
super();
console.log(super.x) // 2
}
}
let b = new B();
上面代码中,属性x是定义在A.prototype上面的,所以super.x可以取到它的值。
ES6 规定,在子类普通方法中通过super调用父类的方法时,方法内部的this指向当前的子类实例。
class A {
constructor() {
this.x = 1;
}
print() {
console.log(this.x);
}
}
class B extends A {
constructor() {
super();
this.x = 2;
}
m() {
super.print();
}
}
let b = new B();
b.m() // 2
上面代码中,super.print()虽然调用的是A.prototype.print(),但是A.prototype.print()内部的this指向子类B的实例,导致输出的是2,而不是1。也就是说,实际上执行的是super.print.call(this)。
由于this指向子类实例,所以如果通过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性。
class A {
constructor() {
this.x = 1;
}
}
class B extends A {
constructor() {
super();
this.x = 2;
super.x = 3;
console.log(super.x); // undefined
console.log(this.x); // 3
}
}
let b = new B();
上面代码中,super.x赋值为3,这时等同于对this.x赋值为3。而当读取super.x的时候,读的是A.prototype.x,所以返回undefined。
如果super作为对象,用在静态方法之中,这时super将指向父类,而不是父类的原型对象。
class Parent {
static myMethod(msg) {
console.log('static', msg);
}
myMethod(msg) {
console.log('instance', msg);
}
}
class Child extends Parent {
static myMethod(msg) {
super.myMethod(msg);
}
myMethod(msg) {
super.myMethod(msg);
}
}
Child.myMethod(1); // static 1
var child = new Child();
child.myMethod(2); // instance 2
上面代码中,super在静态方法之中指向父类,在普通方法之中指向父类的原型对象。
另外,在子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例。
class A {
constructor() {
this.x = 1;
}
static print() {
console.log(this.x);
}
}
class B extends A {
constructor() {
super();
this.x = 2;
}
static m() {
super.print();
}
}
B.x = 3;
B.m() // 3
上面代码中,静态方法B.m里面,super.print指向父类的静态方法。这个方法里面的this指向的是B,而不是B的实例。
注意,使用super的时候,必须显式指定是作为函数、还是作为对象使用,否则会报错。
class A {}
class B extends A {
constructor() {
super();
console.log(super); // 报错
}
}
上面代码中,console.log(super)当中的super,无法看出是作为函数使用,还是作为对象使用,所以 JavaScript 引擎解析代码的时候就会报错。这时,如果能清晰地表明super的数据类型,就不会报错。
class A {}
class B extends A {
constructor() {
super();
console.log(super.valueOf() instanceof B); // true
}
}
let b = new B();
上面代码中,super.valueOf()表明super是一个对象,因此就不会报错。同时,由于super使得this指向B的实例,所以super.valueOf()返回的是一个B的实例。
最后,由于对象总是继承其他对象的,所以可以在任意一个对象中,使用super关键字。
var obj = {
toString() {
return "MyObject: " + super.toString();
}
};
obj.toString(); // MyObject: [object Object]
类的 prototype 属性和 proto 属性
大多数浏览器的 ES5 实现之中, 每一个对象都有__proto__属性, 指向对应的构造函数的 prototype 属性。 Class 作为构造函数的语法糖, 同时有prototype 属性和__proto__属性, 因此同时存在两条继承链。
( 1) 子类的__proto__属性, 表示构造函数的继承, 总是指向父类。
( 2) 子类prototype属性的__proto__属性, 表示方法的继承, 总是指向父类的prototype属性。
举个栗子:
class A {}
class B extends A {}
B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true
静态方法与静态属性(static)
i)静态方法
类相当于实例的原型, 所有在类中定义的方法, 都会被实例继承。 如果在一个方法前, 加上static关键字, 就表示该方法不会被实例继承, 而是直接通过类来调用, 这就称为“ 静态方法”。
class Parent {
static classMethod() {
return 'hello';
}
}
Parent.classMethod() // 'hello'
var parent = new Parent ();
parent.classMethod()
// TypeError: parent.classMethod is not a function
上面代码中, Parent类的classMethod方法前有static关键字, 表明该方法是一个静态方法, 只可以直接在Parent类上调用( Parent.classMethod()), 而不可在Parent类的实例上调用。 如果在实例上调用静态方法, 会抛出一个错误, 表示不存在该方法。
父类的静态方法, 可以被子类继承。
class Parent {
static classMethod() {
return ‘hello’;
}
}
class Children extends Parent {}
Children.classMethod(); // ‘hello’
上面代码中, 父类Parent有一个静态方法, 子类children可以调用这个方法。
静态方法也是可以从super对象上调用的。
class Parent {
static classMethod() {
return 'hello';
}
}
class Children extends Parent {
static classMethod() {
return super.classMethod() + ', too';
}
}
Children.classMethod();
ii)静态属性
静态属性指的是 Class 本身的属性, 即Class.propname, 而不是定义在实例对象( this) 上的属性。
class Foo {}
Foo.prop = 1;
Foo.prop // 1
上面的写法为Foo类定义了一个静态属性prop。
目前, 只有这种写法可行, 因为 ES6 明确规定, Class 内部只有静态方法, 没有静态属性。
// 以下两种写法都无效
class Foo {
// 写法一
prop: 2
// 写法二
static prop: 2
}
Foo.prop // undefined
2:setter/getter的调用执行时机
class Person {
constructor (name, age) {
this.name = name;
this.age = age;
}
set name (name) {
console.log("setter");
this.name = name;
}
get name () {
console.log("getter");
return this.name;
}
}
var p = new Person("zhang", 25);
很快,我们就会发现代码报错了
这是因为,在构造函数中执行this.name=name的时候,就会去调用set name,在set name方法中,我们又执行this.name = name,进行无限递归,最后导致栈溢出(RangeError)。
我们稍作修改,让这个代码可以正常执行,达到我们想要的效果。
class Person {
constructor (name, age) {
this.name = name;
this.age = age;
}
set name (name) {
console.log("setter");
this._name = name;
}
get name () {
console.log("getter");
return this._name;
}
// 加一个成员方法
sayName () {
console.log(this.name);
}
}
var p = new Person("zhang", 25);
p.sayName();
执行结果为
setter
getter
zhang
到这里就可以明白了,原来只要this.name中的属性名和set name/get name后面的name一致,对this.name就会调用setter/getter,也就是说setter/getter是hook函数,而真实的存储变量是_name,我们可以在代码中直接获取它。
class Person {
constructor (name, age) {
this.name = name;
this.age = age;
}
set name (name) {
console.log("setter");
this._name = name;
}
get name () {
console.log("getter");
return this._name;
}
// 加一个成员方法
sayName () {
console.log(this.name);
}
}
var p = new Person("zhang", 25);
console.log(p._name); // "zhang"
执行结果为
setter
zhang
注意到结果并没有执行getter,因为我们直接访问了p._name,而不是p.name
只有getter定义只读属性
当一个属性只有getter没有setter的时候,我们是无法进行赋值操作的(第一次初始化也不行),这一点也是相当地坑。例如
class Person {
constructor (name) {
this.name = name;
}
// 只有getter
get name () {
console.log("getter");
return this.name;
}
}
var p = new Person("zhang");
执行结果为
报错
当同时没有getter和setter时,就可以正常读写属性
3:ajax:
原生:
if(window.XMLHttpRequest){
var xhr = new window.XMLHttpRequest();
}else{
//兼容IE浏览器
var xhr = new ActiveXObject('Microsoft.XMLHTTP');
}
//请求方式get/post
//请求URL
//ture 异步请求;false 同步请求
xhr.open('get','/ajax/getdata',true);
//给xhr 绑定事件监听状态的改变(状态码见下)
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
console.log(xhr.responseText);
}
}
//发送请求数据 //get方法send参数为空或null
xhr.send();
jQuery:
$.ajax({
//请求类型,get,post
type:'GET',
// 请求的数据类型,可以是html,json,xml等
dataType:'json',
//传输的数据
data:{
num1:num1,
num2:num2
},
//选择是否支持异步刷新,默认为true
async:true,
success:function(){
console.log('very good 请求成功');
},
error:function(){
console.log('对不起,请求失败');
}
})
以上资料部分来自网络,若有侵权,请联系作者删除。