构造函数、js原型、原型链、继承
构造函数
构造函数分为 实例成员 和 静态成员
实例成员: 实例成员就是在构造函数内部,通过this添加的成员。实例成员只能通过实例化的对象来访问。
静态成员: 在构造函数本身上添加的成员,只能通过构造函数来访问
function Star(name,age) {
//实例成员
this.name = name;
this.age = age;
}
//静态成员
Star.sex = '女';
let stars = new Star('小红',18);
console.log(stars); // Star {name: "小红", age: 18}
console.log(stars.sex); // undefined 实例无法访问sex属性
console.log(Star.name); //Star 通过构造函数无法直接访问实例成员
console.log(Star.sex); //女 通过构造函数可直接访问静态成员
通过构造函数创建对象
通过构造函数创建一个对象
function Father(name) {
this.name = name;
}
let son = new Father('Lisa');
console.log(son); //Father {name: "Lisa"}
new的过程
(1) 创建一个空对象 son {}
(2) 为 son 准备原型链连接 son.__proto__ = Father.prototype
(3) 重新绑定this,使构造函数的this指向新对象 Father.call(this)
(4) 为新对象属性赋值 son.name
(5) 返回this,此时的新对象就拥有了构造函数的方法和属性了
在构造函数上直接定义方法(不共享)
function Star() {
this.sing = function () {
console.log('我爱唱歌');
}
}
let stu1 = new Star();
let stu2 = new Star();
stu1.sing();//我爱唱歌
stu2.sing();//我爱唱歌
console.log(stu1.sing === stu2.sing);//false
在构造函数上通过this来添加方法的方式来生成实例,每次生成实例,都是新开辟一个内存空间存方法。
通过原型添加方法(共享)
function Star(name) {
this.name = name;
}
Star.prototype.sing = function () {
console.log('我爱唱歌', this.name);
};
let stu1 = new Star('小红');
let stu2 = new Star('小蓝');
stu1.sing();//我爱唱歌 小红
stu2.sing();//我爱唱歌 小蓝
console.log(stu1.sing === stu2.sing);//true
构造函数通过原型添加的函数,是所有对象共享的。
定义构造函数
公共属性定义到构造函数里面,公共方法我们放到原型对象身上。
原型、原型链
function Star(name) {
this.name = name;
}
console.log(Star.prototype)//{constructor: ƒ Star(name)}
console.log(Star.prototype.constructor === Star);//true
Star.prototype 就是原型。原型的作用,就是共享方法。
原型与原型层层相链接的过程即为原型链。对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有__proto__原型的存在
function Star(name,age) {
this.name = name;
this.age = age;
}
Star.prototype.dance = function(){
console.log('我在跳舞',this.name);
};
let obj = new Star('张萌',18);
console.log(obj.__proto__); //{dance: ƒ, constructor: ƒ}
console.log(obj.__proto__ === Star.prototype);//true
console.log(obj.__proto__.constructor); // Star
console.log(obj.__proto__.constructor === Star); //true
原型链图
原型查找顺序
(1)首先看obj对象身上是否有dance方法,如果有,则执行对象身上的方法。
(2)如果没有dance方法,就去构造函数原型对象prototype身上去查找dance这个方法。
(3)如果再没有dance方法,就去Object原型对象prototype身上去查找dance这个方法。
(4)如果再没有,则会报错。
function Star(name) {
this.name = name;
(1)首先看obj对象身上是否有dance方法,如果有,则执行对象身上的方法
this.dance = function () {
console.log(this.name + '1');
}
}
(2)如果没有dance方法,就去构造函数原型对象prototype身上去查找dance这个方法。
Star.prototype.dance = function () {
console.log(this.name + '2');
};
(3)如果再没有dance方法,就去Object原型对象prototype身上去查找dance这个方法。
Object.prototype.dance = function () {
console.log(this.name + '3');
};
(4)如果再没有,则会报错。
let obj = new Star('小红');
obj.dance();
继承
ES5
利用call改变this指向,只可以继承属性,实例不可以使用父类的方法。
function Father(name) {
this.name = name;
}
Father.prototype.dance = function () {
console.log('I am dancing');
};
function Son(name, age) {
Father.call(this, name);
this.age = age;
}
let son = new Son('小红', 100);
console.log(Father.prototype) //{dance: ƒ, constructor: ƒ}
// 利用call改变this指向,只可以继承属性,实例不可以使用父类的方法。
console.log(son.__proto__) //{constructor: ƒ}
son.dance(); //报错 son.dance is not a function
Son.prototype = Father.prototype
改变原型指向,我们给子类增加原型方法,同样会影响到父类
function Father(name) {
this.name = name;
}
Father.prototype.dance = function () {
console.log('I am dancing');
};
function Son(name, age) {
Father.call(this, name);
this.age = age;
}
console.log(Son.prototype)//{constructor: ƒ}
console.log(Father.prototype)//{dance: ƒ, constructor: ƒ}
Son.prototype = Father.prototype;
console.log(Son.prototype)//{dance: ƒ, constructor: ƒ}
//为子类添加方法
Son.prototype.sing = function () {
console.log('I am singing');
};
let son = new Son('小红', 100);
//此时父类也被影响了
console.log(Father.prototype === Son.prototype) // true
console.log(Father.prototype) //{dance: ƒ, sing: ƒ, constructor: ƒ}
子类的原型指向父类的实例,共享父类的方法了。为子类添加原型方法的时候,就不会影响父类。
function Father(name) {
this.name = name;
}
Father.prototype.dance = function () {
console.log('I am dancing');
};
function Son(name, age) {
Father.call(this, name);
this.age = age;
}
Son.prototype = new Father();
console.log(new Father().__proto__)//{dance: ƒ, constructor: ƒ}
console.log(Son.prototype.__proto__)//{dance: ƒ, constructor: ƒ}
console.log(Son.prototype.__proto__ === Father.prototype)//true
Son.prototype.sing = function () {
console.log('I am singing');
};
let son = new Son('小红', 100);
console.log(son.__proto__.__proto__) //{dance: ƒ, constructor: ƒ}
console.log(Father.prototype) //{dance: ƒ, constructor: ƒ}
如有疑惑,请查看原型查找顺序
类
类的本质还是一个函数,类就是构造函数的另一种写法。
function Star(){}
console.log(typeof Star); //function
class Star {}
console.log(typeof Star); //function
类的所有方法都定义在类的prototype属性上面
class Father{
constructor(name){
this.name = name;
}
sing(){
return this.name;
}
}
let red = new Father('小红');
let green = new Father('小绿');
console.log(red.sing === green.sing); //true
class Father{
constructor(name){
this.name = name;
}
sing(){
return this.name;
}
}
//在原型上追加方法
Object.assign(Father.prototype,{
dance(){
return '我爱跳舞';
}
});
let red = new Father('小红');
let green = new Father('小绿');
console.log(red.dance());//我爱跳舞
console.log(red.dance === green.dance); //true
一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。
ES6
class Father {
constructor(name){
this.name = name;
}
dance(){
return '我在跳舞';
}
}
class Son extends Father{
constructor(name,score){
super(name);
this.score = score;
}
sing(){
return this.name +','+this.dance();
}
}
console.log(Son.prototype)//Father {constructor: ƒ, sing: ƒ}
console.log(Son.prototype.__proto__)//{constructor: ƒ, dance: ƒ}
let obj = new Son('小红',100);//'小红,我在跳舞'
构造函数特点:
1.构造函数有原型对象prototype。
2.构造函数原型对象prototype里面有constructor,指向构造函数本身。
3.构造函数可以通过原型对象添加方法。
4.构造函数创建的实例对象有__proto__原型,指向构造函数的原型对象。
类:
1.class本质还是function
2.类的所有方法都定义在类的prototype属性上
3.类创建的实例,里面也有__proto__指向类的prototype原型对象
4.新的class写法,只是让对象原型的写法更加清晰,更像面向对象编程的语法而已。
5.ES6的类其实就是语法糖。