一、ES5中实现继承
1. 对象和函数的原型
- 对象的原型
- 每一个对象中都有一个特殊的内置属性[[prototype]],这个特殊的属性可以指向另一个对象
- 获取的方式
- 通过对象的__proto__属性可以获取到(存在兼容性问题)
- 通过Object.getPrototypeOf方法可以获取到
- 原型的作用
- 当我们通过[[get]]方式获取一个属性对应的value时
- 它会优先在自己的对象中查找,如果找到直接返回
- 如果没有找到,那么会在原型对象中查找
- 函数的原型prototype
- 所有的函数都有一个prototype的属性
- 将函数堪称一个普通的对象时,它具有__proto__(隐式原型)/作用:查找key对应的value时,会找到原型身上
- 将函数堪称一个函数时,它是具备prototype(显式原型,对象没有的)/作用:用来构造对象时,给对象设置隐式原型的
2. new、constructor
- new操作符
- 创建一个空对象 --- 将构造函数的prototype赋值给空对象的__proto__
- 显式原型中的属性
- 构造函数中有一个prototype属性指向该构造函数的原型对象
- 原型对象中有一个constructor属性指向该构造函数
- 创建对象的内存表现
function Person(name, age) { this.name = name this.age = age } Person.prototype.running = function() { console.log("running~") } var p1 = new Person("why", 18) var p2 = new Person("kobe", 30) console.log(p1.name) console.log(p2.name) p1.running() p2.running() - 新增属性
// 新增属性 Person.prototype.address = "中国" p1.___proto__.info = "中国很美丽" p1.height = 1.88 p1.isAdmin = true // 获取属性 console.log(p1.isAdmin) console.log(p2.isAdmin) console.log(p2.info) // 修改address p1.adderss = "广州市“
- constructor属性
- 默认情况下原型上都会添加一个属性叫做constructor,这个constructor指向当前的函数对象
- 重写原型对象
3. 原型链的查找顺序
- 原型链
- 从一个对象中获取属性,如果在当前对象中没有获取到就会去它的原型上面获取
- Object的原型
- 原型链最顶层的原型对象就是Object的原型对象
- Object直接创建出来的的原型对象是[Object: null prototype]{}
- 该对象有原型属性,但是它的原型属性指向的是null,已经是顶层原型了
- 该对象上有很多默认的属性和方法
4. 原型链实现的继承
- 通过原型链实现继承
// 1. 定义父类构造函数 function Person(name, age) { this.name = name this.age = age } // 2. 父类原型上添加内容 Person.prototype.running = function() { console.log("running~") } Person.prototype.eating = function() { console.log("eating~") } // 3. 定义子类构造函数 function Student(name, age, sno, score) { this.name = name this.age = age this.score = score } // 4. 创建父类对象,并且作为子类的原型对象 var p = new Person("why", 18) Student.prototype = p // 5. 在子类原型上添加内容 Student.prototype.studying = function() { console.log(this.name, "studying") } var stu1 = new Student("why", 18, 111, 99) stu1.running() stu1.studying() - 原型链继承的弊端
- 某些属性是保存在p对象上的
- 我们直接打印对象看不到这个属性
- 这个属性会被多个对象共享,如果这个对象是一个引用类型,那么就会造成问题
- 不能给Person传递参数,因为这个对象是一次性创建的
5. 借用构造函数继承
- 借用构造函数继承
- 在子类型构造函数的内部调用父类型构造函数
- 通过apply()/call()方法在新创建的对象上执行构造函数
function Student(name, friends, sno) { Person.call(this, name, friends) this.sno =sno }
- 组合借用继承的问题
- 无论在什么情况下,都会调用两次父类构造函数
- 所有的子类实例事实上会拥有两份父类的属性
6. 原型式继承函数
- 方法一
function object(obj) { function Func() {} Func.prototype = obj return new Func() } - 方法二
function object(obj) { var newObj = {} Object.setPrototypeOf(newObj, obj) return newObj } - 方法三
var student = Object.create(person, { address: { value: "北京市", enumerable: true } })
7. 寄生组合实现继承
- 寄生组合式继承
- 创建一个封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再将这个对象返回
// 工具函数 function createObject(o) { function F() {} F.prototype = o return new F() } // 寄生式函数 // Subtype --- 子类 // Supertype --- 父类 function inherit(Subtype, Supertype) { Subtype.prototype = createObject(Supertype.prototype) Object.defineProperty(Subtype.prototype, "constructor", { enumerable: false, configurable: true, writable: true, value: Subtype }) }
二、JavaScript ES6中实现继承
1. 对象的方法补充
- hasOwnProperty
- 对象是否有一个属于自己的属性
- in/for in操作符
- 判断某个属性是否在某个对象或者对象的原型上
- instanceof
- 用于检测构造函数的prototype,是否出现在某个实例对象的原型链上
- isPrototypeOf
- 用于检测某个对象,是否出现在某个实例对象的原型链上
2. class方式定义类
- 类与构造函数的异同
- 类与构造函数的特性是一致的
- 类的构造函数
-
每个类都有一个自己的构造函数,这个方法的名称是固定的constructor
-
每个类只能有一个构造函数,如果包含多个构造函数,那么会抛出异常
-
当我们通过new关键字操作类时,会调用constructor,并且执行如下操作
- 在内存中创建一个空对象
- 这个对象内部的[[prototype]]属性会被赋值为该类的prototype属性
- 构造函数内部的this,会指向创建出来的新对象
- 执行构造函数的内部代码
- 如果构造函数没有返回非空对象,则返回创建出来的新对象
-
- 类的实例方法
- 实例方法直接在类中定义
- 类的访问器方法
- 类的静态方法
- 用static关键字来定义
- 直接使用类来执行
3. extends实现继承
- extends关键字实现继承
- super关键字
4. Babel的ES6转ES5
5. 面向对象多态理解
- javascript的多态
- 不同的数据类型进行同一个操作,表现出不同的行为
6. ES6对象的增强
- 属性的简写
- 方法的简写
- 对象属性名
- 解构
- 数组的解构
- 对象的解构