js原型链
js主要由对象主要分为原型对象、构造函数对象、实例对象。
new原理
使用new可以根据构造函数创建对象,如果构造函数返回非对象值,则创建空对象;反之使用构造函数返回的值
function f1() {
this.a = "hello"
return 1
}
function f2() {
this.a = "hello"
return {
b:"world"
}
}
console.log(new f1()); // {a: "hello"}
console.log(new f2()); // {b: "world"}
原理
new一个新对象的原理:创建空对象,改变该对象prototype
,调用构造函数
// instanceof 判断该实例是否属于该对象
function fn() {
this.a = "123";
}
var p1 = new fn;
console.log(p1);
console.log(p1 instanceof fn); // 判断p1是否是fn的实例
//等价于
var p2 = new Object(); // 创建一个空对象
p2.__proto__ = fn.prototype; // 将对象原型指向构造函数的原型
fn.call(p2); // 将实例对象赋值给构造函数
console.log(p2);
console.log(p2 instanceof fn); // 判断p2是否是fn的实例
constructor构造函数
构造函数对象:构造函数是用来初始化对象用的,构造函数对象有prototype
指向原型
创建对象-》使用构造函数初始化对象f1
使用构造函数创建,创建prototype
原型=》在原型上添加一个构造函数属性constructor
function f1() {
this.a = 1;
}
var p = new f1;
console.log(p.constructor); // f1() {this.a = 1;}
console.log(p.__proto__.constructor); // f1() {this.a = 1;}
console.log(p.constructor === p.__proto__.constructor); // true
实例上的constructor
是从原型上继承来的,所以实例和原型上的constructor
指向一个对象
原型对象介绍
原型对象:创建实例对象时,构造函数先创建原型对象,然后原型对象是每个实例对象的父对象,原型对象上的属性和方法,实例对象都可以访问。
function f1() {
this.a = 1;
}
var p = new f1;
console.log(p.__proto__); // 实例通过__proto__访问原型对象
console.log(f1.prototype); // 构造函数使用prototype访问原型对象
实例访问原型使用__proto__
方法,构造函数访问原型使用prototype
方法
原型链
通过简单函数我们可以画出他的原型链
function Fn(){
this.a = "123";
}
var p = new Fn;
console.log(p); // {a: "123"}
console.log(p.__proto__);
// 从实例对象开始,向下找原型
console.log(p); // {a: "123"}
console.log(p.__proto__); // Fn.prototype
console.log(p.__proto__.__proto__); // Object.prototype
console.log(p.__proto__.__proto__.__proto__); // null
// 构造函数
console.log(p.constructor); // Fn 从原型继承过来的
console.log(p.__proto__.constructor); // Fn
console.log(p.__proto__.__proto__.constructor); // Object
// null 没有构造函数
// Fn 和 Object
// 构造函数
console.log(p.__proto__.constructor.constructor); // Function
console.log(p.__proto__.__proto__.constructor.constructor); // Function
// 将FN,Object看成实例,去找他们的原型
console.log(p.__proto__.constructor.__proto__); // Function.prototype
console.log(p.__proto__.__proto__.constructor.__proto__); // Function.prototype
// Function的原型
console.log(p.__proto__.constructor.constructor.prototype); // Function.prototype
// Function.prototype的构造函数
console.log(p.__proto__.constructor.constructor.prototype.constructor); // constructor
// 将Function.prototype作为实例去找他的原型
console.log(p.__proto__.constructor.constructor.prototype.__proto__); // Object.prototype
结论:
- 实例向下一直找原型,最终指向Object.prototype,而这个原型指向null。也就是说在Object.prototype上定义的属性和方法任何对象都可以使用
- 实例和其原型的构造函数是Fn,Object.prototype的构造函数是Object,而他两和Function.prototype都是Function构造,构造函数最终指向Function
- 从Function上找原型找到Function.prototype,而他的原型指向Object.prototype,这最终也正式了所有对象原型最终指向Object.prototype
prototype属性的作用
prototype代表对象的原型,相当于对象的父类。我们可以通过将属性和方法或者另一个对象挂载到该对象上,实现js的继承。注意:prototype上的属性和方法都会成为对象的共有属性
// 在原型上添加属性和方法
var Person = function () {
this.name = "alex";
};
Person.prototype.age = 18;
Person.prototype.showName = function () {
console.log(this.name); // this 指向对象Obj
};
var p1 = new Person;
var p2 = new Person(); // new对象时,如果没有参数,加不加括号都可以,结果都一样
console.log(p1); //Person{name: "alex"}
console.log(p2); // Person{name: "alex"}
// p1和p1都共享一个age
p2.age = 22; // 在实例上创建age属性
console.log(p1.age); // 18 p1实例上没有age属性,原型上有age属性
console.log(p2.age); // 22 p2实例上有age属性,直接取实例上age属性
// p1和p1都调用原型上的prototype属性
p1.showName(); // alex
p2.showName(); // alex
constructor属性
构造函数的属性和作用,构造函数用来初始化函数,初始化原型对象时,在原型对象上添加constructor用来指向构造函数。
Fn原型访问构造函数:Fn.prototype.constructor
f1实例访问构造函数:f1.constructor
实例上的constructor
实际上是从其原型对象上找到的。
var Person = function () {
this.name = "alex";
};
// Person 就是构造函数
var p = new Person();
console.log(Person.prototype.constructor); // Person对象
console.log(p.constructor); // Person对象
console.log(Person.prototype.constructor === p.constructor); // true
案例
对象拥有数组的方法
相当于函数的继承,js中继承函数的方法
// js让对象继承数组
function Fruit() {
}
// 继承Array数组类
Fruit.prototype = Array.prototype; // 修改原型对象,将Fruit的原型指向Array的原型
Fruit.prototype.constructor = Fruit; // 将构造函数的原型指向Fruit
// 因为修改原型后,原型上的构造函数就改变为Array,即修改构造函数指向Fruit。即使用Fruit来初始化函数
var apple = new Fruit();
apple.push("big","middle","small","tiny"); // 可以使用数组的push方法,将元素加入对象中
// 只有数组对象才有forEach方法,作用是遍历数组
apple.forEach(element => {
console.log(element);
});
一般继承对象的方法,先设置原型,再修改构造函数的指向。