构造函数
js的构造函数
构造函数是一种特殊的函数,主要用于初始胡对象。我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数中。
new对象的过程:
- 在内存中创建一个空对象。
- 让this指向这个新对象。
- 执行构造函数的代码,给这个对象添加属性和方法。
- 返回这个新对象(不需要return)
创建对象的方法
在ES6之前,js没有引入类的概念。对象不是基于类创建,而是通过构造函数来定义对象和它们的特征。
创建对象的方法:
- 对象字面量
- new Object()
- 自定义构造函数
测试:
//利用new Object();
let obj1 = new Object();
//对象字面量
let obj2 = {};
//自定义构造函数
function Star(uname,age){
this.uname = uname;
this.age = age;
this.sing = function(){
console.log("唱歌!")
}
}
let obj3 =new Star("dyf","12");
console.log("obj1",obj1);
console.log("obj2",obj2);
console.log("obj3",obj3);
结果:
js的静态成员和实例成员
JavaScript的构造函数可以添加一下成员和函数,也可以在构造函数的内部this上添加成员和函数。分别称作静态成员和实例成员。
- 在构造函数上添加的成员称为静态成员,只能由构造函数本身来访问。
- 在构造函数内部创建的对象成员称为实例成员,只能由实例化的对象来访问。
//自定义构造函数
function Star(uname,age){
this.uname = uname;
this.age = age;
this.sing = function(){
console.log("唱歌!")
}
}
let obj =new Star("dyf","12");
//实例成员
console.log('实例成员:',obj.sing)
Star.sex = '男';
//静态成员
console.log('静态成员:',Star.sex);
结果:
注意:
- 静态成员绑定在构造函数区域,只能由构造函数访问静态成员。
- 实例成员绑定在对象的this上,而this是创建对象时才产生,只有访问this才能访问实例成员。
构造函数的问题
构造函数中的方法会被多个对象复制,浪费内存。解决办法就是构造函数原型prototype。
js原型链
原型对象prototype
JavaScript规定每个构造函数都有一个prototype属性,prototype指向一个对象,这个对象的所有方法和属性可以被构造函数共享。
function Star(uname,age){
this.uname = uname;
this.age = age;
this.sing = function(){
console.log("唱歌!")
}
}
let obj =new Star("dyf","12");
let obj1 =new Star("feng","122");
console.log(obj)
console.log(obj1)
Star.prototype.walk = function(){
console.log("走路");
}
结果:
注意:原型的对象是构造函数的prototype属性指向的对象。
对象原型_proto_
每个被创建的对象如何共享prototype属性指向的对象?JavaScript规定**对象都会有一个属性_proto_([[Prototype]])**该属性与构造函数的prototype属性指向的对象相同。
注意:
- 对象原型是对象的_proto_属性指向的对象,与其构造函数的prototype属性指向相同。
- 方法查找的顺序:调用sing()方法,首先查找对象ldh对象中是否有sing方法,如果有就执行这个对象上的sing方法;如果没有sing方法,因为有_proto_的存在可以去构造函数的prototype属性指向的对象上查找sing方法。
constructor构造函数
对象原型(proto)和构造函数(prototype)原型对象里面都有一个属于constructor属性,指向构造函数本身。
function Star(uname,age){
this.uname = uname;
this.age = age;
this.sing = function(){
console.log("唱歌!")
}
}
console.log('Star.prototype:',Star.prototype);
//覆盖了原来的原型对象,需要将该对象的constructor指向原来的构造函数
Star.prototype = {
//如果给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数,相当于重新构造一个prototype指向的对象
constructor : Star,
run: function(){
console.log("跑步");
}
}
let obj =new Star("dyf","12");
let obj1 =new Star("feng","122");
console.log(obj)
console.log(obj1)
Star.prototype.walk = function(){
console.log("走路");
}
//构造函数的prototype和对象上的__proto__指向同一个地址
console.log('Star.prototype === obj.__proto__',Star.prototype === obj.__proto__)
//prototype.constructor
console.log('Star.prototype.constructor:',Star.prototype.constructor);
//__proto__.constructor
console.log('obj.__proto__.constructor:',obj.__proto__.constructor);
结果:
注意:
如果给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数,相当于重新构造一个prototype指向的完整对象。
原型链
每个对象都有_proto_,ldh=>star原型对象=>Object原型对象=>null,组成一条链,称作原型链。
function Star(uname,age){
this.uname = uname;
this.age = age;
this.sing = function(){
console.log("唱歌!")
}
}
console.log('Star.prototype:',Star.prototype);
//覆盖了原来的原型对象,需要将该对象的constructor指向原来的构造函数
Star.prototype = {
//如果给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数,相当于重新构造一个prototype指向的对象
constructor : Star,
run: function(){
console.log("跑步");
}
}
let ldh =new Star("dyf","12");
let obj1 =new Star("feng","122");
console.log(ldh)
console.log(obj1)
Star.prototype.walk = function(){
console.log("走路");
}
//构造函数的prototype和对象上的__proto__指向同一个地址
console.log('Star.prototype === obj.__proto__',Star.prototype === ldh.__proto__)
console.log('ldh的__proto__:',ldh.__proto__)
console.log('Star的__proto__:',ldh.__proto__.__proto__)
console.log('Star的__proto__是Object的prototype:',ldh.__proto__.__proto__ === Object.prototype)
console.log('Object的__proto__:',ldh.__proto__.__proto__.__proto__)
JavaScript的成员查找机制
- 当访问一个对象属性时首先查找这个对象本身有没有这个属性,如果没有就沿着原型链查找。
- 查找依据就近原则。
- 如果到object还没找到就返回空。
function Star(uname,age){
this.uname = uname;
this.age = age;
this.sing = function(){
console.log("唱歌!")
}
}
console.log('Star.prototype:',Star.prototype);
//覆盖了原来的原型对象,需要将该对象的constructor指向原来的构造函数
Star.prototype = {
//如果给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数,相当于重新构造一个prototype指向的对象
constructor : Star,
run: function(){
console.log("跑步");
}
}
let ldh =new Star("dyf","12");
let obj1 =new Star("feng","122");
let obj2 =new Star("feng","122");
ldh.sex = '男'
Star.prototype.sex = '女'
Object.prototype.high = '11'
Star.prototype.walk = function(){
console.log("走路");
}
console.log('ldh的sex:',ldh.sex)
console.log('Star的sex:',obj1.sex)
console.log('Object的high:',obj2.high)
console.log('拿不到:',obj2.hh)
结果:
原型对象的this
function Star(uname,age){
this.uname = uname;
this.age = age;
this.sing = function(){
console.log("唱歌!")
}
}
console.log('Star.prototype:',Star.prototype);
//覆盖了原来的原型对象,需要将该对象的constructor指向原来的构造函数
Star.prototype = {
//如果给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数,相当于重新构造一个prototype指向的对象
constructor : Star,
run: function(){
console.log("跑步");
}
}
let ldh =new Star("dyf","12");
let obj1 =new Star("feng","122");
let obj2 =new Star("feng","122");
ldh.sex = '男'
Star.prototype.sex = '女'
Object.prototype.high = '11'
var that;
Star.prototype.walk = function(){
console.log("走路");
that = this;
}
ldh.walk();
console.log(that)
//原型对象对象中this指向调用它的对象
console.log(that === ldh)
结果:
注意:
- 构造函数的this指向调用它的对象实例。
- 原型对象对象中this指向调用它的对象实例。
继承
ES6之前没有提供extends继承。我们通过构造函数和原型对象模拟实现继承,被称为组会继承。
call()
调用这个函数可以修改函数运行时的this指向
fun.call(thisArg,arg1,arg2,…)
- thisArg:当前调用函数this的指向对象
- arg1,arg2:传递的其他参数
function fn(){
console.log('我想喝咖啡!');
console.log(this)
}
let o = {
name: 'andy'
};
//1.call()可以调用函数
fn.call();
//2.call()可以改变这个函数的this指向,此时这个函数的this就指向了o这个对象
fn.call(o)
实现组合继承
//1.父构造函数
function Father(uname,age){
//this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
//2.子构造函数
function Son(uname,age,score){
//使用call函数将父构造函数的this指向指向子构造函数this
//让传入的uname,age挂载到子对象实例空间中,而不是父对象实例空间。
Father.call(this,uname,age);
//Father(uname,age);
this.score = score;
}
let son = new Son("dyf",212,100)
console.log(Son)
console.log(son)
结果:
注意:
使用call函数将父构造函数的this指向指向子构造函数this。让传入的uname,age挂载到子对象实例空间中,而不是父对象实例空间。
组合继承实现子类共享父类方法
利用父类实例对象作为子类的prototype指向的原型对象,修改其constructor构造函数的指向。子类实例的_proto_指向Father的原型对象,则可以调用父类的prototype中方法。
//1.父构造函数
function Father(uname,age){
//this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
Father.prototype.cook = function(){
console.log("做饭!");
}
//不能直接将父的prototype赋值给子的prototype,会指向同一区域。修改任意一方的prototype都会改变双方的prototype
//Son.prototype = Father.prototype
Son.prototype = new Father();
//如果利用对象的形式修改了原型对象,别忘了利用constructor指回原来的构造函数。
Son.prototype.constructor = Son;
Son.prototype.exam = function(){
console.log("考试!");
}
//2.子构造函数
function Son(uname,age,score){
//使用call函数将父构造函数的this指向指向子构造函数this
//让传入的uname,age挂载到子对象实例空间中,而不是父对象实例空间。
Father.call(this,uname,age);
//Father(uname,age);
this.score = score;
}
let son = new Son("dyf",212,100)
console.log(Father.prototype)
console.log(Son)
console.log(son)
son.cook()
结果:
注意:
- 不能直接将父的prototype赋值给子的prototype,会指向同一区域。修改任意一方的prototype都会改变双方的。
- 如果利用对象的形式修改了原型对象,别忘了利用constructor指回原来的构造函数。
类的本质
类的本质是函数,我们可以简单的认为类是构造函数的另一种写法。
构造函数的特点:
- 构造函数有原型对象prototype
- 构造函数原型对象prototype里面有constructor指向构造函数本身
- 构造函数可以通过原型对象添加方法
- 构造函数创建的实例对象有_proto_原型指向构造函数的原型对象
类拥有上述全部特征
验证:
//ES6之前通过 构造函数+原型实现面向对象编程
//1.构造函数有原型对象prototype
//2.构造函数原型对象prototype里面有constructor指向构造函数本身
//3.构造函数可以通过原型对象添加方法
//4.构造函数创建的实例对象有_proto_原型指向构造函数的原型对象
class father {
run(){
console.log("csc")
}
}
//类的本质是函数,我们可以简单的认为类是构造函数的另一种写法
console.log(typeof father)
console.log(father.prototype)
console.log(father.prototype.constructor)
father.prototype.fun = function(){
console.log("开心");
}
let obj = new father();
obj.fun()
console.log(obj)
console.log('father.prototype === obj.__proto__:',father.prototype === obj.__proto__)
结果:
结论:
- 类的本质是function。
- 类的方法定义在prototype属性上。
- 类创建的实例对象上也有__proto__指向prototype原型属性对象。
- ES6之前使用构造函数++原型实现面向对象编程,ES6的Class只是语法糖。