原型-原型链-ES5原型链继承---ES6新语法Class实现继承

392 阅读6分钟

原型 prototype -重点

原型上存放函数

  1. 解决了同一个 say 浪费 内存的问题
  2. 解决了污染全局变量的问题
 function createStudent(name, age) {
      this.name = name;
      this.age = age;
    }
    // 将刚才的全局函数say 直接挂载到 构造函数的原型上 即可
    // prototype 是个对象 每一个构造函数都会内置有的. 我们称之为原型
    createStudent.prototype.say = function () {
      console.log(this.name);
    }

    const obj = new createStudent("悟能", 83);
    const obj1 = new createStudent("悟能1", 84);

    console.log(obj.say === obj1.say); // true 
    

原型 1.png

原型解释

  • 原型的单词是 prototype, 原型的这个名字是行业内共同认可的名字。
  • 原型本质是一个对象,理解为 JavaScript 自动帮我们添加的,只要是构造函数,系统会默认的为构造函数关联一个对象,这个对象就称为构造函数的原型,写在原型中的成员,可以被构造函数所创建的实例调用
  • 原型是 JavaScript 自动帮我们在定义构造函数的时候添加的
  • 所有构造函数的实例,共享一个原型
  • 原型上一般是挂载函数

image-20200713153918181.png

原型 proto

  1. Javascript 规定,每一个(构造)函数都有一个 prototype 属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。这也就意味着,我们可以把所有对象实例需要共享的属性和方法直接定义在 prototype 对象上,以便让同一类型的对象共享方法或其它成员
  2. 实例的 **proto**属性 等于 构造函数的prototype
function Person (name, age) {
  this.name = name
  this.age = age
}

console.log(Person.prototype)

Person.prototype.type = 'human'

Person.prototype.sayName = function () {
  console.log(this.name)
}

var p1 = new Person(...)
var p2 = new Person(...)

console.log(p1.sayName === p2.sayName) // => true
  • 这时所有实例的 type 属性和 sayName() 方法,其实都是同一个内存地址
  1. 注意:由于不同浏览器的兼容性问题,我们使用的时候,都只会使用 构造函数的prototype
  2. 实例的 *proto* 只是为了方便我们开发的时候查看数据,是不会手动修改和操作它的。

原型的关系

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

构造函数:构造函数就是一个函数,配合new可以新建对象。

实例:通过构造函数实例化出来的对象我们把它叫做构造函数的实例。一个构造函数可以有很多实例。

原型:每一个构造函数都有一个属性prototype,函数的prototype属性值就是原型。通过构造函数创建出来的实例能够直接使用原型上的属性和方法。

image-20200714203213144.png

所有的构造函数都是Function的实例

Array 和 Person 和 Date 等都是 Function的实例

image-20200714203717916.png

Function 和 Object的关系

有人说 JavaScript 是作者花了7天时间写出来的产物 - 不完美

console.log(Object.prototype===Function.prototype.__proto__)

image-20200714204744760.png

接近顶峰了

 console.log(Object.prototype.__proto__ === null);

image-20200714205012513.png

原型链

概念

任何一个对象,都有原型对象,原型对象本身又是一个对象,所以原型对象也有自己的原型对象,这样一环扣一环就形成了一个链式结构,我们把这个链式结构称为:原型链。

完整原型图.jpg.jpeg

proto.png

  • 总结:Object.prototype是原型链的尽头,Object.prototype的原型是null。

属性查找原则

如果是获取操作

  1. 会先在自身上查找,如果没有
  2. 则根据__proto__对应的原型去找,如果没有
  3. 一直找到Object.prototype,如果没有,那就找不到从而报错 原型链.png

es5 原型链继承

利用代码的能力实现 面向对象的特性 封装继承

初体验

  1. 子类strudent 继承了父类 Person的属性
// 父类
function Person(name, height) {
    this.name = name;
    this.height = height;
}

Person.prototype.say = function () {
    console.log(this.name);
    console.log(this.height);
}

// 子类
function Student(grade, name, height) {
    // 借用了父类的构造函数,完成对自己的赋值
    Person.call(this, name, height)
    this.grade = grade;
}

// 赋值了父类原型上的所有的 属性和方法
Student.prototype = Person.prototype;
// 修改之类的指向
Student.prototype.constructor = Student;

// 创建子类的实例
const stu = new Student("一年", "周星星", 170);
stu.say();
Es5-构造函数方式的父子类创建和继承

Es5-构造函数方式的父子类创建和继承.png

案例:需求

  1. 有一个负责创建元素的构造函数 A
  2. 有一个负责创建图片的构造函数 B
  3. 构造函数 B 可以使用 构造函数 A 的原型上的所有的功能 实现继承

效果

image-20200716170051320.png

代码

 // 1 负责创建元素  
    function Element(nodeName, text) {
      const node = document.createElement(nodeName);
      node.classList.add("element")
      node.innerText = text;
      this.node = node;
    }

    // 2 原型上挂载 共用的方法
    Element.prototype.appendTo = function (selector) {
      const parent = document.querySelector(selector);
      parent.appendChild(this.node);
    }

    // 3 创建一个实例
    const div = new Element("div", "做人开心就好");
    // 4 追加到父元素上
    div.appendTo("section");

    // 5 一个新的构造函数 用来创建图片
    function ElementImg(src) {
      // 6 借用了 1 中的构造函数,并且把参数传递了进去
      Element.call(this, "img", "");
      // 7 图片设置路径
      this.node.src = src;
    }

    // 8 继承了 父亲的构造函数上的原型上的所有 函数
    ElementImg.prototype=Element.prototype;			// 修改原型,也就修改了构造函数
    // 9 重新将 constructor 的指向改回来
    ElementImg.prototype.constructor=ElementImg;

    // 10 创建一个图片实例:注意,实例化代码10不能在8和9之前
    const img = new ElementImg("images/01.png");
    
    // 11 创建到父元素上
    img.appendTo("section");

instanceof 和 constructor

判断一个实例是否属于某个构造函数

// 构造函数
function Person() {


}
const p1 = new Person();

console.log(p1 instanceof Person);
console.log(p1.__proto__.constructor === Person);

es6 class

JavaScript 语言中,生成实例对象的传统方法是通过构造函数,es6的class 的出现 基本上可以替代了es5的构造函数和原型,使之代码结构上更加简洁。

  1. 1.构造函数的写法跟传统的面向对象语言(比如 C++ 和 Java)差异很大,很容易让新学习js的程序员感到困惑。
  2. 2.ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。
  3. 3.新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

关键字

  1. class
  2. 属性
  3. 方法
  4. 继承 extends
  5. 构造函数 constructor
  6. 方法重写 override:子类方法覆盖父类,super.父类方法()
  7. 父类的构造函数 super :子类有构造方法且使用this前,必须使用super()

ES5 创建对象

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHi = function () {
  console.log("hello, 我是" + this.name);
};

var p = new Person("lw", 36);
p.sayHi();

ES6 创建对象

class Person {
  constructor(name, age) {
    this.name = name;
  	this.age = age;
  }

  sayHi() {
 	console.log("hello, 我是" + this.name);
  };
}

var p = new Person("lw", 36);
p.sayHi();

ES6-class实现继承

Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

# 继承Person类中的sayHi方法
class Person {
    sayHi(){
        console.log("hello");
    }
}

class Chinese extends Person {}


# 继承Person类中的属性和方法
class Person {
    constructor(name, age, gender){
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    sayHi(){
        console.log("hello");
    }
}

// 子类
class Chinese extends Person {
    constructor(name, age, gender, skin){
        // 在子类中,必须在constructor函数中,首先调用super()
        super(name, age, gender);
        // 调用super之后才可以去写其他代码
        this.skin = skin;
    }
}
var xm = new Chinese("xm", 20, "male", "黄");
console.log(xm);
xm.sayHi();
ES5---ES6

ES5语法&ES6新语法.png