js原型链和继承


面向对象

1、原则:开放封闭原则
1、对于扩展是开放的(open for extension)。当应用的需求改变时,可以对模块进行扩展,使其满足改变的新行为。
2、 对于修改是关闭的(close for modification)。对模块进行扩展时,不必改变模块的源代码。

2、三要素:封装,继承,多态
1、封装:把很多个属性和方法封装到一个对象当中。2、继承:继承一个对象的所有的属性和方法。3、多态:不同的对象对同一个方法有不同的表现

new操作

var object=new Object()时,都发生了什么?

1、创建了一个空对象作为this
2、this.proto指向构造函数的prototype
3、运行构造函数
4、返回this(如果构造函数里没有return,若存在return引用类型,则返回return后的值)

1
2
3
4
5
6
7
8
9
10
var Foo=function(){
this.name="foo";
}
var foo=new Foo();
console.log(foo); //{ name: 'foo' },console对象时,先展示对象的构造函数
//发生了什么?
1、创建了{},this指向{}
2、{}._proto_指向了Foo.prototype
3、执行构造函数,{}.name="foo"
4、返回{}

原型链

解释Person、 prototype、proto、p、constructor之间的关联。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Person(name){     // 不能用箭头函数,不然不能new
this.name = name;
}
Person.prototype.sayName = function(){
console.log('My name is :' + this.name);
}
var p = new Person("Ethan") // 这个new出来的是一个对象
p instanceof Person // true
p instanceof Object // true

//Person是构造函数,p是Person的实例
//Person.prototype是Person构造函数的原型对象
//Person.prototype里的constructor属性指向Person
Person.prototype.constructor===Person //true
//_proto_是p实例里的属性,指向Person.prototype
p._proto_===Person.prototype //true
p._proto_.__proto__ === Object.prototype // true
p._proto_.__proto__.__proto__ === null // true

// Person是Function的实例
Person.__proto__ === Function.prototype
Person.__proto__.__proto__ === Object.prototype

上例中,对对象 p可以这样调用 p.toString()。toString是哪里来的? 画出原型图?并解释什么是原型链。

  1. 原型链:对象的属性和方法,有可能是定义在自身,也有可能是定义在它的原型对象。由于原型对象本身也是对象,也有自己的原型,依次类推,形成了一条原型链(prototype chain)。
  2. 如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype,即Object构造函数的prototype属性指向的那个对象。Object.prototype对象的原型为null,而null没有原型对象
  3. 「原型链」的作用是,读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined

数值,字符串,布尔值,函数,对象,数组的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let num = new Number() // Number{0}
let str = new String() // String {'', length: 0}
let boolean = new Boolean() // Boolean {false}
let fn = new Function()
let obj = new Object() // {}
let arr = new Array() // []
num.__proto__ === Number.prototype
Number.prototype.__proto__ === Object.prototype

str.__proto__ === String.prototype
String.prototype.__proto__ === Object.prototype

boolean.__proto__ === Boolean.prototype
Boolean.prototype.__proto__ === Object.prototype

fn.__proto__ === Function.prototype
Function.prototype.__proto__ === Object.prototype

obj.__proto__ === Object.prototype
Object.prototype.__proto__ === null

arr.__proto__ === Array.prototype
Array.prototype.__proto__ === Object.prototype
// 所以所有类型都有原型,一切皆对象

继承

继承有什么作用?

1、子类拥有父类的属性和方法,不需要重复写代码,修改时也只需修改一份代码
2、可以重写和扩展父类的属性和代码,又不影响父类本身

下面两种写法有什么区别?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//方法1
function People(name, sex){
this.name = name;
this.sex = sex;
this.printName = function(){
console.log(this.name);
}
}
var p1 = new People('name1', 2)

//方法2
function Person(name, sex){
this.name = name;
this.sex = sex;
}

Person.prototype.printName = function(){
console.log(this.name);
}
var p1 = new Person('Ethan', 27);
//方法一:将 printName 方法定义在构造函数 People 内,每次new一个实例出来,都会先声明一个函数,
//把函数放入printName属性中,虽然不会报错,但是每次声明的都是一样的函数,增加了代码量。

//方法二:将 printName 方法定义在 Person 的原型对象内,这样new一个实例出来,
//即使内部没有定义,还是能够通过原型链得到此方法,节约了内存。

Object.create 有什么作用?兼容性如何?

1、Object.create() 方法创建一个拥有指定原型和若干个指定属性的对象。
Object.create(proto, [ propertiesObject ]):第一个参数必选,是一个对象的原型,第二个开始参数可选,为属性对象
2、Object.create是 ES5 中规定的,IE9 以下无效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function grandfather(name) {
this.name = name;
};

grandfather.prototype.say = function() {
console.log('My name is ' + this.name);
};

function father(name) {
grandfather.call(this,name); //调用grandfather,并把函数内的this替换了
};

father.prototype = Object.create(grandfather.prototype);
//把father.prototype._proto_指向了grandfather.prototype,fater.prototype没有定义的原型方法了
// 此时的father.prototype.constructor ==== grandfather
father.prototype.constructor = father; //设置father.prototype.constructor
// 重新定义fater.prototype上的原型方法了
father.prototype.say2 = function() {
console.log(111)
}

var p1 = new father('Ethan');

Object.getPrototypeOf() 获取指定对象的原型

1
2
3
let a = {a:1}
let b = Object.getPrototypeOf(a)
a.__proto__ === b // true

分别用2种方式进行继承:Object.create, proto

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function grandFather(){}
grandFather.prototype.canRead = true
function father() {}
father.prototype.canWrite = true
// 1、__proto__
father.prototype.__proto__ = grandFather.prototype
father.prototype.constructor === father // true

// 2、Obect.create()
father.prototype = Object.create(grandFather.prototype)
// 实际father.prototype.__proto__ = grandFather.prototype
// father.prototype.constructor = grandFather
father.prototype.constructor === grandFather // true
father.prototype.constructor = father
let a = new father()
a.canWrite === true
a.canRead === true

hasOwnProperty有什么作用? 如何使用?

hasOwnProperty 是 Object.prototype 的一个方法,可以判断一个对象是否包含自定义属性而不是原型链的属性,hasOwnProperty 是 JavaScript 中唯一一个处理属性但是不查找原型链的函数。

1
2
3
4
5
function P(name){this.name=name;}
P.prototype.say=function(){console.log(this.name);}
var obj=new P("Ethan");
obj. hasOwnProperty("name"); //true
obj. hasOwnProperty("say"); //false

如下代码中call的作用是什么?

1
2
3
4
5
6
7
8
9
function Person(name, sex){
this.name = name;
this.sex = sex;
}
function Male(name, sex, age){
Person.call(this, name, sex); //这里的 call 有什么作用
this.age = age;
}
//call的作用:调用Person函数,并将Male的this传入,并且传入name和sex两个参数

继承的几种方式

1、借助构造函数

1
2
3
4
5
6
7
8
9
10
 function Parent1() {
this.name = 'parent1 的属性';
}
function Child1() {
Parent1.call(this); //【重要】此处用 call 或 apply 都行:改变 this 的指向
this.type = 'child1 的属性';
}
console.log(new Child1);

// 缺点:这种方式,虽然改变了 this 的指向,但是,Child1 无法继承 Parent1 的原型。

2、通过原型链实现继承

1
2
3
4
5
6
7
8
9
function Parent() {
this.name = 'Parent 的属性';
}
function Child() {
this.type = 'Child 的属性';
}
Child.prototype = new Parent(); //【重要】
console.log(new Child());
// 这种继承方式,Child可以继承Parent的原型,但有个缺点:如果修改child1实例的name属性,child2实例中的name属性也会跟着改变。

3、利用Object.create(A)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Person (name, age) {
this.name = name,
this.age = age
}
Person.prototype.setAge = function () {
console.log("111")
}
function Student (name, age, price) {
Person.call(this, name, age)
this.price = price
}
Student.prototype = Object.create(Person.prototype)//核心代码
Student.prototype.constructor = Student//核心代码
// 因为此时的Student.prototype被重新赋值了,所以重新定义原型链方法
Student.prototype.say2 = function () {}
var s1 = new Student('Tom', 20, 15000)

4、class关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Person {
//构造方法
constructor(name, age) {
this.name = name
this.age = age
}
//原型上的方法
showName () {
console.log("调用父类的方法")
console.log(this.name, this.age);
}
}
let p1 = new Person('kobe', 39)
//定义一个子类
class Student extends Person {
constructor(name, age, salary) {
//通过super调用父类的构造方法,相当于Person.call(name, age)
super(name, age)
this.salary = salary
}
//在子类原型上的方法
showName () {
console.log(this.name, this.age, this.salary);
}
}
let s1 = new Student('wade', 38, 1000000000)
s1.showName()