面向对象有三大特性:封装、继承、多态
封装:我们前面将属性和方法封装到一个类中,可以称之为封装的过程;
继承:继承是面向对象中非常重要的,不仅仅可以减少重复代码的数量,也是多态前提(纯面向对象中);
多态:不同的对象在执行时表现出不同的形态
JavaScript原型链
我们知道,从一个对象上获取属性,如果在当前对象中没有获取到就会去它的原型上面获取:
那么什么地方是原型链的尽头呢
我们会发现它打印的是 [Object: null prototype] {}
事实上这个原型就是我们最顶层的原型了
从Object直接创建出来的对象的原型都是 [Object: null prototype] {}
一、创建对象 – 构造函数和原型组合
我们在构造函数的方式创建对象时,有一个弊端:会创建出重复的函数,比如running、eating这些函数
那么有没有办法让所有的对象去共享这些函数呢?
可以,将这些函数放到Person.prototype的对象上即可
function Person(name, age, height, address) {
this.name = name
this.age = age
this.height = height
this.address = address
}
Person.prototype.eating = function() {
console.log(this.name + "在吃东西~")
}
Person.prototype.running = function() {
console.log(this.name + "在跑步~")
}
var p1 = new Person("why", 18, 1.88, "北京市")
var p2 = new Person("kobe", 20, 1.98, "洛杉矶市")
p1.eating()
p2.eating()
附:构造函数创建对象
// 规范: 构造函数的首字母一般是大写
function Person(name, age, height, address) {
this.name = name
this.age = age
this.height = height
this.address = address
this.eating = function() {
console.log(this.name + "在吃东西~")
}
this.running = function() {
console.log(this.name + "在跑步")
}
}
var p1 = new Person("张三", 18, 1.88, "广州市")
var p2 = new Person("李四", 20, 1.98, "北京市")
console.log(p1)
console.log(p2)
p1.eating()
p2.eating()
二、原型
对象的原型
JavaScript当中每个对象都有一个特殊的内置属性 [[prototype]],这个特殊的对象可以指向另外一个对象。
那么这个对象有什么用呢?
当我们通过引用对象的属性key来获取一个value时,它会触发 [[Get]]的操作;
这个操作会首先检查该属性是否有对应的属性,如果有的话就使用它;
如果对象中没有改属性,那么会访问对象[[prototype]]内置属性指向的对象上的属性
获取的方式有两种:
方式一:通过对象的 __proto__ 属性可以获取到(但是这个是早期浏览器自己添加的,存在一定的兼容性问 题);
方式二:通过 Object.getPrototypeOf 方法可以获取到;
函数的原型 prototype
函数也是一个对象
函数作为对象来说, 它也是有[[prototype]] 隐式原型
console.log(foo.__proto__)
函数它因为是一个函数, 所以它还会多出来一个显示原型属性: prototype
console.log(foo.prototype)
再看new操作符
我们前面讲过new关键字的步骤如下:
- 在内存中创建一个新的对象(空对象);
- 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性;(后面详细讲);
那么也就意味着我们通过Person构造函数创建出来的所有对象的[[prototype]]属性都指向Person.prototype
此时new的这个函数就称为构造函数,通过new调用一个构造函数生成一个新的对象
所以在new操作的时候,会把函数的显式原型赋值给创建出来对象的隐式原型
function Person() {
}
var p1 = new Person()
var p2 = new Person()
// 都是为true
console.log(p1.__proto__ === Person.prototype)
console.log(p2.__proto__ === Person.prototype)
创建对象的内存表现
赋值为新的对象
// 直接修改整个prototype对象
foo.prototype = {
// constructor: foo,
name: "why",
age: 18,
height: 1.88,
seting: function() {
console.log(this.name + "在吃东西~")
}
}
事实上原型对象上面是有一个属性的:constructor
默认情况下原型上都会添加一个属性叫做constructor,这个constructor指向当前的函数对象;
// 真实开发中我们可以通过Object.defineProperty方式添加constructor
Object.defineProperty(foo.prototype, "constructor", {
enumerable: false,
configurable: true,
writable: true,
value: foo
})
三、原型链
四、 寄生组合式继承
组合继承是比较理想的继承方式, 但是存在两个问题:
问题一: 构造函数会被调用两次: 一次在创建子类型原型对象的时候, 一次在创建子类型实例的时候.
问题二: 父类型中的属性会有两份: 一份在原型对象中, 一份在子类型实例中.
事实上, 我们现在可以利用寄生式继承将这两个问题给解决掉.
你需要先明确一点: 当我们在子类型的构造函数中调用父类型.call(this, 参数)这个函数的时候, 就会将父类型中的属性和方法复制一份到了子类型中. 所以父类型本身里面的内容, 我们不再需要.
这个时候, 我们还需要获取到一份父类型的原型对象中的属性和方法.
封装实现继承方式
1.抽离出createObject
//定义object函数:创建新函数,将新函数的原型指向传入的对象o的原型
function createObject(o) {
function Fn() {}
Fn.prototype = o
return new Fn()
//定义寄生式核心函数:SubType子类型 SuperType父类型
function inheritPrototype(SubType, SuperType) {
//子函数的原型指向 新生成的函数的原型,新生成函数原型指向SuperType.prototype
SubType.prototype = createObject(SuperType.prototype)
//给当前函数对象原型添加constructor
Object.defineProperty(SubType.prototype, "constructor", {
enumerable: false,
configurable: true,
writable: true,
value: SubType
})
}
2.直接用 Objec.create()
function inheritPrototype(SubType, SuperType) {
SubType.prototype = Objec.create(SuperType.prototype)
Object.defineProperty(SubType.prototype, "constructor", {
enumerable: false,
configurable: true,
writable: true,
value: SubType
})
}
使用
子类型原型指向新创建函数的原型,而这个新创建函数的原型指向父类型。进而使new出来的stu对象_proto_不再指向继承函数Student的原型,而是指向Person的prototype,中间用一个新建函数链接,不会产生问题
function Person(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
Person.prototype.running = function() {
console.log("running~")
}
Person.prototype.eating = function() {
console.log("eating~")
}
function Student(name, age, friends, sno, score) {
//属性类型用call来继承
Person.call(this, name, age, friends)
this.sno = sno
this.score = score
}
inheritPrototype(Student, Person)
Student.prototype.studying = function() {
console.log("studying~")
}
var stu = new Student("why", 18, ["kobe"], 111, 100)
console.log(stu)
stu.studying()
stu.running()
stu.eating()
console.log(stu.constructor.name)
五、对象的方法补充
hasOwnProperty
对象是否有某一个属于自己的属性(不是在原型上的属性)
in/for in 操作符
var obj = {
name: "why",
age: 18
}
var info = Object.create(obj, {
address: {
value: "北京市",
enumerable: true
}
})
// hasOwnProperty方法判断
// console.log(info.hasOwnProperty("address")) //true
// console.log(info.hasOwnProperty("name")) //false
// in 操作符: 不管在当前对象还是原型中返回的都是true
console.log("address" in info)
console.log("name" in info)
// for in:打印原型 不管在当前对象还是原型中都打印
for (var key in info) {
console.log(key) //address name age
}
instanceof
isPrototypeOf
var obj = {
name: "why",
age: 18
}
var info = Object.create(obj)
// console.log(info instanceof obj) //报错 因为obj是对象 不是函数
console.log(obj.isPrototypeOf(info)) //true obj对象在info的原型链上
![](https://i-blog.csdnimg.cn/blog_migrate/399a09c9ad0887432e700de685bfdc46.png)