ES5对象特性
对象和函数的原型
JS中每一个对象都有一个特殊的内置属性,这个特殊的对象可以指向其他的对象
- 我们通过引用对象的属性key来获取一个value时,它会触发 Get 的操作
- 首先检查该对象是否有对应的属性,如果有的话就使用对象内的
- 如果对象中没有属性,那么会访问对象的
prototype
- 每一个对象都有一个原型属性
使用方式有两种:
- 通过对象的
_proto_
属性可以获取到(浏览器自己添加的,存在一定的兼容性问题) - 通过 Object.getPrototypeOf 方法可以获取
prototype属性是函数特有的属性 我们的对象只能通过
Object.getPrototypeOf
来查看原型。
var obj = {
}
function foo() {
}
console.log(foo.prototype);
当我们这个对象有对多个共同值的时候,可以把相同的东西当如原型里,这样每次创建这个对象的时候,就可以直接调用而不是重新创建。
function Student(name, age) {
this.name = name
this.age = age
// 如果我们每个对象都创建那么这两个方法会出现很多的冗余
// this.running = function () {
// console.log(this.name + "running");
// }
// this.eating = function () {
// console.log(this.name + "eating");
// }
}
Student.prototype.running = function () {
console.log(this.name + "running");
}
Student.prototype.eating = function () {
console.log(this.name + "eating");
}
var stu1 = Student("jjj", 12)
var stu2 = Student("hhh", 18)
Constructor属性
原型对象上面是有一个属性的:constructor,默认情况下原型都会有一个叫constructor
指向当前的对象
function Person() { }
var PersonProtype = Person.prototype
console.log(PersonProtype);
console.log(PersonProtype.constructor);
console.log(PersonProtype.constructor == Person);
原型对象是可以重写的,当我们需要给原型添加更多的属性的时候一般我们会选择重写原型对象
我们也可以改变原型对象中constructor的指向的使用
//改变指向对象
Person.prototype={
constructor:Person
}
//修改枚举类型
Object.defineProperty(Person.prototype,"constructor",{
enumerable:false
})
这里要注意的是原生的constructor是不可枚举的,但是修改constructor的时候会让constructor的特性被设置为true这个时候需要修改一下对象默认属性设置
创建对象的内存表现:
如果我们向对象加入属性在之后的变化:
多种继承方式
继承
面向对象有三大特性:封装、继承、多态
- 封装:我们前面将属性和方法封装到一个类中,可以称之为封装的过程
- 继承:继承是面向对象中非常重要的,不仅仅可以减少重复代码的数量,也是多态前提(纯面向对象中)
- 多态:不同的对象在执行时表现出不同的形态
这里主要将JS中的继承,在了解继承之前我们需要了解JS中的原型链机制,这个是之后理解的关键
原型链
在js中我们不断的获取原型对象,原型链最顶层的原型对象就是Object的原型对象
[Object: null prototype] {}
这种提示一般有两个情况:
- 该对象有原型,且这个原型的属性指向null或者最顶层了
- 这个对象有很多的默认属性方法
ps:Object是所有类的父类
我们也可以对原型链做一些自定义操作,比如这样:
var obj = {
}
obj.__proto__ = {
}
obj.__proto__.__proto__ = {
}
obj.__proto__.__proto__.__proto__ = {
name: "小冷"
}
原型链实现继承
function Person(){
this.name = "l"
}
var p = new Person()
stu.prototype = p
//name == l
stu.prototype.studying = function(){
console.log(this.name+"studying")
}
我们可以通过赋值原型的形式来实现继承,但是有一些弊端
- 直接打印对象是看不到属性的
- 这个属性会被多个对象共享,如果是引用类型就会造成问题
- 不能给父类传递参数,没法定制化
借用构造函数继承
为了解决原型链继承中存在的问题,constructor stealing
应运而生 ,借用继承的做法非常简单:在子类型构造函数的内部调用父类型构造函数
- 因为函数可以任意调用
- 因此通过apply和call也可以再新创建的对象上实行构造函数
function Person(name, age, height, address) {
this.name = name
this.age = age
this.height = height
this.address = address
}
function Student(name, age, height, address, sno, score) {
Person.call(this,name, age, height, address)
this.sno = sno
this.score = score
}
可以使用父类的构造函数来实现创造,解决之前原型链的问题 在ES6之前一直是保持的这个方式,但是这个继承方式依然不是很完美
- 无论在什么情况下,都会调用两次父类构造函数。 一次是创建子类原型,一次是构造函数
- 所有的子类都会有两份父类的属性
继承最终方案
在继续的发展中, JSON的创立者道格拉斯, 提到了新的继承方法,这也是目前es5 阶段最合适的继承方案 寄生组合继承
- 结合原型类继承和工厂模式
- 创建一个封装继承过程的函数,在这个函数的内部来增强对象,最后将这个对象返回
function Person(name, age, height, address) {
this.name = name
this.age = age
this.height = height
this.address = address
}
Person.prototype.running = function () {
console.log(this.name + " running");
}
function Student(name, age, height, address, sno, score) {
Person.call(this, name, age, height, address)
this.sno = sno
this.score = score
}
// 原型继承
var obj = Object.create(Person.prototype)
console.log(obj.__proto__ === Person.prototype);
Student.prototype = obj
// 上到真是环境 会封装用 为了兼容性可以多一个创造类的方法
function object(o){
function F(){}
F.prototype = o
return new F()
}
function inherit(Subtype, Supertype) {
Subtype.prototype = object(Supertype.prototype)
// 需要构造方法
Object.defineProperty(Subtype, "constructor", {
enumerable: false,
configurable: this,
writable: true,
value: Subtype
})
}
inherit(Student, Person)
Student.prototype.eating = function () {
console.log(this.name + "eating");
}
var stu = new Student("小明");
stu.eating()
对象方法补充
hasOwnProperty : 对象是否有某一个属于自己的属性
in/for in 操作符: 判断某个属性是否在对象或者对象的原型上
instanceof : 用于检测构造函数的原型,是否出现在某个实例对象的圆形脸上
isPrototypeOf:用于检测某个对象,是否出现在某个实例对象的原型链上