整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

JavaScript:class继承

上面的章节中我们看到了JavaScript的对象模型是基于原型实现的,特点是简单,缺点是理解起来比传统的类-实例模型要困难,最大的缺点是继承的实现需要编写大量代码,并且需要正确实现原型链。

有没有更简单的写法?有!

新的关键字class从ES6开始正式被引入到JavaScript中。class的目的就是让定义类更简单。

我们先回顾用函数实现Student的方法:

function Student(name) {
 this.name = name;
}
Student.prototype.hello = function () {
 alert('Hello, ' + this.name + '!');
}

如果用新的class关键字来编写Student,可以这样写:

class Student {
 constructor(name) {
 this.name = name;
 }
 hello() {
 alert('Hello, ' + this.name + '!');
 }
}

比较一下就可以发现,class的定义包含了构造函数constructor和定义在原型对象上的函数hello()(注意没有function关键字),这样就避免了Student.prototype.hello = function () {...}这样分散的代码。

最后,创建一个Student对象代码和前面章节完全一样:

var xiaoming = new Student('小明');
xiaoming.hello();

class继承

用class定义对象的另一个巨大的好处是继承更方便了。想一想我们从Student派生一个PrimaryStudent需要编写的代码量。现在,原型继承的中间对象,原型对象的构造函数等等都不需要考虑了,直接通过extends来实现:

class PrimaryStudent extends Student {
 constructor(name, grade) {
 super(name); // 记得用super调用父类的构造方法!
 this.grade = grade;
 }
 myGrade() {
 alert('I am at grade ' + this.grade);
 }
}

注意PrimaryStudent的定义也是class关键字实现的,而extends则表示原型链对象来自Student。子类的构造函数可能会与父类不太相同,例如,PrimaryStudent需要name和grade两个参数,并且需要通过super(name)来调用父类的构造函数,否则父类的name属性无法正常初始化。

PrimaryStudent已经自动获得了父类Student的hello方法,我们又在子类中定义了新的myGrade方法。

ES6引入的class和原有的JavaScript原型继承有什么区别呢?实际上它们没有任何区别,class的作用就是让JavaScript引擎去实现原来需要我们自己编写的原型链代码。简而言之,用class的好处就是极大地简化了原型链代码。

你一定会问,class这么好用,能不能现在就用上?

现在用还早了点,因为不是所有的主流浏览器都支持ES6的class。如果一定要现在就用上,就需要一个工具把class代码转换为传统的prototype代码,可以试试Babel这个工具。

需要浏览器支持ES6的class,如果遇到SyntaxError,则说明浏览器不支持class语法,请换一个最新的浏览器试试。



、构造函数

首先,构造函数其实就是JavaScript程序中定义好的函数,我们可以直接使用。构造函数实际也是一种函数,它是专门用于生成定义对象,通过构造函数生成的对象,称为实例化对象。

构造函数分为两种,一种是JavaScript程序定义好的构造函数,我们成为称为内置构造函数,另外一种是程序员自己定义的构造函数,称为自定义构造函数。

构造函数虽然也是一种函数,但是和普通函数是区别的: 

1、构造函数一定要和关键词new一起使用,new关键词会自动的给构造函数定义一个对象,并且返回这个对象,我们只要对这个对象设定属性,设定方法就可以使用。  

2、构造函数为了和其他函数区别,语法规范规定构造函数的函数名称,第一个字母必须大写,使用大驼峰命名法。  

3、构造函数给对象定义属性和方法的语法,与一般函数不同

//自定义构造函数
function Person(name,sex,age,addr){
  // 定义属性
  this.name = name;
  this.sex = sex;
  this.age = age;
  this.addr = addr;
  // 定义方法
  this.fun = function(){
    console.log(this.name,this.sex,this.age,this.addr);
  }
}
// 生成实例化对象
const person = new Person('终结者','男',28,'杭州');
console.log(person); //输出实例化对象


普通构造函数实现继承

//普通构造函数继承
//动物
function Animal() {
    this.eat = function () {
        console.log('animal eat')
    }
}
 
//狗
function Dog() {
    this.bark = function () {
        console.log('dog bark')
    }
}
//绑定原型,实现继承
Dog.prototype = new Animal()
//哈士奇
var hashiqi = new Dog()



二、class

ES6构造函数语法:ES6与ES5构造函数语法对比,其功能作用完全相同,只是语法不同。ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。

class Perosn{// ES6class方法定义构造函数
  //constructor是构造器,定义实例化对象的属性和属性值, ()中的是参数
  constructor (name,age,sex){
    this.name = name;
    this.age = age; 
  	this.sex = sex;
	}
  //定义方法,虽然没声明,但是也是定义在构造函数中的prototype中
  printAll(){
  	console.log(this.name,this.age,this.sex);
	}
}
// 生成实例化对象
const test = new Perosn('终结者','男',28,'杭州');
console.log(person);//输出实例化对象
person.printAll();  //输出方法

上面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。也就是说,ES5的构造函数Person,对应ES6的Person类的构造方法。

定义“类”的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。


Class实现继承

class Animal{
    constructor(name){
        this.name=name
    }
    eat(){
        console.log(this.name +'eat');
    }
}
class Dog extends Animal{
    constructor(name){
        super(name)//访问和调用Dog对象的父对象Animal上的函数。
        this.name=name
      // 在派生类中, 必须先调用 super() 才能使用 "this",忽略这个,将会导致一个引用错误。
    }
    bark(){
        console.log(this.name+'bark');
    }
}
const dog=new Dog("泰迪");
dog.eat();
dog.bark();

构造函数的prototype属性,在ES6的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。

ES6中的class语法就是ES5中构造函数的另一种写法,一种更高级的写法,

class语法的底层还是ES5中的构造函数,只是把构造函数进行了一次封装而已。

ES6出现的目的为了让我们的让对象原型的写法更加清晰、在语法上更加贴合面向对象的写法、更像面向对象编程让JavaScript更加的符合通用编程规范,即大部分语言对于类和实例的写法,class实现继承更加容易理解,更易于后端语言使用。



三、构造函数与Class的区别

  • Class 类中不存在变量提升

类不存在变量提升(hoist),这一点与 ES5 完全不同。

 // ES5
var bar  = new Bar(); // 可行
function Bar() {
  this.bar = 42;
}
    
//ES6
const foo = new Foo(); // Uncaught ReferenceError
class Foo {
  constructor() {
    this.foo = 42;
  }
}
  • class内部会启用严格模式

类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。考虑到未来所有的代码,其实都是运行在模块之中,所以 ES6 实际上把整个语言升级到了严格模式。

// ES5
function Bar() {
  // 引用一个未声明的变量
  baz = 42; // it's ok
}
var bar  = new Bar(); 

// ES6
class Foo {
  constructor(){
    // 引入一个未声明的变量
    fol = 42;
    // Uncaught ReferenceError: fol is not defined
  }
}
let foo = new Foo();
  • class的所有方法都是不可枚举的

ES6 中的 class,它的方法(包括静态方法和实例方法)默认是不可枚举的,而构造函数默认是可枚举的。细想一下,这其实是个优化,让你在遍历时候,不需要再判断 hasOwnProperty 了

// ES5
function Bar() {}   
Bar.answer = function () {};
Bar.prototype.print = function () {};
console.log(Object.keys(Bar));// ["answer"]
console.log(Object.keys(Bar.prototype))// ["print"]
// ES6
class Foo {
	constructor(){}
  static answer() {}
  print(){}
}
console.log(Object.keys(Foo))// []
console.log(Object.keys(Foo.prototype));// []
  • class 必须使用new调用

class 必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。

// ES5
function Bar(){ }
var bar = Bar();// it's ok;
// ES6
class Foo {
}
let foo = Foo();
// Uncaught TypeError: Class constructor Foo cannot be invoked without 'new'

  • class 内部无法重写类名
// ES5 
function Bar() {
	Bar = 'Baz';
	this.bar = 42;
}
var bar = new Bar();
console.log(bar);// Bar {bar: 42}
console.log(Bar);// 'Baz'

// ES6
class Foo {
	constructor(){
		this.foo = 42;
		Foo = 'Fol'; // Uncaught TypeError: Assignment to constant variable.
	}
}
let foo = new Foo();
Foo = 'Fol';// it's ok
  • class 的继承有两条继承链

一条是:子类的__proto__指向父类

另一条:子类prototype属性的__proto__属性指向父类的prototype属性.

ES6的子类可以通过__proto__属性找到父类,而ES5的子类通过__proto__找到Function.prototype

// ES5
function Super() {}
function Sub() {}
Sub.prototype = new  Super();
Sub.prototype.constructor = Sub;
var sub = new Sub();
console.log( Sub.__proto__ === Function.prototype);// true

// ES6
class Super{}
class Sub extends Super {}
let sub = new Sub();
console.log( Sub.__proto__ === Super);// true
  • ES5与ES6子类this的生成顺序不同。

ES5的继承是先建立子类实例对象this,再调用父类的构造函数修饰子类实例(Surper.apply(this))。

ES6的继承是先建立父类实例对象this,再调用子类的构造函数修饰this。即在子类的constructor方法中必须使用super(),之后才能使用this.

  • 正是因为this的生成顺序不同,所有ES5不能继承原生的构造函数,而ES6可以继承
// ES5
function MyES5Array() {
	Array.apply(this, arguments);
  // 原生构造函数会忽略apply方法传入的this,
  //即this无法绑定,先生成的子类实例,拿不到原生构造函数的内部属性。
}
MyES5Array.prototype = Object.create(Array.prototype, {
	constructor: {
    value: MyES5Array,
    writable: true,
    configurable: true,
    enumerable: true
   }
})
var arrayES5 = new MyES5Array();
arrayES5[0] = 3;
console.log(arrayES5.length);// 0 
arrayES5.length = 0;
console.log(arrayES5[0]);// 3

// ES6
class arrayES6 extends Array {
	constructor(...args){
		super(...args);
	}
}
let arrayes6 = new arrayES6();
arrayes6[0] = 3;
console.log(arrayes6.length);// 1
arrayes6.length = 0;
console.log(arrayes6[0]);// undefined
  • ES6可以继承静态方法,而构造函数不能
  • class 的所有方法(包括静态方法和实例方法)都没有原型对象 prototype,所以也没有[[construct]],不能使用new来调用。

需要注意一点:

ES6的class语法无法执行预解析,是不能被提前调用;

ES5的function函数可以提前调用,但是只能调用属性不能调用方法。


天在写页面的时候,发现class="1212-sale"开头的样式无法调用,后来大群里有同学说class样式不能以数字的开头。试了一下果然是的!为了避免犯同样的错误,我上网查了一下css命名规范。整理了一下:

一、命名规则说明:1、所有的命名最好都小写2、属性的值一定要用双引号("")括起来,且一定要有值如class="divcss5",id="divcss5"3、每个标签都要有开始和结束,且要有正确的层次,排版有规律工整4、空元素要有结束的tag或于开始的tag后加上"/"5、表现与结构完全分离,代码中不涉及任何的表现元素,如style、font、bgColor、border等6、<h1到<h5>的定义,应遵循从大到小的原则,体现文档的结构,并有利于搜索引擎的查询7、给每一个表格和表单加上一个唯一的、结构标记id8、给图片加上alt标签9、尽量使用英文命名原则10、尽量不缩写,除非一看就明白的单词11、类名的第一个字符不能使用数字!它无法在 Mozilla 或 Firefox 中起作用

二、相对网页外层重要部分CSS样式命名:外套 wrap ------------------用于最外层头部 header ----------------用于头部主要内容 main ------------用于主体内容(中部)左侧 main-left -------------左侧布局右侧 main-right -----------右侧布局导航条 nav -----------------网页菜单导航条内容 content ---------------用于网页中部主体底部 footer -----------------用于底部

CSS命名其它说明:DIV+CSS命名小结:无论是使用“.”(小写句号)选择符号开头命名,还是使用“#”(井号)选择符号开头命名都无所谓,但我们最好遵循,主要的重要的特殊的最外层的盒子用“#”(井号)选择符号开头命名,其它都用“.”(小写句号)选择符号开头命名,同时考虑命名的CSS选择器在HTML中重复使用调用。通常我们最常用主要命名有:wrap(外套、最外层)、header(页眉、头部)、nav(导航条)、menu(菜单)、title(栏目标题、一般配合h1\h2\h3\h4标签使用)、content (内容区)、footer(页脚、底部)、logo(标志、可以配合h1标签使用)、banner(广告条,一般在顶部)、copyRight(版权)。其它可根据自己需要选择性使用。

让我们来看下这个类名定义:  .right-red { color:red; }  你可能很明确的知道这个class选择符的所起的作用。但是这里还有个问题,当你在一星期的时间需要重新设计。在重新设计的时候,这个模块被放置到了左边,而且还是绿色。这个类就不再有存在的价值。所以现在不得不选择,要么改变所有的属性值,要么放着它不动,这可能导致混乱。  最好不要在你的类名或者ID名中去加入颜色或者长宽的尺寸等带有属性的名字。你应该避免任何的属性值都是直接的词汇。(如box)直接属性可能会导致内容的分离。让我们来看看最合理ID/CLASS的命名规范:  .product-description { color: red; }  用这种样式定义的product-description(产品描述),不管你怎么改变,她都是那么的干净清晰。