Es6(4)----类与继承

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('对不起,请求失败');
    }
})

以上资料部分来自网络,若有侵权,请联系作者删除。

猜你喜欢

转载自blog.csdn.net/liveingcc/article/details/86635354
今日推荐