js对象深入、原型与原型链
面向对象的特性
面向对象有三大特性:封装、继承、多态
- 封装:我们前面将属性和方法封装到一个类中,可以称之为封装的过程;
- 继承:继承是面向对象中非常重要的,不仅仅可以减少重复代码的数量,也是多态前提(纯面向对象中);
- 多态:不同的对象在执行时表现出不同的形态;
JavaScript原型链
从一个对象上获取属性,如果在当前对象中没有获取到就会去它的原型上面获取
- 所有的引用类型(数组,对象,函数,除了null以外,null也是引用类型),都有一个__proto__属性:(隐式原型),你可以通过 ES6 的
Object.getPrototypeOf()来访问该属性,其属性值是一个普通对象 代码如下:
function Cat() {
this.color = 'orange'
}
var cat = new Cat()
console.log(cat.__proto__)
console.log(Object.getPrototypeOf(cat) === cat.__proto__) // true
- 所有的函数,都有一个prototype属性:(显式原型),其属性值是一个普通对象
- 所有的引用类型(数组,对象,函数,除了null以外),__proto__属性值指向它的构造函数的 prototype 的属性值
参考代码如下:
function Cat() {
this.color = 'orange'
}
var cat = new Cat()
console.log(cat.__proto__ === Cat.prototype) // true
有关构造函数的 prototype 和实例对象的 __proto__ 的关系,我们可以用张图来体现一下。
什么地方是原型链的尽头呢?
Object.prototype 的原型会是什么?
console.log(Object.prototype.__proto__) // null
它就是 null,null 没有原型,所以 Object.prototype 就是原型链的最顶端。
可以说,JavaScript 中的所有对象都来自 Object,Object 位于原型链的最顶端,几乎所有 JavaScript 的实例对象都是基于 Object。
我们可以将图片更新一下:
基于原型链的继承其实随处可见,只是我们没有意识到。当你随手新建一个数组,是否想过它怎么会有 splice、indexOf 等方法,新建一个函数怎么可以直接使用 call 和 bind?其实数组都继承于 Array.prototype,函数都继承于 Function.prototype,它们分别包含了数组和函数的基本方法,尝试去控制台打印出 Array.prototype 和 Function.prototype,上面的疑问便可得到解答。
继承
原型链实现继承(简单粗暴,直接修改prototype)
代码如下:
//1.定义父类构造函数
function Person() {
this.name = "why"
}
//2.父类原型上添加内容
Person.prototype.running = function () {
console.log(this.name + "running~")
}
//3.定义子类构造函数
function Student() {
this.sno = 111
}
//4.创建父类对象,并且作为子类的原型对象
var p = new Person()
Student.prototype = p
//5.在子类原型上添加内容
Student.prototype.studying = function () {
console.log(this.name + "studying")
}
var stu = new Student()
console.log(stu.name)
原型链继承的弊端
- 我们通过直接打印对象是看不到这个属性的
- 这个属性会被多个对象共享,如果这个对象是一个引用类型,那么就会造成问题;
- 不能给Person传递参数,因为这个对象是一次性创建的(没办法定制化);
借用构造函数继承
可解决上述3个弊端 实现方式: 在子类型构造函数的内部调用父类型构造函数.
代码如下:
//1.定义父类构造函数
function Person(name) {
this.name = name
}
//2.父类原型上添加内容
Person.prototype.running = function () {
console.log(this.name + "running~")
}
//3.定义子类构造函数
function Student(name, sno) {
Person.call(this, name)
this.sno = sno
}
//4.创建父类对象,并且作为子类的原型对象(这里主要继承父类原型上的的方法)
var person = new Person()
Student.prototype = person
//5.在子类原型上添加内容
Student.prototype.studying = function () {
console.log(this.name + "studying")
}
var stu = new Student('john', 'rrrr')
console.log(stu.name,stu.runinig())
弊端
- 会调用两次父类构造函数。
- 一次在创建子类原型的时候
- 一次在子类构造函数内部
- 所有的子类实例事实上会拥有两份父类的属性
- 一份在当前的实例自己里面(也就是person本身的),另一份在子类对应的原型对象中(也就是 person.__proto__里面);
原型式继承函数(针对对象)
实现目的:新对象的原型指向旧的对象
方法一:
var obj = {
name: 'why',
age: 18
}
function createObject(o) {
var newObj = {}
Object.setPrototypeOf(newObj, o)
return newObj
}
var info = createObject(obj)
方法二:
var obj = {
name: 'why',
age: 18
}
function createObject(o) {
function Func(){}
Func.prototype = o
return new Func()
}
var info = createObject(obj)
方法三:
var obj = {
name: 'why',
age: 18
}
var info = Object.create(obj)
寄生式继承函数
寄生式继承的思路是结合原型类继承和工厂模式的一种方式;
var personObj = {
running: function () {
console.log('running')
}
}
function createStudent(name) {
var stu = Object.create(personObj)
stu.name = name
stu.studying = function () {
console.log('studying')
}
return stu
}
var stu1 = createStudent('why')
弊端
每次创建的学生都会有studying函数,内存浪费
寄生组合式继承(最终解决方案)
function Person(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
Person.prototype.running = function () {
console.log(this.name + "running~")
}
Person.prototype.eating = function () {
console.log(this.name + "eating~")
}
function Student(name, age, friends,sno,score) {
Person.call(this, name, age, friends)
this.sno = sno
this.score = score
}
// 修改Student的隐式原型指向
Student.prototype = Object.create(Person.prototype)
// 增加student的构造值
Object.defineProperty(Student.prototype, 'constructor', {
enumerable: false,
configurable: true,
writable: true,
value: Student
})
Student.prototype.studying = function () {
console.log(this.name + "studying")
}
var stu = new Student('john', 18, ['zs'],125,99)
console.log(stu)
优化后代码如下:
/**
* 修改子类构造函数显示原型的__proto__指向
* @param {子类构造函数} SubType
* @param {父类构造函数} SupType
*/
function inheritPrototype(SubType, SupType) {
// 修改SubType的隐式原型指向
SubType.prototype = Object.create(SupType.prototype)
// 增加SubType的构造值
Object.defineProperty(SubType.prototype, 'constructor', {
enumerable: false,
configurable: true,
writable: true,
value: SubType
})
}
function Person(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
Person.prototype.running = function () {
console.log(this.name + "running~")
}
Person.prototype.eating = function () {
console.log(this.name + "eating~")
}
function Student(name, age, friends,sno,score) {
Person.call(this, name, age, friends)
this.sno = sno
this.score = score
}
inheritPrototype(Student,Person)
Student.prototype.studying = function () {
console.log(this.name + "studying")
}
var stu = new Student('john', 18, ['zs'],125,99)
console.log(stu)
对象方法
hasOwnProperty
- 对象是否有某一个属于自己的属性(不是在原型上的属性) 代码如下:
var obj = { name: "why", age: 18 }
var info = Object.create(obj, {
address: {
value: "北京市",
enumerable: true
}
})
console.log(info.hasOwnProperty("address")) // true
console.log(info.hasOwnProperty("name")) // false
in/for in 操作符
- 判断某个属性是否在某个对象或者对象的原型上
var obj = { name: "why", age: 18 }
var info = Object.create(obj, {
address: {
value: "北京市",
enumerable: true
}
})
console.log('address' in info) // true
console.log('name' in info) // true
for (var key in info) {
console.log(key)
}
instanceof
- 用于检测构造函数的pototype,是否出现在某个实例对象的原型链上(
对象是否在函数)
/**
* 修改子类构造函数显示原型的__proto__指向
* @param {子类构造函数} SubType
* @param {父类构造函数} SupType
*/
function inheritPrototype(SubType, SupType) {
// 修改SubType的隐式原型指向
SubType.prototype = Object.create(SupType.prototype)
// 增加SubType的构造值
Object.defineProperty(SubType.prototype, 'constructor', {
enumerable: false,
configurable: true,
writable: true,
value: SubType
})
}
function Person(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
Person.prototype.running = function () {
console.log(this.name + "running~")
}
Person.prototype.eating = function () {
console.log(this.name + "eating~")
}
function Student(name, age, friends,sno,score) {
Person.call(this, name, age, friends)
this.sno = sno
this.score = score
}
inheritPrototype(Student,Person)
Student.prototype.studying = function () {
console.log(this.name + "studying")
}
var stu = new Student('john', 18, ['zs'], 125, 99)
console.log(stu instanceof Student) // true
console.log(stu instanceof Person) // true
console.log(stu instanceof Object) // true
isPrototypeOf
- 用于检测某个对象,是否出现在某个实例对象的原型链上(
对象是否在对象)
function Person() { }
var p = new Person()
console.log(p instanceof Person) // true
console.log(Person.prototype.isPrototypeOf(p)) // true
原型继承关系
究极蛇皮怪。。。
Function.prototype === Function.__proto__ // true
Object.__proto__ === Function.prototype // true