原型链

196 阅读3分钟

前提:

对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的类其实就是语法糖,这样更简洁