一. 面向对象
面向对象编程是一种程序设计思想,它具有 3 个显著的特征:封装、继承、多态。
1. 构造函数
封装:以往以普通对象形式封装的代码只是单纯把一系列的变量或函数组合到一起,所有的数据变量都被用来共享(使用 this 访问)。
<script> // 普通对象形式的封装 let beats = { name: '周杰伦', setName: function (name) { this.name = this.name; }, getName() { console.log(this.name); } } beats.setName('周杰伦'); beats.getName(); </script>
对比以下通过面向对象的构造函数实现的封装:
<script> function Person() { this.name = '周杰伦'; // 设置名字 this.setName = function (name) { this.name = name; } // 读取名字 this.getName = () => { console.log(this.name); } } // 实例对像,获得了构造函数中封装的所有逻辑 let p1 = new Person(); p1.setName('张学友'); console.log(p1.name);//张学友 // 实例对象 let p2 = new Person(); console.log(p2.name); //周杰伦 </script>
同样的将变量和函数组合到了一起并能通过 this 实现数据的共享,所不同的是借助构造函数创建出来的实例对象之间是彼此不影响的。
总结:
-
构造函数体现了面向对象的封装特性
-
构造函数实例创建的对象彼此独立、互不影响
-
命名空间式的封装无法保证数据的独立性
2. 原型对象
实际上每一个构造函数都有一个名为
prototype
的属性,译成中文是原型的意思,prototype
的是对象类据类型,称为构造函数的原型对象,每个原型对象都具有constructor
属性代表了该原型对象对应的构造函数。// 1. prototype属性构造函数上的一个属性,这个属性是一个对象; // 特点:所有这个构造函数创建出来的实例对象,都可以访问prototype属性上的值 // 创建一个构造函数 function Star(name, age) { this.name = name this.age = age this.sing = function () { console.log('唱歌') } } //所有构造函数上都有一个 prototype属性 这个属性默认是一个对象 //普通的静态成员 实例对象无法调用 Star.type = '明星' //向prottype属性上绑定的属性和方法 所有实例对象共享 Star.prototype.gender = '男' Star.prototype.dance = '跳舞' //创建实例 const ldh = new Star('刘德华', 20) const zxy = new Star('张学友', 10) //实例对象上持有的属性 所有实例对象各一份; console.log(ldh.sing === zxy.sing) //false console.log(ldh.type); //prottype属性上绑定的属性和方法 所有实例对象共享 console.log(ldh.gender === zxy.gender) // true console.log(ldh.dance === zxy.dance) //true
原型对象中的this指向
// 普通的函数,this指向函数的调用者; // 1.构造函数中this指向 - 实例对象; // 2.prototype中的this指向 - 实例对象; // 构造函数的属性绑定写到构造函数中 function Star(name, age) { this.name = name; this.age = age; } // 构造函数中的方法绑定,写到 prototype 上; Star.prototype.sing = function (song) { //原型方法中的this,指向的不是原型,而是实例对象; console.log(this); console.log(this.name + '唱歌 - ' + song); } //创建实例对象 const ldh = new Star('刘德华', 18); ldh.sing('冰雨'); //创建实例对象 const zxy = new Star('张学友', 20); zxy.sing('吻别')
constructor作用
// 1. 构造函数可以通过 prototype 属性找到原型对象
// 2. 原型对象上有一个属性 constructor 可以找到构造函数
function Star(name, age) {
this.name = name
this.age = age
}
// 作用 - 能够让实例对象,找到构造函数;
const zxy = new Star('张学友', 18)
console.log(zxy)
console.log(zxy.constructor)
// 通过实例对象, 可以找到它的构造函数
const ldh = new zxy.constructor('刘德华', 20)
console.log(ldh)
console.log(ldh.constructor)
原型继承
继承 : 就是一个(子) 对象 继承另一个(父) 对象 的成员
原型继承:把 父对象
作为 子对象
构造函数的 原型。使用他里面的属性和方法。就是把一个father对象 添加到另一个son对象的构造函数中的prototype中,这样son就可以使用father里面的属性和方法了。
//父解构函数 - 将来让Father构造函数,Son构造函数继承
function Father(name, age) {
this.name = name
this.age = age
}
//方法写在原型中
Father.prototype.somking = function () {
console.log('抽烟')
}
//子构造函数 Son
function Son(name, age) {
this.name = name
this.age = age
}
//原型继承 - 将来Son的实例对象,上面没有的属性 就可以去Father的原型去找
Son.prototype = new Father()
//原型继承 - 如果用对象覆盖调用一个构造函数的原型对象,需要手动修改他的constructor
Son.prototype.constructor = Son
//设置完继承 就可以添加任意方法了
Son.prototype.talking = function () {
console.log('说话')
}
//测试构造函数
console.log(Son.prototype.constructor)
//创建实例 Father
const m1 = new Son('爸爸', 28)
console.log(m1)
//创建实例 Son
const m2 = new Son('儿子', 8)
// son 里面现在也可以使用Father里面的方法了
m2.somking()
console.log(m2)
// 记住 prototype里面的属性和方法,所有实例对象都可以共享
对象原型
对象都会有一个属性 __proto__ 指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 __proto__ 原型的存在。
构造函数,实例对象,原型对象三者之间的关系
我画了一张图来表示他们三者之间的关系(有点丑不要建议)
从上图构造函数,原型对象,对象实例三者之间的关系中,我们可以引申出原型链的概念。
原型链
这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系称为原型链,
在 JavaScript 对象中包括了一个非标准备的属性 __proto__
它指向了构造函数的原型对象,通过它可以清楚的查看原型对象的链状结构。
① 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
② 如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)
③ 如果还没有就查找原型对象的原型(Object的原型对象)
④ 依此类推一直找到 Object 为止(null)
⑤ __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
⑥ 可以使用 instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
⑦这个Object的构造函数new的实例对象由js的解释器执行