JS 内置类继承和类的混入、字面量增强、解构、let-const

2022-05-03  本文已影响0人  咸鱼不咸_123

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中新增了两个关键字可以声明变量:letconst

let关键字

const关键字

5.1 const和let的基本使用

 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的另一个重要区别是作用域提升:

那么是不是意味着foo变量只有在代码执行阶段才会被创建呢?

5.3 let/const 有没有作用域提升呢?

从上面我们可以看出,在执行上下文的词法环境创建出来的时候,变量事实上已经被创建了,只是这个变量是不能被访问的。

事实上维基百科并没有对作用域提升有严格的概念解释,那么我们自己从字面上理解

所以我的观点是let、const没有作用域提升,但是会在执行上下文创建阶段被创建出来

5.4 window对象添加属性

我们知道,在全局通过var声明一个变量,事实上会在window添加一个属性

但是let、const是不会给window添加任何属性的。

那么这个变量是保存在哪里呢?

我们看一下ECMA标准对执行上下文的描述

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)+"个按钮被点击了。");
  }
}

6.总结

上一篇下一篇

猜你喜欢

热点阅读