博客样式改的差不多了,剩下的慢慢搞,得开始学习了

js大概是一年前学的东西了,当时跟着视频走感觉自己懂了,现在再回想发现还是模模糊糊的。于是查阅了一些教程,总算是将它给搞懂了。

从对象说起

我们知道,javascript中的一切都是对象,而我们需要一种方式将这些对象关联起来。在Java、C++等语言中,使用的是基于类来实现这一目的。但由于JS设计之初只是当做浏览器的脚本语言来设计,所以并没有引入类的概念。为了解决这一需求,设计者使用了原型这一概念来实现对象的创建、继承等一系列需求。

构造函数

在Java、C++中,是使用new关键字来调用类的构造函数创建一个实例对象。js也延续了这一做法,不过通过new命令调用的,直接就是构造函数(constructor)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//定义Student的构造函数
function Student(name,gender){
this.name = name;
this.gender = gender;
this.say = function(message){
console.log(message);
}
}
//通过构造函数来创建实例对象——构造函数中的this就是最后返回的实例对象
let kika = new Student("KIKA","male");
let kika2 = new kika.constructor();
console.log(kika2 instanceof Student); //true
console.log(kika2.constructor === Student); //true => new关键字后跟的就是构造函数
console.log(kika.name); //KIKA
kika.say("hello"); //hello

原型对象

此时,从构造函数中创建出来的实例对象都会带有一套创建时赋予的属性和方法,但是没有所有实例都共用的属性与方法(如所有的student都会有一个方法study()),在java中此操作可以使用类的静态属性/方法来实现。在js中,设计者为构造函数添加了prototype这一属性,于此同时,在prototype属性中也可以引用constructor属性来指向构造函数(循环了🤔),对于想要实现的共有属性与方法,都可以定义在这个prototype上。对于使用此构造函数创建的实例对象,都可以引用到原型对象上的属性或方法(当然这些方法可以被实例对象的属性或方法覆盖)

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

Dog.prototype.species = "犬科";
Dog.prototype.wang = function(){
console.log("汪汪汪");
}
var d = new Dog("dd");
console.log(d.name); //dd
console.log(d.species); //犬科
d.wang(); //汪汪汪

d.wang = function(){
console.log("汪!汪!汪!")
}
d.wang(); //汪!汪!汪! => 实例“继承”的方法被重写(也叫属性遮蔽)

console.log(Dog.prototype.constructor === Dog); //true => 原型对象上的constructor属性指向构造函数

原型链

通过上面的代码,可以看到实例对象确实可以引用到构造函数的原型对象上的属性或方法,而其具体的实现就是通过原型链

为了让实例对象引用到其构造函数的原型对象(prototype),每个实例对象都会被赋予一个属性__proto__(准确来说js中的每个对象都有此属性,null除外;因为每个对象都相当于Object的实例或间接实例),__proto__属性会指向实例对象构造函数的原型对象(prototype);并且在对实例对象进行属性或方法查询时,若实例对象上没有,则会沿着__proto__向上寻找(类似于变量的作用于);这个向上寻找会一直找到Object的原型对象null(null没有__proto__,寻找到这就结束了,没有找到就会返回undefined)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Dog(name){
this.name = name;
}

let d = new Dog("dd");
Dog.prototype.species = "犬科";
// 每个实例对象都会被赋予一个__proto__属性,指向其构造函数的原型对象,即d.__proto__ = Dog.prototype
console.log(d.__proto__ === Dog.prototype); //true
console.log(d.__proto__.species, d.species); //犬科 犬科
//实例对象没有的属性或方法会向上查询,而所有对象都来自于Object
Object.prototype.test = "test";
console.log(d.test); //test
console.log(d.__proto__); //会输出Dog的原型对象
console.log(d.__proto__.__proto__ === Object.prototype); //Dog的原型对象为Object的实例,所以Dog的原型对象的__proto__属性指向Object的原型对象
console.log(d.__proto__.__proto__.__proto__); //Object的原型对象的__proto__属性指向null => 输出null
console.log(d.__proto__.__proto__.test); //这个test就相当于对d.test进行查询的过程

总结

  • 实例对象可由构造函数配合new关键字产出
  • 每个构造函数都会有prototype属性,指向其原型对象
  • 原型对象中包含有构造函数
  • 每个对象(构造函数也是实例对象,js一切即对象;null为空对象除外,无属性)都会有__proto__属性,指向其构造函数的原型对象
  • 对象.__proto__.proto....这一整个链式调用/查询即我们所说的原型链
  • 在原型链上编程即通过Obj.prototype.xx = yy来改变

扩展

  • 关于显示原型与隐式原型:显示原型指的是函数的prototype属性;隐式原型指的是对象的__proto__

关于原型对象的调用

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
function Dog(name){
this.name = name;
}
let d = new Dog("dd");
let c = new Dog("cc");
// 使用prototype属性
Dog.prototype.test1 = "test1";
//使用Object.getPrototypeOf()函数——返回实例对象的__proto__,即构造函数的prototype
Object.getPrototypeOf(d).test2 = "test2";
console.log(d.test1); //test1
console.log(c.test2); //test2(d和c共用一个__proto__)

//改变原型对象
Dog.prototype = {
test2: "change"
}
console.log(d.test2); //test2 不会改变,因为其__proto__指向的是创建时的原型对象(即__proto__保存的是对原型对象的引用,将Dog.prototype改变引用对象后,实例的__proto__并不会改变)
let a = new Dog("aa");
console.log(a.test2); //change 此时创建实例对象时,原型对象已被改变,所以其索引的为改变后的

//通过Object.setPrototypeOf()函数设置实例对象的__proto__(也可以直接用__proto__设置)
Object.setPrototypeOf(d,{test2:"change from Object.setPrototypeOf()"});
console.log(d.test2); //change from Object.setPrototypeOf()
console.log(a.test2); //change
console.log(c.test2); //test2 =>此时三个实例的__proto__属性指向已经不是同一个对象了

参考

🤪感觉写的还是有些混乱,等头脑清晰了再来改改