面向对象和原型链的用法(上)

原型链是 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 到底干了什么操作

  1. 创建了一个空对象
  2. 将空对象的原型(proto)指向构造函数的原型(prototype)
  3. 将空对象作为构造函数的上下文 (改变this的上下文作用域)
  4. 对构造函数进行返回逻辑的处理(基本类型,忽略自身的返回,返回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

如果想更深一步的了解,请参考博客
面向对象和原型链的用法(中)

面向对象和原型链的用法(下)

好了,一些简单的用法 就先讲到这里吧,如果有什么问题欢迎指出哦,一起进步,哈哈哈

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值