文章目录

  • 一、概述
  • 1.2 构造函数
  • 二、静态成员 & 实例成员
  • 2.1 实例成员
  • 2.2 静态成员
  • 三、原型
  • 3.1对象原型
  • 3.2 原型的 constructor构造函数
  • 3.3 原型链
  • 3.4 原型对象的应用
  • 3.5 总结
  • 四、继承
  • 2.1 call方法
  • 2.2 利用父构造函数实现继承
  • 2.3 class 类的本质


一、概述

在典型的OOP的语言中(如Java),都存在类的概念,类就是对象的模板,对象就是类的实例,但在ES6之前,JS中并没用引入类的概念。

ES6,全称 ECMAScript6.0,2015.06发版。但是目前浏览器的 JavaScript是ES5版本,大多数高版本的浏览器也支持ES6,不过只实现了ES6的部分特性和功能。

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

这里提一下创建对象的三种方法:

// 1. 利用 new Object()创建对象
let obj1 = new Object();

// 2. 利用对象字面量创建对象
let obj2 = {}

// 3. 利用构建函数创造对象
function Star(uname, age) {
  this.uname = uname
  this.age = age
  this.sing = function () {
    console.log('我会唱歌');
  }
}

let ldh = new Star('刘德华', 18)
let zxy = new Star('张学友', 20)
console.log(ldh); // {uname: "刘德华", age: 18, sing: ƒ}

ldh.sing() //我会唱歌
zxy.sing() //我会唱歌

1.2 构造函数

构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与new一起使用。我
们可以把对象中一些公共的属性和方法抽取出来,然后封裝到这个函数里面

在JS中,使用构造函数时要注意以下两点:

  1. 构造函数用于创建某一类对象,其首字母要大写
  2. 构造函数要和new一起使用才有意义

new在执行时会做四件事情:

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

二、静态成员 & 实例成员

构造函数中的属性和方法我们成为成员,成员可以添加

function Star(uname, age) {
  this.uname = uname;
  this.age = age;
  this.sing = function () {
    console.log('我会唱歌');
  }
}
console.log(ldh.uname); //刘德华
ldh.sing() //我会唱歌
console.log(Star.uname);//undefined  不能通过构造函数访问

2.1 实例成员

实例成员就是构造函数内部通过this添加的成员, 在上述代码中 uname age sing 就是实例成员
实例成员只能通过实例化的对象来访问

2.2 静态成员

在构造函数本身上添加静态成员

Star.sex = "男" //添加静态成员

静态成员只能通过构造函数访问

console.log(Star.sex); //男
console.log(ldh.sex); // undefine

三、原型

new构造函数的时候,复杂数据类型是重新开辟新的内存空间,会浪费内存和降低运行速度。
请看下面示例:

function Star(uname, age) {
  this.uname = uname;
  this.age = age;
  this.sing = function () {
    console.log('我会唱歌');
  }
}

let ldh = new Star('刘德华', 18)
let zxy = new Star('张学友', 2)
console.log(ldh.sing === zxy.sing); // false

通过 === 符号,我们可以知道 这两个方法的内存地址是不一样的!
那么 在ES6出来之前,是怎么实现继承的同时又解决掉这个问题的呢?

答案是:原型!
构造函数通过原型分配的函数是所有对象所共享的。

Javascript规定,每一个构造函数都有一个 prototype属性,指向另一个对象。
注意这个 prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。

我们可以把那些不变的方法,直接定义在 prototype对象上,这样所有对象的实例就可以共享这些方法

接下来改进一下我们的 Star构造函数:

function Star(uname, age) {
  this.uname = uname;
  this.age = age;
  // this.sing = function () {
  //   console.log('我会唱歌');
  // }
}
Star.prototype.sing = function (){
  console.log('我会唱歌');
}
let ldh = new Star('刘德华', 18)
let zxy = new Star('张学友', 2)
console.log(ldh.sing === zxy.sing); // true

问答?
1.原型是什么?
一个对象,我们也称为 prototype为原型对象

2.原型的作用是什么
共享方法

总结: 一般情況下,我们的公共属性定义到构造函数里面公共的方法我们放到原型对象身上

3.1对象原型

function Star(uname, age) {
  this.uname = uname;
  this.age = age;
}
Star.prototype.sing = function (){
  console.log('我会唱歌');
}

let ldh = new Star('刘德华', 18)

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

console.log(Star.prototype === ldh.__proto__); //true
console.log(Star.prototype.sing === ldh.__proto__.sing);//
console.log(ldh.sing === ldh.__proto__.sing);//true

个人总结就是:实例对象的 __proto__ 指向(等于) 构造函数的 prototype

注意,方法的查找规则:

  • 首先先看ldh对象身上是否有sing方法,如果有就执行这个对象上的sing
  • 如果没有sing这个方法,因为有 proto的存在,就去构造函数原型对象 prototype身上去查找sing这个方

3.2 原型的 constructor构造函数

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

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

重新指回原来的构造函数:

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

 Star.prototype = {
   // 手动指回原来的构造函数
   constructor: Star,
   sing: function () {
     console.log('我会唱歌');
   },
   movie: function () {
     console.log("我会演电影");
   },
 }
 let ldh = new Star('刘德华', 18)
console.log(ldh.constructor === ldh.__proto__.constructor); // true
console.log(ldh.constructor === Star.prototype.constructor); // true

铁三角:

js文件es6转es5 js es6_ES6

3.3 原型链

js文件es6转es5 js es6___proto___02

示例代码:

function Star(uname, age) {
  this.uname = uname;
  this.age = age;
}
// prototype是一个对象,默认是带有constructor的,但是使用赋值操作后把对象覆盖了
Star.prototype = {
  // 手动指回原来的构造函数
  constructor: Star,
  sing: function () {
    console.log('我会唱歌');
  },
  movie: function () {
    console.log("我会演电影");
  },
}
//1.只要是对象就有 proto_原型,指向原型对象
console.log(Star.prototype.__proto__ === Object.prototype); //true
//2.我们Star原型对象里面的_ proto_原型指向的是 object. prototype
// 3. 我们Object.prototype原型对象里边的 __proto__原型 指向 null
console.log(Object.prototype.__proto__); // null

Object.prototype.gogo = function (){
  console.log("gogo");
}
let ldh = new Star("刘德华",18)
console.log(ldh.__proto__.__proto__.gogo === Object.prototype.gogo); // true

object就是所有类的父类,上面没有了

3.4 原型对象的应用

// 原型对象的应用 扩展内置对象方法
Array.prototype.sum = function () {
  let sum = 0
  for (let i = 0; i < this.length; i++) {
    sum += this[i]
  }
  return sum
}
console.log(Array.prototype);
let arr = [1,2,3,4,5,6,7,8,9]
console.log(arr.sum());//45

注意:数组和字符串内置对象不能给原型对象覆盖操作
Array prototype = {…} //错误示例
只能是
Array.prototype.xxx= function(){…}
的方式

3.5 总结

  • 构造函数有原型对象 prototype
  • 构造函数原型对象 prototype里面有 constructor指向构造函数本身
  • 构造函数可以通过原型对象添加方法
  • 构造函数创建的实例对象有 __proto__原型指向构造函数原型对象

四、继承

ES6之前并没有给我们提供 extends继承。我们可以通过构造函数+原型对象模拟实现继承,被称为组合继承

2.1 call方法

调用这个函数并且修改函数运行时的this指向
fun. call(thisArg,arg1,arg2, …)

thisArg:当前调用函数this的指向对象
arg1,arg2:传递的其他参数

示例代码:

function fn(x, y) {
  console.log("我想喝咖啡");
  console.log(this);
  console.log(x + y);
}
let obj = {
  name: "andy"
}

// call方法

// 1.call方法可以调用方法
fn.call() // this => window

// 2.call() 可以修改函数的 this
fn.call(obj, 2, 3) // this => obj  result:5

2.2 利用父构造函数实现继承

这里能实现继承的核心就是,call 方法改变了this 指向。

// 利用父构造函数继承属性
// 1.父构造函数
function Father(uname,age){
  // this指向对象实例
  this.uname  = uname
  this.age = age
}

// 2.子构造函数
function Son(uname,age,score){
  // this 指向子构造函数的对象实例
  Father.call(this,uname,age)
  this.score = score
}

let son = new Son("刘德华",18,99)
console.log(son); //Son {uname: "刘德华", age: 18, score: 99}

但是这么做的话,只能继承属性,没办法继承方法,请看示例:

// 利用父构造函数继承属性
// 1.父构造函数
function Father(uname, age) {
  // this指向对象实例
  this.uname = uname
  this.age = age
}
Father.prototype.money= function (){
  console.log("挣钱了!!");
}
// 2.子构造函数
function Son(uname, age, score) {
  // this 指向子构造函数的对象实例
  Father.call(this, uname, age)
  this.score = score
}

let son = new Son("刘德华", 18, 99)
console.log(son)

在控制台我们可以看到,子构造函数并没有 父构造函数里的 money 方法

js文件es6转es5 js es6_js文件es6转es5_03

那怎么才能实现功能齐全的继承呢,请看下面示例 :
核心是: Son.prototype = new Father()

// 利用父构造函数继承属性
// 1.父构造函数
function Father(uname, age) {
  // this指向对象实例
  this.uname = uname
  this.age = age
}
Father.prototype.money = function (){
  console.log("挣钱了!!");
}
// 2.子构造函数
function Son(uname, age, score) {
  // this 指向子构造函数的对象实例
  Father.call(this, uname, age)
  this.score = score
}

Son.prototype = new Father()

let son = new Son("刘德华", 18, 99)
console.log(son); //Son {uname: "刘德华", age: 18, score: 99}

其原理是:

js文件es6转es5 js es6_contructor_04

我们再来看一下控制台:

js文件es6转es5 js es6_js文件es6转es5_05


这么做基本就是实现了我们的继承,但是还是有一个小问题,因为我们把Son.prototype给覆盖了,那么son.prototype.constructor就指向了父构造函数的 constructor,因此,我们需要把它手动修正过来。

最终格式:

// 利用父构造函数继承属性
// 1.父构造函数
function Father(uname, age) {
  // this指向对象实例
  this.uname = uname
  this.age = age
}
Father.prototype.money = function () {
  console.log("挣钱了!!");
}
// 2.子构造函数
function Son(uname, age, score) {
  // this 指向子构造函数的对象实例
  Father.call(this, uname, age)
  this.score = score
}
// 利用赋值的形式覆盖了 Son的prototype,所以要把constructor手动修改回来,不然指向的是Father的constructor
Son.prototype = new Father()
Son.prototype.constructor = Son

let son = new Son("刘德华", 18, 99)
console.log(son);

2.3 class 类的本质

// 类的本质

// ES6 通过类 实现 面向对编程
class Star {

}
console.log(typeof Star); // function
// 类的本质其实还是一个函数,我们呢也可以简单的认为类就是构造函数的另外一种写法

// 1.类和构造函数都有原型对象
console.log(Star.prototype); // {constructor: ƒ}
// 2.类和构造函数的原型对象里边都有一个constructor,它指向类或构造函数本身
console.log(Star.prototype.constructor === Star); // true
// 3.类和构造函数都可以通过原型添加方法
Star.prototype.sing = function () {
  console.log("我会唱歌");
}
let ldh = new Star()
ldh.sing() //我会唱歌
// 4.类和构造函数创建的实例对象都有 __proto__,它指向类或构造函数的原型对象
console.log(ldh.__proto__ === Star.prototype); // true


// 所以:ES6的类 实际上就是 语法糖
  1. 类和构造函数都有原型对象
  2. 类和构造函数的原型对象里边都有一个constructor,它指向类或构造函数本身
  3. 类和构造函数都可以通过原型添加方法
  4. 类和构造函数创建的实例对象都有 proto,它指向类或构造函数的原型对象

所以:ES6的类 实际上就是 语法糖
ES6的类它的绝大部分功能,ES5都可以做到,新的 class 写法只是让对象的原型写法更加清晰、更像面向对象编程的语法而已。

补充 :语法糖就是一种便捷写法。简单理解,有两种方法可以实现同样的功能但是一种写法更加清晰、方便那么这个方法就是语法糖