Javascript之原型和原型链

简介

对象基础参考:Javascript之对象
对象继承参考:Javascript之对象继承

原型链是一种机制,指的是 JavaScript 每个对象都有一个内置的 __proto__ 属性指向创建它的构造函数的 prototype(原型)属性
原型链的作用是为了实现对象的继承

function 关键字或 Function 构造函数创建的对象都是函数对象

constructor 构造函数

  • 函数还有一种用法,就是把它作为构造函数使用
  • 构造函数本身也是函数,只不过可以用来创建对象而已
  • 按照惯例,构造函数始终都应该以一个大写字母开头
  • 构造函数与其他函数的唯一区别,就在于调用它们的方式不同
  • 任何函数,只要通过 new 操作符来调用,那它就可以作为构造函数
  • 箭头函数不能用作构造函数

每个对象都有一个constructor指针,这个指针指向的是创建该对象实例的构造函数
每个构造函数都有一个原型对象,该原型对象有一个constructor属性,指向创建对象的函数本身

function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ console.log(this.name); }; } var person1 = new Person("Stone", 28, "Software Engineer"); var person2 = new Person("Sophie", 29, "English Teacher"); console.log(person1.sayName == person2.sayName); // false
// 当作构造函数使用 var person = new Person("Stone", 28, "Software Engineer"); person.sayName(); // "Stone" // 作为普通函数调用 Person("Sophie", 29, "English Teacher"); // 添加到 window window.sayName(); // "Sophie" // 在另一个对象的作用域中调用 var o = new Object(); Person.call(o, "Tommy", 3, "Baby"); o.sayName(); // "Tommy"

prototype 原型

  • 我们创建的每个函数都有一个 prototype(原型)属性。
  • 函数独有的,从一个函数指向一个对象(函数的原型对象),也就是这个函数所创建的实例的原型对象
  • 这个属性是一个指针,指向一个对象(函数的原型对象)
  • 函数原型对象的作用:包含实例共享的属性和方法,可以让所有对象实例共享它所包含的属性和方法
  • 不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型中
  • 我们对原型对象所做的任何修改都能够立即从实例上反映出来,即使是先创建了实例后修改原型也照样如此
  • 默认有两个属性,constructor属性和__proto__属性
  • 原型示例
function Person(){} Person.prototype.name = "Stone"; Person.prototype.age = 28; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){ console.log(this.name); }; var person1 = new Person(); person1.sayName(); // "Stone" var person2 = new Person(); person2.sayName(); // "Stone" console.log(person1.sayName == person2.sayName); // true person1.name = "Sophie"; console.log(person1.name); // "Sophie",来自实例 console.log(person2.name); // "Stone",来自原型 delete person1.name; console.log(person1.name); // "Stone",来自原型
  • 原型的另一种写法
function Person(){} Person.prototype = { constructor : Person, name : "Stone", age : 28, job: "Software Engineer", sayName : function () { console.log(this.name); } }; // 重设构造函数,只适用于 ECMAScript 5 兼容的浏览器 // Object.defineProperty(Person.prototype, "constructor", { // enumerable: false, // value: Person // }); var friend = new Person(); console.log(friend instanceof Object); // true console.log(friend instanceof Person); // true console.log(friend.constructor === Person); // false console.log(friend.constructor === Object); // true
  • 原型的动态性
var friend = new Person(); Person.prototype.sayHi = function(){ console.log("hi"); }; friend.sayHi(); // "hi"(没有问题!) // 当我们调用 person.sayHi() 时,首先会在实例中搜索名为 sayHi 的属性,在没找到的情况下,会继续搜索原型。 // 因为实例与原型之间的连接只不过是一个指针,而非一个副本,因此就可以在原型中找到新的 sayHi 属性并返回保存在那里的函数 // 重写整个原型,就等于切断了构造函数与最初原型之间的联系 Person.prototype = { // constructor: Person, name : "Stone", age : 28, job : "Software Engineer", sayName : function () { console.log(this.name); } }; friend.sayName(); // Uncaught TypeError: friend.sayName is not a function
  • 构造函数和原型结合(目前在 JavaScript 中使用最广泛、认同度最高的一种创建自定义类型的方法)
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.friends = ["ZhangSan", "LiSi"]; } Person.prototype = { constructor : Person, sayName : function(){ console.log(this.name); } } var person1 = new Person("Stone", 28, "Software Engineer"); var person2 = new Person("Sophie", 29, "English Teacher"); person1.friends.push("WangWu"); console.log(person1.friends); // "ZhangSan,LiSi,WangWu" console.log(person2.friends); // "ZhangSan,LiSi" console.log(person1.friends === person2.friends); // false console.log(person1.sayName === person2.sayName); // true

proto

为什么在构造函数的 prototype 中定义了属性和方法,它的实例中就能访问呢?
那是因为当调用构造函数创建一个新实例后,该实例的内部将包含一个指针 __proto__,指向构造函数的原型

为了确保浏览器兼容性问题,不要直接使用 __proto__ 属性

  • 当我们访问一个对象的属性时,如果这个属性不存在,那么就会去 __proto__ 里找
  • 这个 __proto__ 又会有自己的 __proto__,于是就这样一直找下去,直到找到为止。
  • 在找不到的情况下,搜索过程总是要一环一环地前行到原型链末端才会停下来。
function Person(){} var person = new Person(); console.log(person.__proto__ === Person.prototype); // true

构造函数、原型对象和实例对象之间的关系如下图

image.png

原型链

  • 每个对象都有一个原型__proto__,这个原型还可以有它自己的原型,以此类推,形成一个原型链
  • 查找特定属性的时候,我们先去这个对象里去找,如果没有的话就去它的原型对象里面去,如果还是没有的话再去向原型对象的原型对象里去寻找
  • 这个操作被委托在整个原型链上,这个就是我们说的原型链

image.png

const Person = function(name) { this.name = name || 'wmm' } Person.prototype.sayName = function() { console.log('prototype.sayName: ', this.name) } const p = new Person() console.log(p.__proto__ === Person.prototype) // true console.log(Person.prototype.constructor === Person) // true // p没有constructor属性,沿着原型链往上找,找到Person.prototype,如果不覆盖的话默认会有constructor属性 console.log(p.constructor === Person) // true console.log(p.name) // wmm66 man // 实例原型链 // 实例 p 的 __proto__ 指向 构造函数的原型对象 console.log(p.__proto__ === Person.prototype) // true // p的构造函数的原型Person.prototype,还有它自己的构造函数(普通对象,是由Object构造的)的原型对象 console.log(Person.prototype.__proto__ === Object.prototype) // true console.log(p.__proto__.__proto__ === Object.prototype) // true // Object是顶级的了,往上没有了。 // Object.prototype这个对象没有原型对象 console.log(Object.prototype.__proto__ === null) // true

继承

const Person = function(name) { this.name = name || 'wmm' } Person.prototype.sayName = function() { console.log('prototype.sayName: ', this.name) } const p = new Person() const Man = function() { Person.call(this, 'wmm66') this.sex = 'man' } // 如果直接用p的话,容易污染,后面不方便演示 // 这里使用Person作为构造函数新建一个对象用来继承。使用new 或者 Object.create // const p1 = new Person() const p1 = Object.create(Person.prototype) Man.prototype = p1 // Man.prototype直接赋值,下面的constructor被覆盖掉了。建议重新指回去 Man.prototype.constructor = Man const m = new Man() console.log(p.__proto__ === Person.prototype) // true console.log(Person.prototype.constructor === Person) // true // p没有constructor属性,沿着原型链往上找,找到Person.prototype,如果不覆盖的话默认会有constructor属性 console.log(p.constructor === Person) // true console.log(m.constructor === Man) // true console.log(m.__proto__ === Man.prototype) // true console.log(Man.prototype.constructor === Man) // true console.log(m.name, m.sex) // wmm66 man // 实例原型链 // 实例 m 的 __proto__ 指向 构造函数的原型对象 console.log(m.__proto__ === Man.prototype) // true // m的构造函数的原型对象 Man.prototype 就是p1, p1的 __proto__ 指向 p1 的构造函数的原型 console.log(Man.prototype === p1) // true console.log(p1.__proto__ === Person.prototype) // true console.log(Man.prototype.__proto__ === Person.prototype) // true // p的构造函数的原型Person.prototype,还有它自己的构造函数(普通对象,是由Object构造的)的原型对象 console.log(Person.prototype.__proto__ === Object.prototype) // true // Object是顶级的了,往上没有了。 // Object.prototype这个对象没有原型对象 console.log(Object.prototype.__proto__ === null) // true

关卡

// 1.定义一个构造函数 Animal,它有一个 name 属性,以及一个 eat() 原型方法。 // 2.eat() 的方法体为:console.log(this.name + " is eating something.")。 // 3.new 一个 Animal 的实例 tiger,然后调用 eat() 方法。 // 4.用 __proto__ 模拟 new Animal() 的过程,然后调用 eat() 方法。 var Animal = function(name){ this.name = name; }; Animal.prototype.eat = function(){ console.log(this.name + " is eating something."); }; var tiger = new Animal("tiger"); tiger.eat(); var tiger2 = {}; tiger2.__proto__ = Animal.prototype; Animal.call(tiger2, "tiger2"); tiger2.eat();
// 1.定义一个构造函数 Bird,它继承自 Animal,它有一个 name 属性,以及一个 fly() 原型方法。 // 2.fly() 的方法体为:console.log(this.name + " want to fly higher.");。 // 3.new 一个 Bird 的实例 pigeon,然后调用 eat() 和 fly() 方法。 // 4.用 __proto__ 模拟 new Bird() 的过程,然后用代码解释 pigeon2 为何能调用 eat() 方法。 var Bird = function(name){ this.name = name; } Bird.prototype = new Animal(); Bird.prototype.fly = function(){ console.log(this.name + " want to fly higher."); }; var pigeon = new Bird("pigeon"); pigeon.eat(); pigeon.fly(); var pigeon2 = {}; pigeon2.__proto__ = Bird.prototype; Bird.call(pigeon2, "pigeon2"); console.log(pigeon2.__proto__.__proto__.eat === Animal.prototype.eat);
// 1.定义一个构造函数 Swallow,它继承自 Bird,它有一个 name 属性,以及一个 nesting() 原型方法。 // 2.nesting() 的方法体为:console.log(this.name + " is nesting now.");。 // 3.new 一个 Swallow 的实例 yanzi,然后调用 eat()、fly() 和 nesting() 方法。 // 4.用 __proto__ 模拟 new Swallow() 的过程,然后用代码解释 yanzi2 为何能调用 eat() 方法。 var Swallow = function(name){ this.name = name; } Swallow.prototype = new Bird(); Swallow.prototype.nesting = function(){ console.log(this.name + " is nesting now."); }; var yanzi = new Swallow("yanzi"); yanzi.eat(); yanzi.fly(); yanzi.nesting(); var yanzi2 = {}; yanzi2.__proto__ = Swallow.prototype; Swallow.call(yanzi2, "yanzi2"); console.log(yanzi2.__proto__.__proto__.__proto__.eat === Animal.prototype.eat);

创作不易,若本文对你有帮助,欢迎打赏支持作者!

 分享给好友: