javaScript 深入原型和原型链

原型和原型链一直是javaScript的基础和必考题之一。

关于javaScript的继承解释,有兴趣的可以跳转,阮一峰老师的文章:Javascript继承机制的设计思想


原型和原型链

简单来一个字面定义变量

var a = '1';
console.log(a);// 1

如果要查看变量 a 的字符串长度,都会想到使用 a.length ,但是其实变量 a 并没有属性 length 。当我们去访问变量 a 中的 length 属性时,它其实会先经历以下阶段。

  1. 判断该变量中是否有 length 属性。
  2. 如果没有,则通过__proto__属性去其原型中查找。
  3. 如果还没有找到,就重复第 2 步,直到找到该属性或者到原型链为止。
var a = '1';
console.log(a.__proto__);
//String {"", length: 0, constructor: ƒ, anchor: ƒ, big: ƒ, blink: ƒ, …}

a.__proto__访问到的是String类型的基础对象,它包含了字符串操作的所有API。String类型的基础对象称为变量 a 的 原型

通俗来说,就是儿子(实例对象)要某个属性,然后看看自己有没有。如果没有的话,就通过__proto__(指针)找到爸爸(实例对象的原型),然后问他有没有,以此类推,找爷爷、爷爷的爷爷。。。直到找到或者到头为止。

爸爸称为儿子的 原型,而把整个家族串起来的,就叫 原型链 ,原型链最后是 NULL

__proto__
__proto__
__proto__
变量 a
String 基础对象
String 基础对象的原型
NULL

__proto__属性

__proto__是实例对象的指针,指向其原型所在的内存堆。

上诉内容中, 实例对象用来指向其原型的属性__proto__,并不是实例对象的内部属性,而是通过原型链,在基础对象的原型中得到的。

var a = '1';
a.hasOwnProperty('__proto__');// false
a.__proto__.__proto__.hasOwnProperty('__proto__');// true

通俗来说,如果儿子用__proto__的话,会找到爸爸。爸爸用__proto__的话,会找到爷爷。而__proto__这个神器是存放在大当家(基础对象的原型)手上的,原型链上的人都可以随意使用。

有点意思,一家人全靠一个神器保持着沟通,那么,老夫有一个大胆的想法。如果神器丢失,或者神器带他们去了错误的地方,那。。。

var a = '1';
console.log(a.length);// 1
a.__proto__ = null;
console.log(a.length);// 1

var b = {};
console.log(b.__proto__);// {constructor: ƒ, __defineGetter__: ƒ, …}
b.__proto__ = null;
console.log(b.__proto__);// undefined

测试了一下发现,好像 原始数据类型 无法修改__proto__,而 引用数据类型 可以修改__proto__,结果就是让引用数据类型变成了孤儿,或者变成别人的儿子。

咦,这样的操作如果投入到实际开发中,那岂不是有某种代码风格?

实不其然。在实际开发中,禁止修改__proto__,因为有个别的平台不支持这样的操作,这样会导致代码变得无法跨平台使用,且破坏常态的原型链结构,违反了开发时候的正向思路(你永远不知道这个变量现在是谁家的小孩)。再而言之,JavaScript引擎对于对象属性的设置和获取有着重优化,修改对象的内部结构会导致优化失效。

说说new关键字都做了什么。

准备一份简单的构造函数创建实例对象。

function createPeople(age,sex){
  this.age = age || '未知';
  this.sex = sex || '未知';
}
let he = new createPeople(22,'男'); //age:22 sex:男
let she = new createPeople(); //age:未知 sex:未知

以下是伪代码,只是为了更好的表达new关键字做的事情。

//1.创建一个新的对象
var  newObj = {};
//2.将createPeople函数的自身原型对象作为newObj的原型
newObj.__proto__ = createPeople.prototype;
//3.将createPeople的this指向newObj,再执行createPeople,为newObj初始化
createPeople.apply(newObj,arguments);
//4.判断:如果createPeople存在一个对象的返回值,则返回该对象,否则则返回newObj(有问题,先别记)
if(createPeople.exist(return) && typeof return == 'object'){
  return object;
}else{
  return newObj;
}

注意! 关于第四点的判断,探究一下吧:
首先先认为 typeofobject 的值,都会替代掉。
如下方

typeof {}; //"object"
typeof []; //"object"
typeof null; //"object"

把上诉类型拿来操作一下吧

function createPeople1(age,sex){
  this.age = age || '未知';
  this.sex = sex || '未知';
  return {}
}
let obj1 = new createPeople1(22,'男'); // {}
//-------------------------------------
function createPeople2(age,sex){
  this.age = age || '未知';
  this.sex = sex || '未知';
  return []
}
let obj2 = new createPeople2(22,'男'); // []
//-------------------------------------
function createPeople3(age,sex){
  this.age = age || '未知';
  this.sex = sex || '未知';
  return null
}
let obj3 = new createPeople3(22,'男'); // {age: 22, sex: "男"}
//-------------------------------------
function createPeople4(age,sex){
  this.age = age || '未知';
  this.sex = sex || '未知';
  return '11'
}
let obj4 = new createPeople4(22,'男'); // {age: 22, sex: "男"}

根据上面的代码。发现一个问题。null 在typeof中确实是显示为object,但是实例对象却不是 null 值。

说明一下,null 在JS中是一个完全为空的对象。但它却和数字、字符串一样,是原始数据类型。 而构造函数其实并不是以返回类型是否为对象进行判断,而是判断返回类型是否为引用类型。

所以,应该改一下第四点的概念:

判断:如果createPeople存在一个 引用类型 的返回值,则返回该对象,否则则返回newObj

constructor属性

constructor 属性返回对创建此对象的数组函数的引用。

所有的javaScript都包含constructor属性。实例对象可以通过constructor属性访问到创建他们的构造函数。

function a(){
console.log(11);
}
var b = new a();
console.log(b.constructor);// ƒ a(){console.log(11);}
b.constructor();// 11

prototype属性

prototype是函数才具有的一个内部属性,和__proto__属性一样,它指向其原型。

prototype和__proto_的区别:

function a(){};
// 1.prototype是函数的内部属性,而不是通过原型链获取得到的。
a.hasOwnProperty('prototype');// true
// 2.prototype不存在于原型链中,它指向的原型发生改变,不会导致原型链查询过程中发生改变。
a.__proto__.b = 1;
a.prototype = '';
console.log(a.b);//1

函数的prototype属性默认指向的是一个空的对象。这个空对象中带有指向自身函数的constructor属性。

function a(){
 console.log(111);
}
console.log(a.prototype.constructor);// ƒ a(){console.log(111);}
a.prototype.constructor();// 111

基础都是由解答问题进行巩固的

综上,抛出问题:
1.为什么变量 a 跟着__proto__属性查找原型时候,是找到String.prototype,而不是String ?

var a = '1';
a.__proto__ === String.prototype; // true

我们先来看看。String 是什么?,String.prototype是什么?

console.log(String);// ƒ String() { [native code] }
conSole.log(String.prototype);// String {"", length: 0, constructor: ƒ, …}

String是一个函数。而String.prototype指向的才是包含各种API的String基础对象。

那么,为什么呢?为什么不直接使用String作为基础对象就好了呢?

因为字面量定义和通过new定义对象,除了性能问题之外,还有是否执行构造函数的区别而已。

那么,也许我们可以从new的执行过程中找到答案。

回顾一下new执行流程的第二步。

//2.将createPeople函数的自身原型对象作为newObj的原型
newObj.__proto__ = createPeople.prototype;

结果显而易见,因为new会将构造函数的prototype作为实例对象的__proto__指向原型。

下一个。

2.what?

var a = {};
a.__proto__ === Object.prototype;// true
function b(){};
b.__proto__ === Function.prototype;// true
b.__proto__.__proto__ === Object.prototype;// true

莫非 Function.prototype 是先从 Object 创建出来的

Function.prototype.__proto__.constructor;// ƒ Object() { [native code] }

嗯,结果显而易见。

最后再来看看一个原型链常见的面试题的问题。

问:什么情况下,proto 和 prototype 的指向是同一个原型?
答:Function.__proto === Function.prototype。

后记一下

为什么在实例化的对象中有包含 constructor这个内部属性,但是在for循环的时候,没有获取到?
因为它是不可枚举的。

<div v-if="作者有写相应链接">
	<a href="作者写的链接">超多玩法的Object</a>
</div>

跪求点评中。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值