原型链是 proto 所有类型 都有原型链, 数字, 数组, 对象 都有, 只有函数 才有原型, prototype,然后原型一直往上面找 会找到Object, 然后在上面去找, 就只能找到 null, null是最大的元素
1.原型可以解决什么问题
可以共享对象的属性跟方法
2.谁有原型
函数拥有: prototype
对象拥有: proto 这个也称隐式原型
3. 对象属性或方法的查找顺序
3.1 现在本身的对象查找 -> 然后再构造函数中查找-> 然后再对象的原型中查找-> 然后对象的构造函数中查找 -> 当前原型的上一级构造函数中查找
结果看一下 下面的例子:
function Fun() {
this.run = '1'; // 第二次去构造函数中去找
}
Fun.prototype.run = "2"; // 第四次去构造原型上面去找
const obj = new Fun();
obj.run = "3"; //第一次 本身的属性上面找
obj.__proto__.run = "4"; // 第三次去对象原型去找
Object.prototype.run = "5"; // 第五次去父元素构造函数中去找
// **谁先显示就注释**的话 结果是: 3 1 4 2 5
console.log(obj.run);
4.原型链是什么?
4.1 就是把原型串联起来
4.2 原型链的顶端是 null
对我们来说,面向对象编程,大家在平时都已经很自然的用到了,比如说
// 我们可以这样子定义
let cat = new Object();
cat.color = "yellow";
cat.age =1;
cat.say = function () {
alert(this.color);
}
// 当然为了方便我们也可以 这样
let cat = {
color: "yellow",
age: 1,
say: function () {
alert(this.color);
}
};
// 这两个的用法是完全一样的
下面我们来介绍一下Object.defineProperty() 这个属性,它可以往我们的对象中添加属性
具体用法请参考 这篇文章
let cat = {
color: "yellow",
age: 1,
say: function () {
alert(this.color);
}
};
// Object.defineProperty 有四个值(configurable, enmurable, writable, value)
// configurable 是否可以修改属性特性 和 删除
// enmurable 是否可以进行枚举 就是是否可以进行(使用for...in或Object.keys())
// writable 属性的值是否可以被重写
// value 设置属性的值
Object.defineProperty(cat, "name", {
writable: false,
value: "小白"
})
alert(cat.name); // 结果是 "小白"
cat.name = " 小黑 ";
alert(cat.name); // 结果是 => "小白" writable属性是不允许修改的
// 如果想要定义多个属性 请使用Object.defineProperties() 用法跟Object.defineProperty() 一样的,只是可以多定义几个属性而已
好了,说到了这里,我们是否觉得上面的定义属性太麻烦了,所以我们就使用到了工厂函数
function createCat(name, age, color) {
let cat = new Object();
cat.name = name;
cat.age = age;
cat.color = color;
cat.say = function () {
alert(this.name);
}
return cat;
}
let cat1 = createCat("小白", 2, "白色");
cat1.say();
let cat2 = createCat("小黑", 1, "黑色");
cat2.say();
// 我们这个 工厂函数可以无数次的调用,解决了上面创建多个相似对象的问题,但是没有解决对象识别的问题(就是怎么知道一个对象的类型)
所以说 我们的构造函数出来了,它可以定义一些 Array 和 Object这样的原生构造函数,例如
function CreateCat(name, age, color) {
this.name = name;
this.age = age;
this.color = color;
this.say = function () {
alert(this.name);
}
}
let cat1 = new CreateCat("小白", 2, "白色");
let cat2 = new CreateCat("小黑", 1, "黑色");
// 这个与上面的对比,我们可以知道,没有在函数中创建显示的Object,直接把方法赋值给this对象,没有return返回对象
// 我们可以看到上面的实例cat1和cat2都有自己的constructor对象,分别都指向createCat
alert(cat1.constructor == CreateCat); // true
alert(cat2.constructor == CreateCat); // true
// 当然我们也可以通过 instanceOf关键字来判断,因为createCat本来就是Object的实例,所以返回的是true
alert(cat1 instanceof Object) // true
alert(cat1 instanceof CreateCat) // true
alert(cat2 instanceof Object) // true
alert(cat2 instanceof CreateCat) // true
我们这里简单的说一下 new 到底干了什么操作
- 创建了一个空对象
- 将空对象的原型(proto)指向构造函数的原型(prototype)
- 将空对象作为构造函数的上下文 (改变this的上下文作用域)
- 对构造函数进行返回逻辑的处理(基本类型,忽略自身的返回,返回this本身, 引用类型返回引用数据自己)
// 这个是手写一个new关键字的用法
function Fun(age, name) {
this.age = age;
this.name = name;
return {a: 18};
}
function create(fn, ...args) {
// 1.创建一个空对象
let obj = {}; // Object.create();
// 将空对象的原型指向构造函数的原型
Object.setPrototypeOf(obj, fn.prototype);
// 改变this的上下文作用域
let result = fn.apply(obj, args);
// 第四步 对构造函数进行返回的判断
return result instanceof Object ? result : obj;
}
console.log(create(Fun, 18, "py"));
但是构造函数有一个问题,就是每次创建一个实例都是独自的,那么我们用到的公共方法this.say() 每次都会创建 ,那么这样子会很消耗资源
所以我们可以把公共的方法单独的提出来
function CreateCat( name, age, color) {
this.name = name;
this.age = age;
this.color = color;
this.say = sayCommon
}
// 公共的方法
function sayCommon() {
alert(this.name);
}
let cat1 = new CreateCat( "小白", 2, "白色");
let cat2 = new CreateCat( "小黑", 1, "黑色");
cat1.say(); // 小白
cat2.say(); // 小黑
那么又存在一个问题了,如果函数的公共方法很多,那我们就要创建很多的全局函数,那么看起来不是很麻烦吗,所以我们渴望的原型链就出来了
function CreateCat() {
}
CreateCat.prototype.name = "小白";
CreateCat.prototype.age = 1;
CreateCat.prototype.color = "白色";
CreateCat.prototype.say = function () {
alert(this.name);
}
let cat1 = new CreateCat();
let cat2 = new CreateCat();
cat1.name = "小黑";
// 这个是为什么呢,因为cat1的实例被赋值了,它是先找到实例上有没有该属性,如果没有就去原型链上面去找,所以cat2 返回的是原型链上面的值
alert(cat1.name); // 来自于实例 =>小黑
alert(cat2.name); // 来自于原型 =>小白
alert(cat1.say == cat2.say); // true 这个可以说明cat1 和cat2 都是拿的原型链里面的同一个方法,它们的属性和方法是共享的
// 我们可以通过方法 isPrototypeOf() 方法来判断是否跟原型存在着联系
alert(CreateCat.prototype.isPrototypeOf(cat1)); // true
alert(CreateCat.prototype.isPrototypeOf(cat2)); // true
我们也可以通过delete 来删除实例
function CreateCat() {
}
CreateCat.prototype.name = "小白";
CreateCat.prototype.age = 1;
CreateCat.prototype.color = "白色";
CreateCat.prototype.say = function () {
alert(this.name);
}
let cat1 = new CreateCat();
let cat2 = new CreateCat();
// 通过 hasOwnProperty() 可以看到是不是该对象的实例,只有当cat1.name 被重写了 才会返回true
cat1.name = "小黑";
alert(cat1.name); // 来自于实例 =>小黑
alert(cat2.name); // 来自于原型 =>小白
alert(cat1.hasOwnProperty("name")); // true
alert(cat2.hasOwnProperty("name")); // false
// 现在我们通过delete 属性,删除了实例上的属性,那么它就从原型链上面去找了
delete cat1.name;
alert(cat1.name); // 删除实例属性之后,就来自于原形了 =>小白
alert(cat2.name); // 来自于原型 =>小白
alert(cat1.hasOwnProperty("name")); // false
alert(cat2.hasOwnProperty("name")); // false
如果想更深一步的了解,请参考博客
面向对象和原型链的用法(中)
面向对象和原型链的用法(下)
好了,一些简单的用法 就先讲到这里吧,如果有什么问题欢迎指出哦,一起进步,哈哈哈