1、原型
1.1 、原型的作用
为实现对象
之间共享属性
和方法
,允许对象通过原型
访问另一个对象的属性和方法
js
function fn() {
this.name = "tom";
}
fn.prototype = { age: 11 };
let obj = new fn();
console.log(obj.age);//11
js
function fn() {
this.name = "tom";
}
// fn.prototype = { age: 11 };
let obj = new fn();
obj.__proto__ = { age: 11 };
console.log(obj.age);
- 可以发现
fn.prototype
与obj.__proto__
是同一个对象
1.2、对象原型与函数原型
1.2.1、对象原型
- 对象中存在一个原型属性
[[Prototype]]
- 早期是无法访问这个
[[Prototype]]
- 后续一些浏览器实现了非标准的
__proto__
访问器 - 在 2015 年,
Object.setPrototypeOf
和Object.getPrototypeOf
被加入到标准中
1.2.2、函数原型
-
函数(构造函数)
中存在一个原型属性prototype
-
并且函数是一种特殊的对象,因此函数也具有原型属性
[[Prototype]]
-
prototype
中的属性直接共享给实例
1.2、对象的原型怎么来的?
F.prototype
是一个对象,new
操作符会使用它为新对象设置[[Prototype]]
。
- 可以简单在了解下
new的实现
js
function _new(fn, ...args) {
let obj = {};
obj.__proto__ = fn.prototype;
fn.apply(obj, args);
return obj;
}
1.3、原型链
当访问一个对象的属性或方法时,JavaScript 首先会在该对象本身查找,如果找不到,就会顺着 [[Prototype]]
所指的原型对象继续查找,以此类推,直到找到该属性或方法,或者到达原型链的末尾(即 [[Prototype]]
为 null
)。这种通过 [[Prototype]]
连接起来的层次结构就是原型链 。
js
function F(){}
const f = new F()
console.log(F.__proto__)
console.log(F.prototype.__proto__)
console.log(f.__proto__)
console.log(Function.prototype.__proto__)
console.log(Object.prototype.__proto__)
2、继承
了解原型与原型链的最终目的,就是为了实现继承
2.1、原型链继承
js
// 父类构造函数
function Father() {
this.name = "father";
}
Father.prototype = { a: 1 };
// 子类构造函数
function Son() {
this.age = 16;
}
// 子类的prototype指向父类的实例
Son.prototype = new Father();
var son = new Son();
console.log(son.name); // father
console.log(son.age); // 16
console.log(son.a); //1
优点
- 可以继承父类的私有属性和原型属性。
缺点
- 无法向父类构造函数传参。
2.2、盗用构造函数
js
function parent(name) {
this.name = name;
this.age = 12;
}
parent.prototype = { a: 1 };
function child(name) {
parent.call(this, name);
}
console.log(new child("tom"));
console.log(new child('tonm').a)//undefined
优点
- 在子类中可以给父类传参。
- 可以继承父类的私有属性。
缺点
- 不能继承父类的原型
2.3、原型链+盗用构造函数
js
function parent(name) {
this.name = name;
this.age = 12;
}
parent.prototype = { a: 1 };
function child(name) {
parent.call(this, name);
}
child.prototype = new parent();
console.log(new child("tom"));
console.log(new child("tom").a);
缺点
- 父类构造函数被调用两次
- 数据的冗余:数据即存在于私有属性,又存在prototype中
2.4、create继承
js
let parent = {
name: "parent",
friends: ["p1", "p2", "p3"],
getName: function () {
return this.name;
},
};
let personA = Object.create(parent);
personA.name = "tom";
personA.friends.push("jerry");
let personB = Object.create(parent);
personB.friends.push("lucy");
console.log(personA.name); // tom
console.log(personA.name === personA.getName()); // true
console.log(personB.name); // parent
console.log(personA.friends); // ["p1", "p2", "p3","jerry","lucy"]
console.log(personB.friends); // ["p1", "p2", "p3","jerry","lucy"]
缺点
- 原型为浅拷贝
2.5、盗用构造函数+create
js
function inheritPrototype(child, parent) {
let prototype = Object.create(parent.prototype); // 创建对象
child.prototype = prototype; // 赋值对象
}//通过这个函数,将父元素的Prototype移到子元素的Prototype上
function Parent(name) {
this.name = name;
this.friends = ["rose", "lily", "tom"];
}
Parent.prototype.sayName = function () {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
inheritPrototype(Child, Parent);
Child.prototype.sayAge = function () {
console.log(this.age);
};
let child1 = new Child("yhd", 23);
child1.sayAge(); // 23
child1.sayName(); // yhd
child1.friends.push("jack");
console.log(child1.friends); // ["rose", "lily", "tom", "jack"]
let child2 = new Child("yl", 22);
child2.sayAge(); // 22
child2.sayName(); // yl
console.log(child2.friends); // ["rose", "lily", "tom"]
- 子类继承父类的私有属性和原型属性。
- 子类可以向父类传递参数。
- 继承后没有冗余属性。
2.5、extends(最完美的继承)
js
class Person {
constructor(name) {
this.name = name
}
// 原型方法
// 即 Person.prototype.getName = function() { }
// 下面可以简写为 getName() {...}
getName = function () {
console.log('Person:', this.name)
}
}
class Gamer extends Person {
constructor(name, age) {
// 子类中存在构造函数,则需要在使用"this"之前首先调用 super()。
super(name)
this.age = age
}
}
const asuna = new Gamer('Asuna', 20)
asuna.getName() // 成功访问到父类的方法
3、class
3.1、function与class区别
js
// 用纯函数重写 class User
// 1. 创建构造器函数
function User(name) {
this.name = name;
}
// 函数的原型(prototype)默认具有 "constructor" 属性,
// 所以,我们不需要创建它
// 2. 将方法添加到原型
User.prototype.sayHi = function() {
alert(this.name);
};
// 用法:
let user = new User("John");
user.sayHi();
js
class User {
constructor(name) {
this.name = name;
}
sayHi() {
alert(this.name);
}
}
// 用法:
let user = new User("John");
user.sayHi();