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

Javascript的继承与原型链 #17

Open
yanyue404 opened this issue Mar 24, 2018 · 0 comments
Open

Javascript的继承与原型链 #17

yanyue404 opened this issue Mar 24, 2018 · 0 comments

Comments

@yanyue404
Copy link
Owner

yanyue404 commented Mar 24, 2018

继承与原型链

什么是原型?

原型(prototype)的定义:给其它对象提供共享属性的对象

哪些数据类型有原型?

只有函数才有 prototype 属性。

基本概念

1. 获取属性的查找规则

如果访问一个对象的属性,首先检查对象是否含有对应的属性,如果含有即得结果;

如果不含有该属性, 则去其原型中查找, 如果原型中含有该属性, 既得结果;

如果原型中依旧没有属性, 就到其原型中的原型去查找, 直到 Object.prototype。最后还没有则返回 undefined

Object.prototype.rain = true;
function Person() {}
console.dir(Person.rain); // true

2.修改对象属性

如果修改一个对象的属性, 那么会检查当前对象是否存在该属性, 如果存在即修改, 如果不存在则会给其添加属性.

3.值类型和引用类型的区别

在使用数组赋值的时候,数组是引用类型,存的是一个指针指向,在实例操作后会改变原型中的引用类型。从而影响所有的实例成员

如果实例对象"修改了"原型中的值类型, 那么其实并没有影响到其他的对象

构造函数 new 一个对象

function Person() {}
Person.prototype.name = 'Rainbow';
var person1 = new Person();
console.log(person1.name);

new 命令简化的内部流程,可以用下面的代码表示。

function _new(/* 构造函数 */ constructor, /* 构造函数参数 */ params) {
  // 将 arguments 对象转为数组
  var args = [].slice.call(arguments);
  // 取出构造函数
  var constructor = args.shift();
  // 创建一个空对象,继承构造函数的 prototype 属性
  var context = Object.create(constructor.prototype);
  // 执行构造函数
  var result = constructor.apply(context, args);
  // 如果返回结果是对象,就直接返回,否则返回 context 对象
  return typeof result === 'object' && result != null ? result : context;
}

// 实例
var actor = _new(Person, '张三', 28);

从构造函数到原型对象

构造函数与其原型对象使用 prototype 属性连接,而原型对象中的 constructor 属性指向构造函数。

console.log(Person === Person.prototype.constructor); // true

实例对象与两者的连接

实例对象的 __proto__指向其该对象的原型,这是每一个 JavaScript 对象(除了 null )都具有的一个属性。

console.log(person1.__proto__ === Person.prototype); // true

Snipaste_2019-11-21_18-55-12.png

原型链

对象的__proto__指向自己构造函数的 prototypeobj.__proto__.__proto__...的原型链由此产生,包括我们的操作符 instanceof 正是通过探测 obj.__proto__.__proto__ === constructor.prototype 来验证 obj 是否是 constructor 的实例。

console.log(Object.prototype.__proto__ === null); // true
console.dir(Object.__proto__.__proto__.__proto__); // null

Snipaste_2019-11-21_19-45-28.png

透过一个例子查看

// 顶级原型链
Object.prototype.foo = 'rainbow';
function doSomething() {}
// 构造函数的原型
doSomething.prototype.hello = 'bar';
// 实例
var doSomeInstancing = new doSomething();
doSomeInstancing.prop = 'some value';
console.log('doSomeInstancing.foo:       ' + doSomeInstancing.foo); // 'rainbow'
console.log('doSomeInstancing.global:       ' + doSomeInstancing.global); // undefined
// 属性查找步骤
// 1. 自身具备 foo属性 ? X
// 2. doSomeInstancing.__proto__: doSomething.prototype 有 foo 属性 X (这里指向构造函数的原型)
// 3. doSomeInstancing.__proto__.__proto__: Object.prototype 自身具备 foo 属性 √

完整的原型链结构上溯

相互关联的原型组成的链状结构就是原型链,也就是__proto__的这条线。

在下面的结构中,__proto__将实例对象,实例对象的原型,实例对象的原型的原型向上连接,直至 `Object.prototype'的上一级null,原型链上溯结束。

var o = { a: 1 };

// o 这个对象继承了 Object.prototype 上面的所有属性
// o 自身没有名为 hasOwnProperty 的属性
// hasOwnProperty 是 Object.prototype 的属性
// 因此 o 继承了 Object.prototype 的 hasOwnProperty
// Object.prototype 的原型为 null
// 原型链如下:
// o ---> Object.prototype ---> null

var a = ['yo', 'whadup', '?'];

// 数组都继承于 Array.prototype
// (Array.prototype 中包含 indexOf, forEach 等方法)
// 原型链如下:
// a ---> Array.prototype ---> Object.prototype ---> null

function f() {
  return 2;
}

// 函数都继承于 Function.prototype
// (Function.prototype 中包含 call, bind等方法)
// 原型链如下:
// f ---> Function.prototype ---> Object.prototype ---> null

原型链

【很少单独来使用】

缺点

1.引用类型值的原型属性会被实例共享

2.在创建子类型的实例时,不能向超类型的构造函数中传递参数【很少单独来使用】

function Father() {
  this.name = 'father';
  this.firend = ['aaa,bbb'];
}
function Son() {}

//原型绑定
Son.prototype = new Father();
Son.prototype.constructor = Son;

var s1 = new Son();
var s2 = new Son();

console.log(s1.name); //father
console.log(s2.name); //father

s1.name = 'son'; //实际上已经在构造函数上定义了这个name属性
console.log(s1.name); //son
console.log(s2.name); //father

console.log(s1.firend); //['aaa,bbb']
console.log(s2.firend); //['aaa,bbb']
s1.firend.push('ccc,ddd');
console.log(s1.firend); //['aaa,bbb,ccc,ddd']
console.log(s2.firend); //['aaa,bbb,ccc,ddd'] 引用类型的原型属性会被实例共享

借用构造函数

【很少单独来使用】

实现方法

在子类型构造函数的内部调用超类型构造函数(使用 apply 和 call 方法)

优点

解决了原型中引用类型属性的问题,并且子类可以向超类中传参

缺点

子类实例无法访问父类(超类)原型中定义的方法,所以函数的复用就无从谈起了

function Father(name, friends) {
  this.name = name;
  this.friends = friends;
}
Father.prototype.getName = function() {
  return this.name;
};

function Son(name) {
  // 注意: 为了确保 Father 构造函数不会重写 Son 构造函数的属性,请将调用 Father 构造函数的代码放在 Son 中定义的属性的前面。
  Father.call(this, name, ['aaa', 'bbb']);

  this.age = 22;
}

var s1 = new Son('son1');
var s2 = new Son('son2');

console.log(s1.name); // son1
console.log(s2.name); // son2

s1.friends.push('ccc', 'ddd');
console.log(s1.friends); // ["aaa", "bbb", "ccc", "ddd"]
console.log(s2.friends); // ["aaa", "bbb"]

// 子类实例无法访问父类原型中的方法
s1.getName(); // TypeError: s1.getName is not a function
s2.getName(); // TypeError: s2.getName is not a function

组合继承

【最常用的继承模式】

又叫伪经典继承,结合原型链继承和构造函数式继承,使用原型链实现对原型属性和方法的继承(函数的复用),又使用构造函数实现对实例属性的继承(每个实例拥有自己的属性)。

优点

避免了原型链继承和构造函数继承的缺点,融合了他们的优点

缺点

无论在什么情况下,都会调用两次超类型构造函数,一次是构造子类型原型,一次是在子类型构造函数内部,解决方法查看寄生组合式继承

function SuperType(name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function() {
  console.log(this.name);
};

function SubType(name, age) {
  //继承属性
  SuperType.call(this, name); // 第二次调用 SuperType()
  this.age = age;
}
//继承方法
SubType.prototype = new SuperType(); // 第一次调用 SuperType()
SubType.prototype.sayAge = function() {
  console.log(this.age);
};

var instancel1 = new SubType('Nicholas', 29);
var instancel2 = new SubType('yue', 25);
instancel1.colors.push('black');
console.log(instancel1.colors); // ["red", "blue", "green", "black"]
console.log(instancel2.colors); // ["red", "blue", "green"] 实例独立

instancel1.sayName(); //Nicholas
instancel2.sayName(); //yue 传参

原型式继承

想要保证一个对象与另一个对象保持类似的工作可以胜任

缺点

包含引用类型值的属性始终会共享相应的属性,无法保证单个实例拥有自己的引用类型属性,这点与原型链模式存在的问题一样

function object(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

var person = {
  name: 'Nicholas',
  friends: ['Shelby', 'Court', 'Van'],
};
var anotherPerson = Object.create(person);
var anotherPerson = object(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob');

var yetAnotherPerson = object(person);
yetAnotherPerson.name = 'Linda';
yetAnotherPerson.friends.push('Barbie');

alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
Object.create API

第一个参数是用作新对象的原型对象(可选的),第二个参数是用来扩展新对象属相的另外的对象,在一个参数的情况下,与object()方法相同

var anotherPerson1 = Object.create(person);
anotherPerson1.name = 'Greg';

var anotherPerson2 = Object.create(person, {
  name: {
    value: 'Greg',
  },
});

寄生式继承

【有用的模式】

缺点

使用寄生继承为对象添加函数,由于不能做到函数的复用性而降低效率。这一点与构造函数类似

function createAnother(original) {
  var clone = Object(original); //调用函数创建一个新对象
  clone.sayHi = function() {
    // 方法
    alert('hi');
  };
  return clone;
}

var person = {
  //属性
  name: 'Nicholas',
  friends: ['Shelby', 'Court', 'Van'],
};

var anotherPerson = createAnother(person);
anotherPerson.sayHi();
alert(anotherPerson.name);

寄生组合式继承

【最理想的继承模式】

优点

  1. 高效率调用一次 SuperType 构造函数,因此避免了在 SuperType.prototype 上面创建不必要,多余的属性
  2. 原型链保持不变,可以正常使用istanceofisPrototypeOf()
function object(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

function inheritPrototype(subType, superType) {
  var prototype = object(superType.prototype); //创建对象
  prototype.constructor = subType; //增强对象
  subType.prototype = prototype; //指定对象
}

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

SuperType.prototype.sayName = function() {
  alert(this.name);
};

function SubType(name, age) {
  SuperType.call(this, name);

  this.age = age;
}

inheritPrototype(SubType, SuperType);

SubType.prototype.sayAge = function() {
  alert(this.age);
};

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

var instance2 = new SubType('Greg', 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27

参考文章

@yanyue404 yanyue404 changed the title js的继承模式 Javascript的继承模式 May 23, 2018
@yanyue404 yanyue404 changed the title Javascript的继承模式 Javascript的继承与原型链 Nov 21, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant