前提:
对js的构造函数,原型链一直处于半吊子状态,所以需要静心沉淀
目标:
对constructor、prototype、__proto__和原型链理解通透
step1:为什么要用function创建对象
ES6之前,没有类的概念,所以用一种称为构造函数的特殊函数来定义对象
创建对象的三种方式:
1.对象字面量 let person={}
2.new Object() let person=new Object()
3.自定义构造函数
function Person(name, age) {
this.name = name;
this.age = age;
this.say = function() {
console.log(this.name + "唱歌" + this.age);
};
}
let person1 = new Person("name1", "12");
person1.say();
step2:静态成员与实例成员
实例成员:构造函数内部通过this添加的成员,不可以用构造函数来访问实例成员Person.name is error
静态成员:在构造函数本身添加的成员
Person.sex="女";
person1.sex is error
step3:构造函数的问题
当创建了多个对象,这多个对象在内存中都有占用,当对象中存在了复杂数据类型(Object Array Date function),function函数又会去开辟一块内存空间来存放数据 总结:浪费内存与时间
console.log(person1.say == person2.say); //false
step3:构造函数原型对象prototype
构造函数通过原型分配的函数是所有对象所共享的 每一个构造函数都有一个prototype,prototype就是一个对象,这个对象里面的所有属性与方法,都会被构造函数拥有 可以把不变的方法,直接定义在prototype对象上,这样所有对象的实例都可以共享这些方法
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sing = function() {
console.log("唱歌");
};
let person1 = new Person();
let person2 = new Person();
console.log(person1.sing() === person2.sing()); //true
step4:定义在Person的原型对象的方法,为什么Person的实例对象可以使用呢
let person1 = new Person();
let person2 = new Person();
console.log(person1.__proto__===Person.prototype);
对象__proto__指向构造函数的原型对象 方法查找规则:实例对象是否有对应的方法,如果有就执行,如果没有因为有__proto__的存在,它指向构造函数的原型对象,就去构造函数的原型对象找
step5: constructor与原型
对象的原型(proto)与构造函数的原型对象(prototype)里面都有一个属性constructor,称之为构造函数,因为它指回构造函数本身 constructor主要用于记录该对象引用于哪个构造函数,可以让原型对象重新执行原来的构造函数
console.log(person1.__proto__.constructor);
console.log(Person.prototype.constructor);
打印出:
ƒ Person(name, age) {
this.name = name;
this.age = age;
}
如果更改一下sing方法就又变了,
Person.prototype = {
sing: function() {
console.log("唱歌");
},
};
console.log(person1.__proto__.constructor);
打印出:
ƒ Object() { [native code] }
根据语法也能知道constructor被覆盖了,可以让原型对象重新执行原来的构造函数
Person.prototype = {
constructor:Person,
sing: function() {
console.log("唱歌");
},
};
就可以了
step6:构造函数,原型对象,实例之间的关系
原型对象也是对象,对象都有__proto__
console.log(Person.prototype.__proto__); Object
console.log(Object.prototype.__proto__); null
对象成员的查找规则:自身实例对象上找,找不到往原型上找,找到停止,找不到的话,一直找到null
step7:原型对象的this指向
1>调用的时候确定指向 2>原型对象的this指向实例对象
step8:扩展内置对象
console.log(Array.prototype);
利用原型对象,对原来的内置对象进行扩展自定义的方法,比如数组求偶数的功能
Array.prototype.sum = function() {
var count = 0;
for (var i = 0; i < this.length; i++) {
count += this[i];
}
return count;
};
console.log([1, 2].sum()); //3
step9:组合继承
es6之前,通过构造函数与原型对象模拟实现继承,组合继承 call() :调用这个函数,并且修改函数运行时的this指向
function sing(name) {
console.log(name + "我在唱歌");
}
var person = {
name: "ghp",
};
sing.call(person, "ghp");
step10:继承
继承属性
function Parent(name, age) {
//this 指向父构造函数的对象实例
this.name = name;
this.age = age;
}
function Son() {
//this 指向子构造函数的对象实例
Parent.call(this, "Son", 10);//执行了构造函数,call不仅仅改变this指向,也执行了函数
}
var son = new Son();
console.log(son.name);
继承方法
Parent.prototype.money = function() {
console.log("挣money");
};
Son.prototype = Parent.prototype;//Son.prototype指向的内存与Parent一致,造成Son.prototype有修改会影响到Parent,造成Parent.prototype也会有study方法
Son.prototype.study = function() {
console.log("学习");
};
怎么解决呢?
Son.prototype=new Parent();
Son.prototype.constructor=Son;
step11:类的本质
class Person {
}
console.log(typeof Person); function
1.class的本质还是function 2.类的所有方法都定义在类的prototype属性上 3.es6的类其实就是语法糖,这样更简洁