原型链
构造函数的属性是各个实例自己的属性,原型(对象)的属性是所有实例共享的属性
- 构造函数的原型 (
prototype ),就是它的实例的原型对象 (__proto__ ) Object.protptype 是最顶层的原型(对象),其原型对象为 null - 只有
Object.protptype 没有原型对象(为 null );除此之外的对象,都能通过 .__proto__ 找到原型对象 - 一般构造函数的原型,是
Object 的实例;构造函数的原型的原型对象,是 Object.prototype
构造函数
- 引用类型:有内置的构造函数:
Object() 、Array() 、Function() 、RegExp() 、Date() … - 基本类型:① 有内置的包装类:
Number() 、String() 、Boolean() ;② null 、undefined 无包装类
原型链查找逻辑
访问实例的属性时,先查看该实例的属性;若没有,则查看其原型对象的属性;若还是没有,则查看其原型对象的原型对象的属性;以此类推… 直至找到 Object.prototype
- 所以,所有对象都能使用
Object.prototype 的属性 ,eg:toString() - 如果我们给
Object.prototype 添加属性,则所有的实例对象都能使用该属性 - 如果我们给实例重写了一些的同名属性,会覆盖其原型对象的属性,eg:
Number.toString()
判断对象的属性
① 对象可以通过打点,判断属性是否存在于原型链中
如果属性存在,则返回属性值;如果不存在,则返回 undefined ;如果属性值为 undefined ,也返回 undefined
var obj = {
a: 1
}
console.log(obj.a)
console.log(obj.b)
obj.__proto__.b = 2
console.log(obj.b)
② 可以通过 in 运算符,判断属性是否存在于原型链中
- 如果属性存在,则返回
true ;否则返回 false - 注意属性是 string 类型的,要用引号括住
var obj = {
a: 1
}
console.log("a" in obj);
console.log("b" in obj);
obj.__proto__ = {
b: 20
}
console.log("b" in obj);
- 我们还可以通过
in 操作符,遍历原型链上的可枚举属性 - 可枚举属性是指原型链上,自己添加的属性。系统默认的属性(eg:constructor)是不可枚举的
for (var k in obj) {
console.log(k)
}
我们可以通过 hasOwnProperty() ,判断属性是否在实例自己身上
hasOwnProperty() 定义在 Object.prototype 对象上面,所以任意对象都可调用该方法- 如果实例存在指定属性,则返回
true ;否则返回 false (不考虑原型链)
var obj = {
a: 1
}
obj.__proto__.b = 4
console.log(obj.hasOwnProperty("a"));
console.log(obj.hasOwnProperty("b"));
配合 for in 使用,就可以遍历实例自己的属性啦
for (var k in obj) {
obj.hasOwnProperty(k) && console.log(k);
}
Object.defineProperty()
我们可以通过 Object.defineProperty() 方法,定义属性 / 修改属性的配置(eg:是否可枚举)
该方法接收 3 个参数:属性所在的对象 、属性名 、描述符对象
描述符对象 有以下配置项:
configurable :是否可以重新配置;默认为 true enumerable :是否可以通过 for in 枚举;默认为 false writable :属性值是否可修改;默认为 false value :默认属性值;默认为 undefined
var obj = {}
Object.defineProperty(obj, 'name', {
configurable: true,
enumerable: true,
writable: true,
value: 'superman'
})
console.log(obj)
除了以上配置项外,描述符对象 还有两个方法:
get :获取属性值时,会被调用set :修改属性值时,会被调用
Object.defineProperties()
- 可通过该方法,同时对多个属性进行定义 / 修改其配置
var book = {}
Object.defineProperties(book, {
_year: {
writable: true,
value: 2004
},
edition: {
writable: true,
value: 1
},
year: {
get: function () {
return this._year;
},
set: function (newValue) {
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
}
})
console.log(book)
book.year = 2010
console.log(book)
Object.getOwnPropertyDescriptor()
- 用于获取属性的描述对象
- 第1个参数:
属性所属的对象 ;第2个参数:属性名 - 返回该属性的
描述符对象
Object.getOwnPropertyDescriptor(obj, 'name')
instanceof
function Dog() {}
function Cat() {}
Cat.prototype = new Dog()
var a = new Cat()
console.log(a.constructor)
console.log(a.__proto__)
console.log(a.__proto__.__proto__)
console.log(a.__proto__.__proto__.__proto__)
console.log(a.__proto__.__proto__.__proto__.__proto__)
console.log(a instanceof Cat)
console.log(a instanceof Dog)
console.log(a instanceof Object)
验证数组
- 使用
typeof
var arr = []
console.log(typeof arr)
- 使用
instanceof
var arr = []
console.log(arr instanceof Array)
arr 不一定是 Array 的实例,也有可能是某继承了 Array 的构造函数的实例
- 使用 API(IE9 开始兼容)
var arr = []
console.log(Array.isArray(arr))
检测数据类型的方法
使用 Object.prototype.toString.call()
Object.prototype.toString()
Object.prototype.toString.call({});
Object.prototype.toString.call(function () {});
Object.prototype.toString.call([]);
Object.prototype.toString.call(123);
Object.prototype.toString.call("123");
Object.prototype.toString.call(true);
Object.prototype.toString.call(undefined);
Object.prototype.toString.call(null);
必须通过 Object.prototype 调用 toString() ,因为子类可能重写了该方法(eg:Number(1).toString() = 1 )
继承
原型链继承
将父级的实例作为子类的原型
子类可以重写父类的方法。重写后,子类的方法会覆盖父类的方法。
function People(name) {
this.name = name
}
People.prototype.sayHello = function () {
console.log("名字:" + this.name)
}
function Student(name, id) {
this.name = name
this.id = id
}
Student.prototype = new People('大明')
Student.prototype.sayHello = function () {
console.log("姓名:" + this.name, "学号:" + this.id)
}
var Hong = new Student("小红", 1001)
Hong.sayHello()
console.log(Hong.__proto__)
Hong.__proto__.sayHello()
console.log(Hong.__proto__.__proto__)
Hong.__proto__.__proto__.sayHello()
构造函数继承
- 可以简单地认为是,复制父类实例的属性给子类
- 方法都在父类的构造函数中定义,只能继承父类实例的属性
- 继承不涉及父类的原型链,不能继承父类的原型链上的属性
- 每个子类都有父类实例的函数的副本,影响性能
function People(name) {
this.name = name
}
People.prototype.sayHello = function () {
alert("你好我是" + this.name)
}
function Student(name, id) {
People.call(this, name)
this.id = id
}
Student.prototype.study = function () {
console.log("好好学习,天天向上")
}
var hong = new Student("小红", 1001)
console.log(hong.name, hong.id)
hong.study()
组合继承
就是原型链继承和构造函数继承组合在一起:
- 通过构造函数继承,继承父类的属性
- 通过原型链继承,继承父类的原型链的属性,以实现函数的复用
function People(name) {
this.name = name
}
People.prototype.sayHello = function () {
console.log("名字:" + this.name)
}
function Student(name, id) {
People.call(this, name)
this.id = id
}
Student.prototype = new People('大明')
Student.prototype.study = function () {
console.log("好好学习,天天向上")
}
var hong = new Student("小红", 1001)
console.log(hong)
hong.sayHello()
- 如果不用
构造函数继承 ,sayHello 输出的名字是大明
寄生组合继承
通过空的构造函数,得到没有属性的子类原型。这样,在进行原型链继承时,就可以避免父类的实例属性数据的冗余
function People(name) {
this.name = name
}
People.prototype.sayHello = function () {
console.log("你好我是" + this.name)
}
function Student(name, id) {
People.call(this, name)
this.id = id
}
function Fn() {}
Fn.prototype = People.prototype
Student.prototype = new Fn()
Student.prototype.study = function () {
console.log("好好学习,天天向上")
}
var hong = new Student("小红", 1001)
hong.sayHello()
圣杯模式
就是将寄生组合继承 封装成函数
function inherit(People, Student) {
function Fn() {}
Fn.prototype = People.prototype
Student.prototype = new Fn()
Student.prototype.constructor = Student
Student.prototype.parent = People
}
inherit(People, Student)
|