JS 内置类继承和类的混入、字面量增强、解构、let-const
1.类的相关用法
1.1 继承内置类
class Person{ //* Person默认是继承Object类
}
class HyArray extends Array{
firstItem(){
return this[0];
}
lastItem(){
return this[this.length-1]
}
}
var arr=new HyArray(1,2,3,4);
console.log(arr);
console.log(arr.firstItem());
console.log(arr.lastItem());
1.2 类的混入mixin
JS只能允许有一个直接的父类,不能继承多个类。
混入的本质是继承。
混入的实现的作用:继承多个类,然后复用这些类的一些方法和属性
// * JS只支持单继承 ,只能有一个父类
class Person{
}
function mixinRunner(baseClass){
class newClass extends baseClass{
running(){
console.log("跑步");
}
}
return newClass;
}
function mixinEater(baseClass){
class newClass extends baseClass{
eating(){
console.log("吃东西");
}
}
return newClass
}
class Student extends Person{
}
var newClass=mixinEater(mixinRunner(Student));
var p=new newClass()
p.running();
p.eating()
react开发更加接近于原生js开发,更加灵活。
react(双刃剑):①对开发者要求比较高 ② 更加容易出错 ③维护起来比较困难。
react的redux(状态管理工具):相当于vue的vuex
2.JavaScript的多态
面向对象的三大特性:封装、继承、多态
抽象:将现实世界的事物抽象为代码中的某个数据结构。
2.1 维基百科的解释
多态(polymorphism)指为不同数据类型的实体提供统一的接口,或使用一个单一的符号来表示多个不同的类型。
个人总结:不同的数据类型进行同一个操作,表现出不同的行为,就是多态的体现。
2.1.1 传统面向对象多态的特点
传统面向对象实现多态的三个特点:
- 必须使用有子类继承父类的方法
- 必须重写父类的方法
- 必须有父类引用子类对象
// * 传统面向对象的三个必要条件
// * 1.继承是实现多态的前提
// * 2. 必须有重写
// * 3. 必须有父类引用子类对象
class Shape{
getArea(){
console.log("获取面积");
}
}
class Rectangle extends Shape{
getArea(){
return 100;
}
}
class Circle extends Shape{
getArea() {
return 1;
}
}
var r=new Rectangle();
var c=new Circle();
function calcArea(shape:Shape){
shape.getArea()
}
console.log(calcArea(r));
console.log(calcArea(c));
2.1.2 javascript面向对象多态的特点
function calcArea(foo){
console.log(foo.getArea());
}
var obj1={
name:"wjy",
getArea(){
return 100;
}
}
class Person{
getArea(){
return 1000
}
}
var p=new Person();
calcArea(p);
calcArea(obj1);
// * 不同数据类型的对象执行同一个操作,会呈现不同的形式。
function sum(m,n){
return m+n;
}
// * 这也是不同数据类型的数据,执行同一个操作,会呈现不同的表现形式。
console.log(sum(1,2));
console.log(sum("hello ","world"));
3.字面量的增强
3.1 对象字面量和块级作用域的区别
var obj={};//{}为对象字面量
{ //这个是块级作用域
}
3.2 属性简写 (property shorthand)
es5之前对对象属性的定义是这样的:
var name="wjy";
var age=20;
var obj={
name:name,
age:age
}
es6:如果属性和变量名一致,可以缩写。
var name="wjy";
var age=20;
var obj={
name,
age
}
3.3 方法简写 (method shorthand)
es5之前:
// * 对象字面量增强
var obj={
foo:function(){
}
}
es6之后:
var obj={
foo(){
}
}
45.png
3.4 计算属性名(Computed Property Names)
es5之前定义的计算属性:属性名是通过计算出来的
var name="wjy"
var obj={};
obj[name+123]="123"
es6之前可以定义这样定义计算属性:
var name="wjy";
var obj={
[name+123]:"123"
}
4.解构Destructuring
ES6中新增了一个从数组或对象中方便获取数据的方法,称之为 解构Destructuring。
4.1 数组的解构
数组是按顺序解构的
- 数组中的元素全部解构
- 解构后面的参数,可以通过英文逗号跳过
- 解构出数组
- 解构的默认值
var names=["abc","efg","ddd"]
// 对数组的解构
var [item1,item2,item3]=names;
console.log(item1,item2,item3);//abc efg ddd
将es6转化为es5的代码:
"use strict";
var names = ["abc", "efg", "ddd"]; // 对数组的解构
var item1 = names[0],
item2 = names[1],
item3 = names[2];
console.log(item1, item2, item3);
示例代码:
var names=["abc","efg","ddd"]
// * 对数组的解构
var [item1,item2,item3]=names;
console.log(item1,item2,item3);//abc efg ddd
// * 解构后面的元素
var [,itemA,itemB]=names;
console.log(itemA,itemB);//efg ddd
// * 解构出一个元素,后面的元素放到一个新数组中,用到了剩余参数,
var [itema,...newNames]=names;
console.log(itema,newNames);//abc [ 'efg', 'ddd' ]
// * 解构的默认值
var [a,b,c,d='aaa']=names;
console.log(a,b,c,d); //abc efg ddd aaa
4.2 对象的解构
对象的解构是一个大括号。
对象的解构是按照key来解构的。
它默认是根据变量名去取对象的某个属性名也是这个变量名,如果变量名在对象的属性中找不到,这个变量最后的值是undefined
- 基本解构过程
- 任意顺序
- 重命名
- 默认值
4.2.1 全部解构
var obj={
name:"wjy",
age:20,
height:1.88
}
// * 对象的解构用的是大括号,不必按照顺序解构
var {name,age,height}=obj;
console.log(name,age,height);//wjy 20 1.88
4.2.2 部分解构
// * 也可以部分解构
var obj={
name:"wjy",
age:20,
height:1.88
};
var {age}=obj;
4.2.3 给新的变量名赋值
var obj={
name:"wjy",
age:20,
height:1.88
};
// * 从这个对象找到属性为name,并将值赋给一个新的变量(这里是newName)
var {name:newName}=obj;
console.log(newName);//wjy
4.2.4 默认值
当要提取的对象没有对应的属性,变量就被赋予默认值。
var obj={
name:"wjy",
age:20,
height:1.88
};
// * 默认值
var {address="怀化"}=obj;
console.log(address);//怀化
4.3 应用场景
- 比如在开发中拿到一个变量时,自动对其进行解构使用。
- 比如对函数的参数解构
var obj={
name:"wjy",
age:20,
height:1.88
}
// * 应用场景
function foo(info){
console.log(info.name,info.age);
}
foo(obj);
// * 函数的参数解构
function bar({name,age}){
console.log(name,age);
}
bar(obj)
5.let/const
在ES5中我们只能通过var关键字来声明变量,从ES6中新增了两个关键字可以声明变量:let 、const
- let、const在其他编程语言中都是有的,所以并不是新鲜的关键字
- 但是let、const确确实实给javascript带来了不一样的东西
let关键字
- 从直观的角度来说,let和var并没有太大的区别的,都是用于声明一个变量
const关键字
- const关键字是constant单词的缩写:表示常量、衡量的意思
- 它表示保存的数据一旦被赋值,就不能被修改。
- 但是如果赋值的是一个引用类型(内存地址),则可以通过引用找到对象,修改对象内部的值。
5.1 const和let的基本使用
-
注意一:const本质上是值不能修改
- 如果const定义的变量的值是引用类型(内存地址),则可以通过引用找到对象,修改对象内部的属性
-
注意二:let和const不能重复定义变量,但var可以
-
注意三:const在定义变量的同时,必须赋值。
var foo="foo";
let bar="bar";
// * const是声明的一个常量
// variable declaration:变量声明
const name="abc";
// name="ddd";//报错
// * 注意事项一:
// * const本质上是传递的值不可以修改,
// * 但是如果传递的是引用类型(内存地址):可以通过引用找到对应的对象,修改对象内部的属性
const obj={
foo:"foo"
};
obj.foo="wjy"
// obj={};//报错
// * 注意事项二:var可以重复定义变量,let和const不可以重复定义变量
var foo="abc";
var foo="dd";
console.log(foo);//dd
let a="a";
// let a="b";// Identifier 'a' has already been declared
// const c;//报错
5.2 let/const的作用域提升
let、const和var的另一个重要区别是作用域提升:
- 我们知道var声明的变量是会进行作用域提升的。
- 但是如果我们使用let声明的变量,在声明访问之前会报错
那么是不是意味着foo变量只有在代码执行阶段才会被创建呢?
- 事实上并不是这样的,我们可以看一下ECMA262对let和const的描述
- 这些变量会被创建在包含他们的词法环境被实例化时,但是是不可以访问它们的,直到词法绑定被求值时
5.3 let/const 有没有作用域提升呢?
从上面我们可以看出,在执行上下文的词法环境创建出来的时候,变量事实上已经被创建了,只是这个变量是不能被访问的。
- 那么变量已经有了,但是不能被访问,是不是一种作用域的提升呢?
事实上维基百科并没有对作用域提升有严格的概念解释,那么我们自己从字面上理解
- 作用域提升:在声明变量的作用域中,如果这个变量可以在声明之前被访问,那么我们可以称之为作用域提升
- 在这里,它虽然被创建出来了,但是不能被访问,我认为是不能称之为作用域提升。
所以我的观点是let、const没有作用域提升,但是会在执行上下文创建阶段被创建出来
5.4 window对象添加属性
我们知道,在全局通过var声明一个变量,事实上会在window添加一个属性
但是let、const是不会给window添加任何属性的。
那么这个变量是保存在哪里呢?
我们看一下ECMA标准对执行上下文的描述
- 早期:每个执行上下文会被关联到一个变量对象中(Variable Object),源代码中的变量和函数声明会作为属性添加到VO对象中。对函数而言,参数也会加入到VO中
- 后期:每个执行上下文会被关联到一个变量环境中(Variable Environment),执行代码中的变量和函数声明会作为环境记录,添加到变量环境。对函数而言,参数也会添加到VE中
5.5 变量被保存到VariableMap中
也就是说 我们声明的变量和环境记录是被添加到变量环境中的:
但是标准有没有规定这个对象是window对象还是其他对象?
其实并没有,每个JS引擎在解析的时候,其实会有自己的实现。
比如V8中是通过VariableMap的一个hashmap来实现它们的存储的。
那么window对象呢?而window对象是早期的GO对象,在最新的实现中其实是浏览器添加的全局对象,并且一直保持了window和var之间值的相等性。
V8中:
VE指向的是一个变量名为variable_:类型是VariableMap
5.6 块级作用域
5.6.1 块级作用域对var不生效
在ES5中,单独写一个{}代表是一个代码块.
ES5中只有两个可以形成作用域:
- 函数作用域
- 全局作用域
{
var a="aaa"
}
console.log(a);//还是可以访问到的,因为块级作用域对var无效
5.6.2 ES6的块级作用域
在ES6中新增了一个块级作用域,它对var无效,但对let、const、function、class声明的标识符是具有块级作用域的限制的。
{
let a='aaa';
function demo(){
}
class Person{
}
}
console.log(a);//报错
console.log(demo);//不报错
console.log(Person);//报错
但上面的代码使用function定义的函数为什么不会报错呢?
因为在不同的浏览器有不同的实现,有些浏览器为了兼容,让函数没有块级作用域。
如果某个浏览器仅支持ES6的语法,那么上面的function定义的函数一定会报错的。
5.6.2.1 if-switch-for有块级作用域
// * 代码块:也会形成块级作用域
{
}
// * if也会形成块级作用域
// if(true){
// let foo="foo";
// }
// console.log(foo);//* ReferenceError: bar is not defined
// * switch:块级作用域
// var color="red";
// switch(color){
// case "red":var foo="foo";let bar="bar"
// }
// console.log(foo);//foo
// console.log(bar);//* ReferenceError: bar is not defined
// * for:块级作用域
// * 使用var 声明的变量:在外层是可以访问到的
// for(var i=0;i<10;i++){
// // console.log(i);
// }
// console.log(i);// 10
// * 使用let声明的变量:在外层是访问不到的
for(let i=0;i<10;i++){
console.log(i);
}
console.log(i);//* ReferenceError: i is not defined
5.6.3 应用场景
例如:给10个按钮,添加点击事件,点击第几个按钮,就输出 第几个按钮被点击了。
const btns=document.getElementsByTagName("button");
for(var i=0;i<btns.length;i++){
btns[i].onclick=function(){
console.log("第"+i+"个按钮被点击了。");
}
}
上面这段代码有问题:就是无论点击第几个按钮,最后都输出 的是 :第10个按钮被点击了。
这是为什么呢?因为对应的按钮被点击的时候会调用对应的处理函数,要取i的值的时候,去上层作用域(块级作用域)找,发现没有,又去全局作用域中,发现i的值是10,因为在进行遍历for循环的时候,,终止的时候i的值就已经是10了。
5.6.3.1 解决方案一:立即执行函数
立即执行函数形成自己的函数作用域,在遍历的时候,将每个i传进行,最后会打印出不同的值,其实这个本质上是形成了闭包。
const btns=document.getElementsByTagName("button");
// * 解决方案一:立即执行函数会形成自己的作用域
for(var i=0;i<btns.length;i++){
(function(n){
btns[i].onclick=function(){
console.log("第"+i+"按钮被点击了。");
}
})(i)
}
5.6.3.2 解决方案二:let
let定义的变量在自己的块级作用域有限制、
const btns=document.getElementsByTagName("button");
// * 解决方案二:使用let:其实在变量每次遍历时,会产生一个新的块级作用域,所以在不同块级作用域中,i的是不一样的,所以在函数内部能打印不同i的值
for(let i=0;i<btns.length;i++){
btns[i].onclick=function(){
console.log("第"+(i+1)+"个按钮被点击了。");
}
}