JavaScript高级

JavaScript高级 & ES6

一、ES6中的类和对象

1.1 JavaScript中的对象

在JavaScript语言中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,例如字符串、数值、数组、函数等。

​ 对象是由属性方法组成的:

  • 属性:事物的一些特性
  • 方法:事物的行为或功能

1.2 类 class

​ ES6新增了类的概念,可以使用class关键字声明一个类,之后以这个类为模板来实例化对象。

1.3 创建类

​ 语法:

class Person{
    constructor(name,age){  //构造函数
        this.name = name;
        this.age = age;
    }
    
    //在类中所有的函数都可以不用写function
    say(){         //类的共有方法
        console.log('人可以说话')
    }
}

​ 创建实例:

//之前定义变量使用var,但是var中存在一些问题,例如作用域提升
//因此现在更推荐使用let声明一个变量
let xx = new Person('张三',12)   //使用new关键字创建对象

1.4 类的构造函数 constructor

constructor()方法是类的构造函数,用于传递参数并返回一个实例化对象。该方法不需要显式调用,在new对象的时候会自动执行。如果没有显式定义构造函数,系统就会自动给我们创建一个constructor()。

1.5 类的继承 extends

​ 继承,就是将父类中的公有的方法和属性继承过来达到复用的目的,这个也符合现实世界的特征。例如,猫和狗都是动物,而且他们都是哺乳动物,都能够吃饭、睡觉。

​ 示例:

class Father{    //父类
    constructor(){...}
    money(){...}
}
class Son extends Father{    //子类
    //这样就可以将父类中的方法和属性继承过来 
    //虽然现在子类中并没有出现money()方法,但是一旦子类继承父类,就会自动拥有父类的非私有方法和属性
}

1.6 super 关键字

super关键字用于访问父类中的函数,不仅可以调用父类构造函数,也可以调用父类中的普通函数。

​ 示例:

class Father{    //父类
    constructor(name,age){...}
    money(){...}
}
class Son extends Father{    //子类
    constructor(name,age,sno){
        //这里需要注意,super必须在this之前,否则会报错
        super(name,age);
        this.sno = sno;
    	...
    }
    ...
}

注意:在继承中采取"就近原则",如果一个属性或者方法在父类中找不到,那么就会去找父类的父类,如果还是找不到那就报错,否则返回该方法结果。

1.7 三个注意点

  1. 在ES6中类没有变量提升,所以必须 先定义类,然后才能实例化对象。
  2. 类里面的共有方法和属性要加this使用。
  3. **类里面this的指向问题。**constructor里面的this指向实例对象,方法里面的this指向这个方法的调用者。

二、构造函数和原型

2.1 概述

​ 在典型的面向对象的语言中,都存在类的概念,但是在ES6之前,JS中并没有引入类的概念。

在ES6之前,对象不是基于类创建的,而是用一种称为构造函数的特殊函数来定义对象和他们的特征。

在ES6之前,创建对象的三种方式:

  1. 对象字面量

    let obj1 = {}
    
  2. new Object()

    let obj2 = new Object();
    
  3. 自定义构造函数

    function Person(name,age){
        this.name = name;
        this.age = age;
        
        this.sing = function(){
            console.log('我会唱歌!')}
    }
    
    let obj3 = new Person('刘德华',18);
    

2.2 构造函数

构造函数是一种特殊的函数,主要用于对象的初始化,总是和new一起使用。我们可以将一些公共的属性和方法抽取出去出来,然后封装到这个函数中去。

​ 在JS中,构造函数要注意以下几点:

  • 构造函数用于创建某一类的对象,首字母要大写;
  • 构造函数要与new一起使用来由意义。

2.3 new的执行过程(重要

  1. 在内存中创建一个新的空对象;
  2. 让this指向这个新的对象;
  3. 执行构造函数里面的代码,给这个新对象添加属性和方法;
  4. 返回这个新对象(所以构造函数里面不需要return)

2.4 实例成员和静态成员

​ **实例成员:**实例成员就是构造函数内部通过this添加的成员,实例成员只能通过实例对象进行访问。

function Person(name,age){
    this.name = name;
    this.age = age;
    
    this.sing = function(){
        console.log('我会唱歌!')}
    
    //上面的this.name,this.age,this.sing都是实例成员
}

​ **静态成员:**在构造函数自身上添加的成员,当然也只能通过构造函数来访问。例如:

Person.sex = '男';

//Person是一个构造函数,sex是添加到它上面的一个方法
//因此每一个Person的实例对象创建后都会有一个属性sex='男'

2.5 构造函数原型对象prototype(重要

​ 构造函数方法很好用,但是存在浪费内存的问题。那么具体为什么会浪费内存呢?

​ 首先,构造函数会占用内存中的一块区域,每创建一个实例对象就会开辟一个构造函数的内存空间,这样的话,有多少实例对象就会额外开辟多少构造函数的内存空间,而这些开辟的空间实际上都是一样的,因此保留一份就可以,其他的都是冗余。**如果打印 ldh.sing === zxy.sing ,你会发现结果是false,就是因为他们两个实例对象所指向的构造函数不是同一块内存。**下面用一个图来更好地解释:

​ 上面这个问题的解决办法就是prototype,所有的实例对象都共用一个原型对象。构造函数通过原型分配的函数是所有对象所共享的

​ JavaScript规定,每一个构造函数都有一个prototype属性,指向另一个对象。注意这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。我们可以把那些不变的方法,直接定义在原型对象上面,这样所有对象的实例就可以共享这些方法。

​ 在原型对象身上添加公共方法:

Star.prototype.sing = function(){
    console.log('我会唱歌!');
}

关于原型的两个问题:

  1. 原型是什么?

    一个对象,我们也称prototype为原型对象。

  2. 原型的作用是什么?

    共享方法。

一般情况下,我们的公共属性定义到构造函数里面,但是公共方法我们放在原型对象身上。

2.6 对象原型 __ proto__ (重要)

对象都会有一个属性__ proto__ 指向构造函数的prototype原型对象,之所以我们对象可以使用构造函数prototype原型对象的属性和方法,就是因为 __ proto__原型的存在。

​ 如果实例对象的身上没有某一个方法,那么它就会去__ proto __ 所指的prototype上面去找。

### 2.7 __ proto__ 和prototype中的constructor属性

对象原型(__ proto__)和构造函数原型对象(prototype)里面都有一个constructor属性,constructor我们称为构造函数,因为它指回构造函数本身。

​ 上面红色框中的输出结果为:两个指向的constructor实际上是一样的

constructor主要用于记录该对象引用哪个构造函数,它可以让原型对象重新指向原来的构造函数。

function Star(uname,age){
    this.uname = uname;
    this.age = age;
}

//很多情况下,我们需要手动利用constructor这个属性指回原来的构造函数
Star.prototype = {
    sing:function(){
        console.log('我会唱歌');
    },
    movie:function(){
        console.log('我会演电影');
    },
    ...
}

​ 如果按照上面将公共方法都写在原型对象中,这样就会产生下面的问题:constructor都指向了Object

​ 出现上面问题的原因:自己定义的prototype对象覆盖掉了系统中默认自带的prototype(系统生成的prototype中会自带constructor属性),因此在输出的时候会一直找到Object中的constructor。

​ 解决:重新让constructor指回原来的构造函数

Star.prototype = {
    
    constructor: Star,    //指回原构造函数
    
    sing:function(){
        console.log('我会唱歌');
    },
    movie:function(){
        console.log('我会演电影');
    },
    ...
}

2.8 构造函数、实例、原型对象三者之间的关系

2.9 原型链

​ 原型链的图如下:

​ **具体的过程:**首先,Star是一个构造函数,它会有一个prototype属性指向自己的原型对象,同时原型对象中会有constructor属性指向自己的构造函数Star。图中的ldh是Star的一个实例对象,它的__ proto__ 属性会指向Star的原型对象,(其实ldh.constructor也可以指向Star构造函数,只是因为Star原型对象中有constructor已经指向了Star,所以就没必要了),Star.prototype也是一个对象,也有一个属性__ proto__ 指向它的上一级Object的原型对象Object.prototype,紧接着Object.prototype.__ proto__ 又指向它的上一级null(因为Object是继承的顶端,所以它的上一级原型对象为null)。

实例对象.__ proto__ 指向该类(构造函数)的原型对象,该类(构造函数)原型对象的__ proto__ 又指向上一级的原型对象,然后一级一级这样向上指向,这样的一条链状的指向结构就是原型链。

2.10 JavaScript的成员查找机制

  1. 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有这个属性。
  2. 如果没有就查找他的原型(也就是__ proto__ 指向的prototype原型对象)。
  3. 如果还没有就查找原型对象的原型(Object的原型对象)。
  4. 以此类推一直找到Object为止(null)。
  5. __ proto__ 对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条线路。

2.11 原型对象this的指向

  • 在构造函数中,里面的this指向的是实例对象
  • 原型对象函数里面的this指向的也是实例对象
  • 只有在调用的时候才能确定this指向的是谁。

2.12 扩展内置对象(例如Array等)

​ 我们可以通过原型对象对原来的内置对象进行扩展自定义方法。比如给数组加自定义求偶数和的功能。

//以下代码就是给JS内置对象Array添加求和方法的代码
Array.prototype.sum = function(){
    let sum = 0;
    for(let i = 0; i < this.length; i++){
        sum += this[i];
    }
    return sum;
}

//第一种调用方式
let arr= [1,2,3];
console.log(arr.sum())    //结果为6

//既然已经对Array的原型进行了扩展,那么也可以用它的构造函数new对象
let arr2 = new Array(11,22,33);
console.log(arr2.sum());    //结果为66

​ 对于JavaScript中内置的对象,不能采用Array.prototype = {}这种方式来覆盖掉原来的原型,会报错。下面的写法是错误的:

Array.prototype = {
    sum: function(){
        let sum = 0;
        for(let i = 0; i < this.length; i++){
            sum += this[i];
        }
        return sum;
    }
}

三、继承

​ 在ES6之前,JavaScript中并没有继承的概念。都是通过构造函数+原型对象模拟实现继承,被称为组合继承

3.1 call()

​ call()的作用:(1)调用某个函数;(2)可以修改函数运行时this的指向。

fun.call(thisArg,arg1,arg2,...)

​ thisArg:当前调用函数this的指向对象

​ arg1,arg2:传递的其他参数

function fn(){
    console.log('')
}

}


## 三、继承

​	在ES6之前,JavaScript中并没有继承的概念。都是<font color='red'>通过**构造函数+原型对象**模拟实现继承,被称为**组合继承**。</font>

### 3.1 call()

​	call()的作用:(1)调用某个函数;(2)可以修改函数运行时this的指向。

```javascript
fun.call(thisArg,arg1,arg2,...)

​ thisArg:当前调用函数this的指向对象

​ arg1,arg2:传递的其他参数

function fn(){
    console.log('')
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值