Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

原型和原型连 #26

Open
lazyken opened this issue Aug 2, 2020 · 0 comments
Open

原型和原型连 #26

lazyken opened this issue Aug 2, 2020 · 0 comments

Comments

@lazyken
Copy link
Owner

lazyken commented Aug 2, 2020

原型

构造函数

当用 new 运算符调用函数时,被调用的函数称作为构造函数(详情请看函数和构造函数构造函数部分)。

原型

每个构造函数都有一个 prototype 属性,这个属性指向一个对象,称之为函数的原型对象。
函数的原型对象有一个 constructor(构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针,即指向构造函数。

实例

通过 new 操作符调用构造函数得到的对象,称之为实例。实例内部包含一个指针(内部属性),指向构造函数的原型对象。一般通过对象的 __proto__ 属性可以访问对象的原型。

构造函数、原型、实例的关系

  • 构造函数的 prototype 属性指向 调用构造函数创建的实例 的原型对象;
  • 原型的 constructor 属性指向 prototype 属性所在的构造函数;
  • 实例的 __proto__ 属性指向原型对象。
function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
}
Person.prototype.sayName = function () {
  console.log(this.name);
};
var person1 = new Person('Nicholas', 29, 'Software Engineer');
console.log(Person.prototype.constructor === Person); // true
console.log(person1.__proto__ === Person.prototype); // true
console.log(person1.sayName()); // Nicholas

当读取实例的属性或方法时,如果找不到,就会查找实例的原型中的属性,如果还查不到,就去找原型的原型,一直向上找到最顶层为止。
实例 person1sayName 实际上是继承了它的原型的 sayName 方法。

构造函数、原型、实例的关系

图片来源:'https://github.com/mqyqingfeng/Blog/issues/2'

原型链

先看下面的代码:

function Animal() {
  this.type = 'Animal';
}
Animal.prototype.makeSound = function () {
  console.log(this.sound);
};
function Dog(name) {
  this.name = name || 'dog';
  this.type = 'Dog';
  this.sound = '汪汪汪';
}
function Cat(name) {
  this.name = name || 'mimi';
  this.type = 'Cat';
  this.sound = '喵喵喵';
}
// 修改构造函数的prototype属性,继承了 Animal
Dog.prototype = new Animal();
var myDog = new Dog();
var myCat = new Cat();
// 修改实例的原型,继承了 Animal
myCat.__proto__ = new Animal();
console.log(myDog.makeSound()); // 汪汪汪
console.log(myCat.makeSound()); // 喵喵喵

myDogDog 的实例,调用 myDog.makeSound() 方法时,先在 myDog 实例上寻找 makeSound 方法,没有找到,继续在 myDog 的原型上查找,myDog 的原型是 Animal 的一个实例,Animal 实例上也没有 makeSound 方法,于是继续向上查找 Animal 实例的原型,Animal 实例的原型是 Animal.prototype,它有 makeSound 方法,于是执行了Animal.prototype.makeSound 方法。
像这样,实例与原型之间的层层递进形成的链条一样的关系,称之为原型链。

默认原型

需要知道的是,js中的引用类型都继承了 Object 类型,这个继承也是通过原型链实现的。引用类型都可以通过原型链继承 Object.prototype 上的方法。这也正是所有自定义类型都会继承 toString()、valueOf() 等默认方法的原因。

确定实例与原型的关系

使用 instanceof 运算符

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

使用 isPrototypeOf() 方法

isPrototypeOf() 方法用于测试一个对象是否存在于另一个对象的原型链上。

function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
}
Person.prototype.sayName = function () {
  console.log(this.name);
};
var person1 = new Person('Nicholas', 29, 'Software Engineer');
console.log(person1 instanceof Object); //true
console.log(person1 instanceof Person); //true
console.log(Object.prototype.isPrototypeOf(person1)); //true
console.log(Person.prototype.isPrototypeOf(person1)); //true

原型链的问题

原型中引用类型的值

原型中如果存在引用类型的值,则会被所有实例共享。

function SuperType() {
  this.colors = ['red', 'blue', 'green'];
}
function SubType() {}
SubType.prototype = new SuperType();

var instance1 = new SubType();
instance1.colors.push('black');
alert(instance1.colors); // "red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); // "red,blue,green,black"

如果原型的属性被所有的实例共享,就会存在一个实例修改了这个属性后,也会影响到其他实例,这往往会造成一些不必要的麻烦。因此,通常的做法是在构造函数中,而不是在原型对象中定义属性。

function SuperType() {}
function SubType() {
  this.colors = [];
}
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push('black');
console.log(instance1.colors); // ["black"]
var instance2 = new SubType();
instance2.colors.push('red');
console.log(instance2.colors); // ["red"]

不能向超类型的构造函数中传递参数

原型链的第二个问题是:在创建子类型的实例时,不能向超类型的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。有鉴于此,再加上前面刚刚讨论过的由于原型中包含引用类型值所带来的问题,实践中很少会单独使用原型链。
为了解决这些问题,可以使用一种叫做 借用构造函数(constructor stealing)的技术(有时候也叫做伪造对象或经典继承)。这种技术的基本思想相当简单,即在子类型构造函数的内部调用超类型构造函数。

function SuperType(name) {
  this.name = name;
}
function SubType(name, age) {
  //继承了 SuperType,同时还传递了参数
  SuperType.call(this, name);
  //实例属性
  this.age = age;
}
var instance1 = new SubType('Nicholas', 29);
console.log(instance1.name); // "Nicholas";
console.log(instance1.age); // 29
var instance2 = new SubType('Ken', 28);
console.log(instance2.name); // "Ken";
console.log(instance2.age); // 28

以上代码在 SubType 内部使用 call 实现了对 SuperType 的"继承",同时每个实例都有自己的实例属性,互不影响;而且创建实例时还可以向超类型 SuperType 传递参数。

经验

在使用原型链继承时,一般都是把所有实例都会用到都方法和属性定义在原型上,而实例本身都属性和方法,且需要和其他实例区分开的,则定义在实例自身上(即在构造函数中定义)。
原型链继承的方式和技巧远不止这些,更多的技巧方法和对应解决的问题,可以看 创建对象和原型链继承的多种模式

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant