专业术语
- 类、封装、继承、多态
- 构造函数、实例、对象字面量
- 命名空间
- 内置对象、宿主对象、本地对象
面向对象
js里还没有’类’,js的面向对象,是基于原型的,无论是ES5/还是ES6,ES6中引入的class,只是基于原型继承模型的语法糖。
创建对象的方法
方法1:工厂模式
function body() {
var o = {}
o.name = 'jinxing'
o.age = 20
return o;
}
var person1 = body()
var person2 = body()
方法2:构造函数模式
js中可以使用(function)作为类,定义一个类与定义一个函数一致。使用new操作符,来创建新的对象。该function不显示创造对象。
function Body() {
this.name = 'jinxing'
this.age = 20
}
var person = new Body()
/**
* new的四个步骤
* 1. 创建一个新对象
* var newObj = {}
*
* 2. 将构造函数中的作用域指向该对象,此处可用call合并2,3步操作
* var newFn = Body.bind(newObj)
*
* 3. 执行构造函数中的代码
* newFn()
*
* 4. 返回新对象
* var person = newObj
*/
实现一个上述new函数
function newOperation(constructFn) {
const newObj = Object.create(null)
constructFn.call(newObj)
return newObj
}
方法3:原型模式
我们创建的每个函数都有prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。那么prototype就是通过调用构造函数而创建的那个对象实例的原型对象。
function Body() {}
Body.prototype.name = 'jinxing'
Body.prototype.age = 20
var person = new Body()
什么是原型?
- 真正的原型,在构造函数中。
- 我们每声明一个函数,浏览器就会在内存中创建一个对象,在这个对象中增加一个属性,叫constructor指向我们的函数,并把我们函数的prototype属性指向这个对象,也就是上述**Body.prototype **打印出 { constructor: ƒ Body() } 。
- 用构造函数创建的对象都有一个不可访问的属性 [[ prototype ]] , 这个属性就指向了构造函数的prototype,在浏览器中,支持使用 _proto_ 来访问这个对象。
使用原型对象的好处是,可以在创建出来的多个对象中,共享属性和方法。
实现一个上述new函数
function newOperation(constructFn) {
const newObj = Object.create(constructFn.prototype)
// 创建一个新对象newObj,并让 newObj.__proto__ 指向 constructFn,即 newObj.__proto__ === constructFn.prototype 返回true。
// constructFunc.call(newObj);
return newObj;
}
这种模式下,每一个生成的对象都有一个属性 [[ prototype ]] ,浏览器厂商在具体实现的时候,保留了一个 _proto_ 属性,用以让我们访问 [[ prototype ]] 。
方法4:组合模式
组合模式是构造函数和原型模式一起使用,构造函数模式用于定义实例对象,原型模式用于定义方法和共享属性。
function Body() {
this.name = 'jinxing'
this.age = 20
}
Body.prototype = {
getAge() {
console.log(this.age)
}
}
var person = new Body()
实现一个真正的new函数
function newOperation(constructFn) {
const newObj = Object.create(constructFn.prototype)
constructFunc.call(newObj);
return newObj;
}
原型链
利用js的原型模式,代码读取某个对象的某个属性的时候,都会按照属性名执行一次搜索,在实例中找到了则返回,如果没找到,则继续在当前实例的原型对象中搜索,直到找到为止。如果还没找到,则继续在原型对象的原型对象中寻找,以此类推,知道搜索到Object对象为止,这样就形成了一个原型指向的链条,专业术语称之为原型链。在浏览器中可用person.__proto__.__proto__…进行查找。
继承
方法1:原型链继承
// 父类
function Body() {
this.info = {
name: 'jinxing',
age: 20
}
}
Body.prototype.say = function() {
console.log('hello')
}
// 子类
function Person() {}
Person.prototype = new Body()
var person1 = new Person()
var person2 = new Person()
person1.info.age = 21
console.log(person2.info.age)
// 注意:修改person1的值后,打印person2时info里的age也被更改
// 如果将上述父类改成
function Body() {
this.name = 'jinxing'
this.age = 20
}
// 则修改person1.age = 21不会波及person2的age
// 原因:第一次给person1.info.age赋值之前会先寻找person1.info,在实例上找不到就会在原型链上找,所以在prototype上找到了该对象,操作了父类中info的值,而第二次变成了一个单纯的赋值操作,没有向上查找去寻找原型链上的age,只是单纯的修改了自己的age。
// 结论:如果只有一个点操作符,则赋值操作不会向上寻找,实例上没有该值会直接创建一个再赋值,但是如果有两个或两个以上的点操作符,赋值操作会先向上寻找原型链有没有该值,若有,则赋值覆盖,若没有,则在实例上创建再赋值。(我也不知道对不对)
方法2:借⽤构造函数继承
// 父类
function Body() {
this.info = {
name: 'jinxing',
age: 20
}
}
Body.prototype.say = function() {
console.log('hello')
}
// ⼦类
function Person() {
Body.call(this);
};
var person = new Person();
// 注意:这种⽅式,Person⽆法继承⽗类prototype上的⽅法和属性
方法3:原型链+借用构造函数组合继承
// 父类
function Body() {
this.name = 'jinxing'
this.age = 20
}
Body.prototype.say = function() {
console.log('hello')
}
// ⼦类
function Person() {
Body.call(this);
};
Person.prototype = new Body()
// 上面这句赋值语句会覆盖函数创建时生成的prototype,本来函数的prototype中有constructor指向Person函数本身,所以保持原型链完整要加上下面这句
Person.prototype.constructor = Person
var person = new Person();
方法4:寄生组合继承
// 父类
function Body() {
this.name = 'jinxing'
this.age = 20
}
Body.prototype.say = function() {
console.log('hello')
}
// ⼦类
function Person() {
Body.call(this);
};
Person.prototype = Object.create(Body.prototype)
Person.prototype.constructor = Person
// 为什么不直接Person.prototype = Body.prototype,因为当给Person.prototype增加属性方法时会污染到父类的prototype,所以应该多套一层,用Object.create()给Person的prototype创建一个新对象,并且把Body.prototype挂在Person.prototype的__proto__上,这样及能顺着原型链找到父类方法,还不会污染到父类的prototype。
var person = new Person();
方法5:es6的class
// 父类
class Body {
constructor() {
this.name = 'jinxing'
this.age = 20
}
say: function() {
console.log('hello')
}
}
// 子类
class Person extends Body {
constructor() {
super()
}
}
var person = new Person();