1、ES6基础
ECMAScript(ES6) 是JavaScript语言的下一代标准,已经在2015年6月正式发布了;在 JavaScript 的基础上做了重大的更新,提供了更优雅的语法和特性。ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现(另外的ECMAScript 方言还有 Jscript 和 ActionScript )。
ES6 新特性
(1)let 和 const
- let 和 const 相比于 var 声明的变量有块作用域,let 和 const 只在于当前的块作用域中有效。而 var 声明的变量是在函数作用域内有效。
- const 和 let 的区别则在于, const 声明变量的同时必须立即给一个初始值,且无法在对该值进行修改。如果该初始值是一个对象,那么该对象的值是可以被修改的。
{
let a = 1; // a 只能在当前块中被访问
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
(2)变量的解构赋值
按照一定的模式,从数组或对象中提取值,对变量进行赋值的操作。
- 数组的解构
var [a,b,c] = [1,2,3] // 结果 a=1 b=2 c=3
var [a,[b,c]] = [1,[2,3]] // 结果 a=1 b=2 c=3
//解构不成功
var [a] = []; // a = undefined
var [a, b] = [1]; //a=1 b=undefined
//不完全解构
var [a] = [1,2]; //a=1
//默认值
var [a=1] = [] //a=1
//如果等号的右边不是数组(或者严格地说,不是可遍历的结构),那么将会报错
let [a] = 1;
let [a] = false;
let [a] = NaN;
let [a] = undefined;
let [a] = null;
let [a] = {};
- 对象的解构
var { b, a } = { a: "aaa", b: "bbb" };
a // "aaa"
b // "bbb"
(3)字符串拓展
- 模板字符串
模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串(类似 pre 标签的作用),或者在字符串中嵌入变量。
// 普通字符串 想要换行需要加上 '\n'
`In JavaScript '\n' is a line-feed.`
// 多行字符串
`In JavaScript this is
not legal.`
// 字符串中嵌入变量
var name = "ac", time = "today";
`Hello ${name}, how are you ${time}?` // Hello ac, how are you today?
- rest 展开运算符
function example(...values){
console.log(values)// console: [1,2,3,4]
}
example(1,2,3,4)
var a = [1,2,3]
var b = [...a,4,5,6] //b = [1,2,3,4,5,6]
(4)箭头函数
- ES6 提供了新的方式 => 来定义函数
var func = parm => parm
等同于
var func = function (parm){
return parm
}
- 如果函数没有参数或有多个参数,那么:
var func = () => //some code
等同于
var func = function (){
some code
}
var func = (parm1,parm2) => //some code
等同于
var func = function (parm1,parm2){
some code
}
- 如果箭头函数的函数体只包含一行代码,则可以不需要写大括号以及 return 语句返回(如果有返回值)
var sum = (num1,num2) => num1+num2
等同于
var sum = (num1,num2) => {return num1+num2}
等同于
var sum = function(num1,num2){return num1+num2}
- 箭头函数使得表达更加简洁
[1,2,3].map( item=> 2 * item)
等同于
[1,2,3].map(function(item){
return item * 2
})
[1,3,2].sort((a,b) => a - b)
- 箭头函数中的
this
总是指向词法作用域,也就是外层调用者obj
:
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
return fn();
}
};
obj.getAge(); // 25
2、面向对象编程
1、封装
(1)生成实例对象的原始模式
var Cat = {
name : '',
color : ''
} // 原型对象
// 生成两个实例对象
var cat1 = {};
cat1.name = "大毛“;
cat1.color = "黄色";
var cat2 = {};
cat2.name = "二毛";
cat2.color = "黑色";
这就是最简单的封装,把两个属性封装在一个对象里。缺点:多个对象书写较麻烦;看不出实例与原型间的联系
(2)工厂模式
function Cat(name, color) {
return {
name:name,
color:color
}
}
//生成实例对象,等于调用函数
var cat1 = Cat("大毛","黄色");
var cat2 = Cat("二毛","黑色");
缺点:cat1和cat2之间没有内在的联系,不能反映出它们是同一个原型对象的实例
(3)构造函数(Constructor)模式
构造函数就是普通函数,但是在内部使用了this变量。对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例对象上。
function Cat(name,color){
this.name = name;
this.color = color;
}
// 生成实例对象
var cat1 = new Cat("大毛","黄色");
var cat2 = new Cat("二毛","黑色");
// cat1和cat2会自动含有constructor属性
alert(cat1.constructor == Cat); //true
alert(cat2.constructor == Cat); //true
//instanceof运算符,验证原型对象和实例对象之间的关系
alert(cat1 instanceof Cat); //true
alert(cat2 instanceof Cat); //true
//相同的属性和方法占用内存不同
alert(cat1.eat == cat2.eat);//false
缺点:相同的属性和方法会占用不同的内存。
(4)Prototype模式
JavaScript规定,每一个构造函数都有一个prototype属性,指向另一个对象,这个对象的所有属性和方法,都会被构造函数的实例继承。因此,可以把那些不变的属性和方法直接定义在prototype对象上。
function Cat(name, color) {
this.name = name;
this.color = color;
}
Cat.prototype.type = "猫科动物";
Cat.prototype.eat = function(){
alert("吃老鼠")
};
//生成实例
var cat1 = new Cat("大毛","黄色");
var cat2 = new Cat("二毛","黑色");
alert(cat1.type); //猫科动物
cat1.eat(); //吃老鼠
//相同属性和方法占用同一个内存
alert(cat1.eat == cat2.eat); //true
2、继承
(1)使用call或apply方法将父对象的构造函数绑定在子对象上:
function Cat(name,color){
Animal.apply(this,arguments);
this.name = name;
this.color = color;
}
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); //动物
(2)prototype模式
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); //动物
(3)直接继承prototype
function Animal() {}
Animal.prototype.species = "动物";
Cat.prototype = Animal.prototype;
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); //动物)
缺点:Cat.prototype和Animal.prototype指向同一个对象,任何对Cat.prototype的修改都会反映到Animal.prototype上
(4)利用空对象作为中介
var F = function(){};
F.prototype = Animal.prototype;
Cat.prototype = new F();
Cat.prototype.constructor = Cat;// 修改Cat的prototype对象不会影响到Animal的prototype对象
alert(Animal.prototype.constructor); //Animal
//将上面的方法封装成一个函数,便于使用
function extend(Child, Parent) {
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.uber = Parent.prototype;
}
//使用
extend(Cat,Animal);
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); //动物
(5)拷贝继承
//把Animal的所有不变属性放到prototype对象上
function Animal(){}
Animal.prototype.species = "动物";
//用函数来实现属性拷贝目的
function extend2(Child,Parent) {
var p = Parent.prototype;
var c = Child.prototype;
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
}
//使用
extend2(Cat,Animal);
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); //动物
3、非构造函数的继承
(1)object()方法
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
//第一步先在父对象的基础上,生成子对象:
var Doctor = object(Chinese);
//然后,再加上子对象本身的属性:
Doctor.career = '医生';
//子对象已经继承了父对象的属性了
alert(Doctor.nation); //中国
(2)浅拷贝
function extendCopy(p) {
var c = {};
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
return c;
}
//使用的时候,这样写:
var Doctor = extendCopy(Chinese);
Doctor.career = '医生';
alert(Doctor.nation); // 中国
缺点:如果父对象的属性等于数组或另一个对象,那么实际上,子对象获得的只是一个内存地址,而不是真正拷贝,因此存在父对象被篡改的可能。
(3)深拷贝
function deepCopy(p, c) {
var c = c || {};
for (var i in p) {
if (typeof p[i] === 'object') {
c[i] = (p[i].constructor === Array) ? [] : {};
deepCopy(p[i], c[i]);
} else {
c[i] = p[i];
}
}
return c;
}
//使用的时候这样写:
var Doctor = deepCopy(Chinese);
4、Class 的基本语法
(1)类的定义(类的数据类型就是函数,类本身就指向构造函数),使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致。构造函数的prototype属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
- Object.assign()方法可以很方便地一次向类添加多个方法
class Point {
constructor(){
// ...
}
}
Object.assign(Point.prototype, {
toString(){},
toValue(){}
});
- 类必须使用new调用,否则会报错。
- 实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。
//定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
var point = new Point(2, 3);
point.toString() // (2, 3)
point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') // true
上面代码中,x和y都是实例对象point自身的属性(因为定义在this变量上),所以hasOwnProperty方法返回true,而toString是原型对象的属性(因为定义在Point类上),所以hasOwnProperty方法返回false。
- 取值函数(getter)和存值函数(setter):在“类”的内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
class MyClass {
constructor() {
// ...
}
get prop() {
return 'getter';
}
set prop(value) {
console.log('setter: '+value);
}
}
let inst = new MyClass();
inst.prop = 123;
// setter: 123
inst.prop
// 'getter'
- 属性表达式:类的属性名,可以采用表达式。
let methodName = 'getArea';
class Square {
constructor(length) {
// ...
}
[methodName]() {
// ...
}
}
- Class 表达式
const MyClass = class Me {
getClassName() {
return Me.name;
}
};//这个类的名字是MyClass而不是Me,Me只在 Class 的内部代码可用,指代当前类
采用 Class 表达式,可以写出立即执行的 Class
let person = new class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}('张三');
person.sayName(); // "张三"
(2)静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
- 如果静态方法包含this关键字,这个this指的是类,而不是实例
- 静态方法可以与非静态方法重名。
- 父类的静态方法,可以被子类继承。
- 静态方法也是可以从super对象上调用的。
5、Class 的继承
Class 可以通过extends关键字实现继承,constructor方法和toString方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。
子类必须在constructor方法中调用super方法
class Point {
}
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
网友评论