构造函数
- 可以利用以下方法生成对象
// 利用Object构造函数
let obj1 = new Object()
// 利用字面量
let obj2 = {}
// 利用自定义构造函数
function Star (uname, age) {
this.uname = uname
this.age = age
this.sing = function () {
console.log(`${this.uname}会唱歌`)
}
}
let ldh = new Star('刘德华', 18)
let zxy = new Star('张学友', 29)
ldh.sing()
用构造函数new一个对象时,会在内存为其分配空间,然后令this指向这个对象,然后执行内部代码生成属性和方法,然后返回这个对象
实例成员就是构造函数内部通过this添加的成员 如上uname, age, sing就是实例成员
实例成员必须通过实例对象来访问和使用
console.log(Star.uname); 这样直接通过构造函数访问实例成员会报错,值为undefined
Star.sex = ‘男’
// 如上直接给构造函数添加的成员称为静态成员 sex就是一个静态成员
// 静态成员只能直接通过构造函数访问 且不能通过实例对象访问
console.log(Star.sex);
原型对象
- 构造函数存在内存浪费问题
在使用构造函数实例化不同的对象时,由于方法是复杂数据理类型,不同对象会开辟不同的空间来存储方法
相当于不同的对象的相同方法要占用不同的空间
function Star (uname, age) {
this.uname = uname
this.age = age
this.sing = function () {
console.log(`${this.uname}会唱歌`);
}
}
let ldh = new Star('ldh', 18)
let zxy = new Star('zxy', 18)
console.log(ldh.sing === zxy.sing);
如上所示,两个对象的相同方法并不严格相等,说明其在内存中各自占有不同空间
- 构造函数原型 prototype
- 构造函数通过原型分配的函数是所有对象共享的
- JS规定 ,每个构造函数都有一个prototype属性,指向另一个对象,这个对象是所有属性和方法都会被构造函数所拥有
- 可以把那些不变的方法直接定义在原型对象上,这样所有的实例对象就可以共享这些方法
- 原型就是一个对象 直接称prototype为原型对象即可
- 原型的作用在于共享方法,节约内存
function Star (uname, age) {
this.uname = uname
this.age = age
}
Star.prototype.sing = function () {
console.log(`我会唱歌`);
}
let ldh = new Star('ldh', 18)
let zxy = new Star('zxy', 18)
ldh.sing()
zxy.sing()
以上代码块即利用原型对象来定义方法
对象原型
- 对象都会有一个属性__proto__指向构造函数的prototype原型对象,之所以对象可以直接使用构造函数prototype原型对象的属性和方法就是因为对象有__proto__原型的存在
- 对象身上自带一个__proto__属性,其指向构造函数的原型对象,也就是说对象原型和原型对象等价,对象原型指向原型对象,当对象在自身找不到要访问的属性或方法时会自动通过对象原型指向的原型对象,在原型对象身上寻找要凤问的属性或方法
function Star (uname, age) {
this.uname = uname
this.age = age
}
Star.prototype.sing = function () {
console.log(`我会唱歌`);
}
let ldh = new Star('ldh', 18)
let zxy = new Star('zxy', 18)
console.log(ldh.__proto__ === Star.prototype);
如上代码,对象原型和原型对象是严格相等的
- 方法的查找规则
- 首先先在对象身上直接查找是否有sing方法,如果没有的话,就去__proto__(对象原型)指向的prototype(原型对象)身上查找是否有sing方法
constructor属性指向构造函数本身
- 对象原型和构造函数的原型对象都有一个属性:constructor(对象原型本身就指向原型对象,所以对象原型的constructor属性其实就是原型对象的constructor属性)
- constructor称之为构造函数,他指回构造函数本身
function Star (uname, age) {
this.uname = uname
this.age = age
}
Star.prototype.sing = function () {
console.log(`我会唱歌`);
}
let ldh = new Star('ldh', 18)
let zxy = new Star('zxy', 18)
console.log(Star.prototype.constructor)
console.log(ldh.__proto__.constructor);
如上代码块所示,constructor属性指向构造函数本身,他的主要作用在于记录对象引用于哪个构造函数,他可以让原型对象重新指向原来的构造函数
- 有些情况下需要手动利用constuctor属性指回原来的构造函数
- 上述情况发生在将对象的原型对象prototype手动赋值了一个新的对象(这种情况会将原来的原型对象覆盖掉),此时,需要将该对象的constructor属性手动赋值为构造函数本身,令其重新指向构造函数本身 如下代码块所示
function Star (uname, age) {
this.uname = uname
this.age = age
}
Star.prototype = {
constructor: Star,
sing: function() {console.log(`我会唱歌`);},
movie: function() {console.log(`我会演戏`);}
}
let ldh = new Star('ldh', 18)
let zxy = new Star('zxy', 18)
console.log(Star.prototype.constructor)
console.log(ldh.__proto__.constructor);
构造函数,实例对象,原型对象三者之间的关系
原型链
- 原型对象的__proto__属性指向的是Object构造函数的原型对象 如下代码
function Star (uname) {
this.uname = uname
}
Star.prototype.sing = function () {
console.log('我会唱歌');
}
let ldh = new Star('ldh')
console.log(Star.prototype.__proto__ === Object.prototype);
如上图,Object原型对象的__proto__属性指向一个空对象,而自定义构造函数的原型对象的__proto__属性指向的是Object构造函数的原型对象。
原型链成员查找机制
- 就近原则
- 实例对象 -> 原型对象 -> Object原型对象
- 譬如Object原型对象有toString方法,但实例对象和他的原型对象没有,but实例对象可以直接使用toString方法,这就是由于原型链查找机制
原型对象中的this指向问题
- 在构造函数里,this指向的是对象实例
- 原型对象函数中的this指向对象实例本身 这是由于原型对象函数通常由实例对象来调用,所以其内部的this指向调用者即对象本身,当直接使用构造函数的prototype来调用函数时,由于是原型对象在调用函数,所以此时的this指向的是原型对象本身
通过原型对象给内置对象扩展自定义方法
- 给Array构造函数的原型对象扩展求和方法
Array.prototype.sum = function () {
let sum = 0
for (let d of this) {
sum += d
}
return sum
}
arr = [1,2,3,4,5,6]
console.log(arr.sum());
继承
- call(thisArg, arg) 方法可以调用方法时改变其内部this指向
let obj = {
uname: 'zhaoji'
}
function fn () {
console.log(this.uname);
}
fn.call(obj)
- 借用父构造函数继承属性
function Father (uname, age) {
this.uname = uname
this.age = age
}
function Son (uname, age) {
Father.call(this, uname, age)
}
let son = new Son('刘德华', 18)
console.log(son);
- 借用父构造函数的实例对象继承方法
function Father () {
}
Father.prototype.money = function () {
console.log(10000);
}
function Son () {
Father.call(this)
}
Son.prototype = new Father()
Son.prototype.constructor = Son
let son = new Son()
son.money()
这样借用父构造函数的实例对象来继承父构造函数的方法时,对子实例对象单独定义新方法的话其实是在该实例对象上增加方法,并不会影响父构造函数的原型对象
类的本质
- ES6通过类实现面向对象编程
class Star {
}
console.log(typeof Star);
- 如上所示,类的本质还是一个函数,可以认为类就是构造函数的另外一种写法
- 类也有原型对象
- 类原型对象也有constructor属性,也指向类本身
- 类也可以通过原型对象新增方法
- 类的实例对象的__proto__属性也指向类的原型对象
- ES6的类其实就是构造函数的语法糖
类的用法
- 通过class关键字创建类
- 通过constructor函数接受参数,同时返回实例对象
- 在new一个实例时,会自动调用constructor函数,没有显式定义这个函数的话会自动生成
- 生成实例的new不能省略
class Star {
constructor (uname) {
this.uname = uname
}
sing () {
console.log('小苹果');
}
}
let ldh = new Star('刘德华')
- 类的继承
class Father {
constructor () {
}
money () {
console.log(100);
}
}
class Son extends Father {
constructor (x, y) {
super(x, y)
}
}
let son = new Son()
son.money()
- super关键字
- super关键字可以用于访问和调用父类上的函数,可以用于调用父类的构造函数,也可以调用父类的普通函数
类和对象的注意点
- ES6中的类没有变量提升,所以必须先定义类,才能使用类实例化对象
- 类里面共有的属性和方法必须用this调用
类内的this指向
- constructor内的this指向的就是实例对象
- 方法内的this的指向取决于谁调用了这个方法,谁调用的这个方法,他内部的this就指向谁