我们都知道面向对象语言如 Java, C++ 等都基本实现了 封装, 继承, 多态
等特性, 用面向对象语言来编程的基本套路就是抽象出类, 然后实例化, 用实例调用方法来模拟进行程序间的通信.
但 JS 不是面向对象的语言, 或者我们称它的脚本语言, 它的一等公民就是 对象/函数
. 本篇这里主要来说 JS 如何能进行模拟 OO 语言实现 继承
特性的, 即它巧妙的原型设计.
什么是 prototype
以前我也不懂这些东西, 后来用多了就渐渐懂了, 或者说先记住,会用等积累多了自然就明白了.
- 任何函数都有
prototype
属性, 翻译成中文就是 "原型" 的意思, 它是一个指针, 指向原型对象 - prototype 属性值是个
对象
, 它默认拥有用constructor
属性指向是函数自身
function sum(a, b) {
return a + b
}
console.log('函数的原型是一个对象: ', sum.prototype, typeof sum.prototype)
console.log('次对象自有的 constructor 属性值是函数自身', sum.prototype.constructor);
console.log(sum.prototype.constructor == sum);
console.log('1 + 1 的值是: ', sum.prototype.constructor(1, 1));
函数的原型是一个对象: {} object
次对象自有的 constructor 属性值是函数自身 [Function: sum]
true
1 + 1 的值是: 2
这例子说明, 函数对象天生就有一个 prototype 属性, 它是一个对象. 且此对象的也天生具有一个 constructor 属性指向函数自身.
那这个感觉有点内循环的逻辑设计有什么用呢?
- 普通函数的
prototype
属性确实没啥大作用, 但对于构造函数
用大作用 - 构造函数的
prototype
属性是它实例的原型
构造函数的 prototype
属性是它实例的原型
这个结论非常非常重要!!!
假设有一个构造函数: People // function
则它会自带一个原型属性: People.prototyep // object
现在它有一个实例: const youge = new People()
划重点!
youge.__ proto __ == People.prototype
这里的 __ proto __ 和 prototype 功能一样, 只是前者是浏览器添加的方法做兼容而已
只有函数对象才有 prototye, 其他对象不一定有. 但所有对象都有 __ proto __
function People(name, age, sex) {
this.name = name
this.age = age
this.sex = sex
}
// 实例化
var youge = new People('油哥', 18, '男')
// 测试三角关系是否存在, 即实例的原型 是否等于 构造函数的 prototype
console.log(youge.__proto__ === People.prototype); // 返回 true
实例的原型, 是其构造函数的 prototype 属性, 这个结论真的是太重要啦!
可以想象成构造函数 People 是母亲, People.prototype 是父亲. 构造函数 (母亲) People 生出来的儿子 (实例) youge 的原型 __proto __
就指向了他的父亲, 父亲的 constructor
类似配偶, 又指向了 People 母亲
这个说法似乎有点问题, 但意思差不多吧, 那这个东东到底有什么用呢 ?
答案就是: 原型链查找!
原型链查找
- JS 规定, 实例可以打点访问它的原型上属性和方法, 这又被成为
原型链查找
function People(name, age, sex) {
this.name = name
this.age = age
this.sex = sex
}
// 在原型上添加属性和方法, 则实例都可以进行访问
People.prototype.want = '躺平'
People.prototype.eat = function (name) {
return name
}
// 实例化
var youge = new People('油哥', 18, '男')
// 实例可访问它原型上的属性或方法
console.log('想: ', youge.want);
console.log('吃: ', youge.eat('肉肉'));
console.log(youge)
// 给对象绑定了同原型上同名方法时, 会遮蔽掉原型的
youge.want = '上班'
console.log('想: ', youge.want);
想: 躺平
吃: 肉肉
People { name: '油哥', age: 18, sex: '男' }
想: 上班
对象的 hasOwnProperty 方法
hasOwnProperty()
可以检查对象是否真正自己拥有
某个属性或者方法in
运算符只能检查某个属性或方法是否可以被对象访问, 不能检查是否是自己
function People(name, age, sex) {
this.name = name
this.age = age
this.sex = sex
}
// 在原型上添加属性和方法, 则实例都可以进行访问
People.prototype.want = '躺平'
People.prototype.eat = function (name) {
return name
}
// 实例化
var youge = new People('油哥', 18, '男')
console.log(youge.hasOwnProperty('name'));
console.log(youge.hasOwnProperty('sex'));
console.log(youge.hasOwnProperty('sex'));
// 这个 want 是原型上的, 就不是自己的属性/方法啦
console.log(youge.hasOwnProperty('want'));
// in 的话原型上的属性也会算给对象
console.log('want' in youge);
true
true
true
false
true
所以, 这个 in
还是没有像 Python 里面的 in 强大, 看来还是得用 hasOwnProperty()
稳一点.
可以看到这个原型还是真和面向对象里面的 类 有点像的, 但是我觉得它更巧妙一点呢. 同时, 如果我们要进行封装啥的, 那直接可以将方法和属性写到原型上, 这样就相等于面向对象中的 类方法
, 让所有的实例对象都能使用, 同时更节约内存.