上面的章节中我们看到了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定义对象的另一个巨大的好处是继承更方便了。想一想我们从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()
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实现继承更加容易理解,更易于后端语言使用。
类不存在变量提升(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;
}
}
类和模块的内部,默认就是严格模式,所以不需要使用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();
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调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用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'
// 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
一条是:子类的__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的继承是先建立子类实例对象this,再调用父类的构造函数修饰子类实例(Surper.apply(this))。
ES6的继承是先建立父类实例对象this,再调用子类的构造函数修饰this。即在子类的constructor方法中必须使用super(),之后才能使用this.
// 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语法无法执行预解析,是不能被提前调用;
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(产品描述),不管你怎么改变,她都是那么的干净清晰。
*请认真填写需求信息,我们会在24小时内与您取得联系。