资讯专栏INFORMATION COLUMN

浅谈:快速理解JS的原型与原型链

ZoomQuiet / 3018人阅读

摘要:中有基本类型和复杂类型的区分。原型与原型链这里,就引入了两个新的概念。原型对象就是用来存放声明对象中共有的那部分属性。而原型对象自身也是一个对象,它也有自己的原型对象。这样层层上溯,就形成了一个类似链表的结构,这就是原型链。

JavaScript中有基本类型和复杂类型的区分。

当我们在声明一个基本类型时:

var n1= 1;
console.log(n1);
//1

这时我们可以用Number方法将1包装为对象,即声明一个对象1。

var n2= new Number(1);
console.log(n2);
//Number {1}
//[[PrimitiveValue]]:1

//△__proto__: Number

//constructor: ƒ Number()
//toExponential: ƒ toExponential()
//toFixed: ƒ toFixed()
//toLocaleString: ƒ toLocaleString()
//toPrecision: ƒ toPrecision()
//toString: ƒ toString()
//valueOf: ƒ valueOf()
//__proto__: Object

n2的PrimitiveValue(初始值)为1,其实此时它就是一个hash
此时对象n2现在有很多方法,可以调用对应各自的函数。

但是,我们发现有一点:在我们直接声明基本数据类型n1时,也可以使用这些方法调用函数。
比如toString方法:

var n1= 1;
n1.toString();
//"1"

这就要涉及到JavaScript的发明历史。在设计之初,它被要求:“长得像” Java
当时的Java声明一个对象,就是如此:

var n= new Number(1)

设计师在设计的同时,为了简便快捷,也制定了我们最常用的声明方式:

var n= 1

当然,这种方法肯定被JavaScript程序员所喜爱,第一种方法几乎没有人用。
但是有一个问题,如果直接声明n,那么它就是一个数字。而基本数据类型是没有属性的。
这时候该如何调取各种方法呢?

声明一个临时对象,我们暂将它称为temp
比如在对n进行toString方法引用时,会声明一个临时对象,对n进行复杂封装,将其变为对象。

var n= 1;
n.toString();

其实是:

var temp= new Number(n);
temp.toString();
//1

将temp.toString的值赋予n.toString。在操作结束后,临时对象将从内存中抹除

共用属性

number,string,boolean,object都有一些共同的方法,比如toStringvalueof等等。为了避免重复声明和内存浪费,这些方法“归纳”与一个共用属性之中。

JavaScript在声明一个对象后,并不是先默认复制一遍共用属性在内存中的存放地址,再在引用时调取对应的函数。而是:

在声明对象时,就存入一个隐藏的属性:__proto__。该属性,对应其共用属性。

var obj= {
  name: "Jack",
  age: 18
};
console.log(obj);
//△{name:"Jack", age:18}
  //age: 18
  //name: "Jack"
  //△__proto__: Object
    //constructor: ƒ Object()
    //hasOwnProperty: ƒ hasOwnProperty()
    //isPrototypeOf: ƒ isPrototypeOf()
    //propertyIsEnumerable: ƒ propertyIsEnumerable()
    //toLocaleString: ƒ toLocaleString()
    //toString: ƒ toString()
    //valueOf: ƒ valueOf()
    //__defineGetter__: ƒ __defineGetter__()
    //__defineSetter__: ƒ __defineSetter__()
    //__lookupGetter__: ƒ __lookupGetter__()
    //__lookupSetter__: ƒ __lookupSetter__()
    //get __proto__: ƒ __proto__()
    //set __proto__: ƒ __proto__()

那我们在调用toString方法时:
JavaScript首先会检查数据类型是否是对象;若不是,则包装为对象作临时的转换。
然后,在检查对象中是否有toString这个属性;若没有,才进入共用属性进行检查

两个空对象是不相等的,但他们的共有属性是相等的。

var obj1= {};
console.log(obj1);
//{}
var obj2= new Object();
console.log(obj2);
//{}
obj1 === obj2;
//false
obj1.__proto__ === obj2.__proto__;
//true

但是,当我们声明一个number为对象时,它就有自己区别于普通对象的独特的共有属性。
比如toFixedtoExponential等等。
那么它的__proto__属性就对应自己独有的共同属性,在共同属性中还有另一个隐藏属性__proto__对应一般对象的共有属性。这样,number类型的对象就可以调用所有的函数。

原型与原型链

这里,就引入了两个新的概念。
那么,这个共有属性,就被称为原型(对象)。原型对象就是用来存放声明对象中共有的那部分属性。
JavaScript中所有的对象都可以继承其原型对象的属性。
而原型对象自身也是一个对象,它也有自己的原型对象。这样层层上溯,就形成了一个类似链表的结构,这就是原型链

为了避免对原型对象在没有被使用时被内存清理,JavaScript通过prototype来默认引用。

Object.prototype

Object.prototype;

//constructor: ƒ Object()
//hasOwnProperty: ƒ hasOwnProperty()
//isPrototypeOf: ƒ isPrototypeOf()
//propertyIsEnumerable: ƒ propertyIsEnumerable()
//toLocaleString: ƒ toLocaleString()
//toString: ƒ toString()
//valueOf: ƒ valueOf()
//__defineGetter__: ƒ __defineGetter__()
//__defineSetter__: ƒ __defineSetter__()
//__lookupGetter__: ƒ __lookupGetter__()
//__lookupSetter__: ƒ __lookupSetter__()
//get __proto__: ƒ __proto__()
//set __proto__: ƒ __proto__()

Number.prototype

Number.prototype;

//constructor: ƒ Number()
//toExponential: ƒ toExponential()
//toFixed: ƒ toFixed()
//toLocaleString: ƒ toLocaleString()
//toPrecision: ƒ toPrecision()
//toString: ƒ toString()
//valueOf: ƒ valueOf()
//__proto__: Object
//[[PrimitiveValue]]: 0

如此,还有:
String.prototype
Boolean.prototype
等等。

Object.prototype.__proto__ === null

我们制作一个简单的示意图,以便更直观地理解这些概念。

那么我们可以得到关系:

var obj= {};
obj.__proto__ === Object.prototype;
//true

var n= new Number(1);
n.__proto__ === Number.prototype;
//true
n.__proto__.__proto__ === Object.prototype;
//true
总结起来,得到的结论就是:

Object.prototypeObject的共用属性

obj.__proto__Object的共用属性的引用

同理,对于StringBooleanSymbolNumber也是如此



文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/100235.html

相关文章

  • 浅谈javascript原型原型

    摘要:在中,主要有两种创建对象的方法分别是对象字面量以及调用构造函数对象字面量调用构造函数其实上述两种创建对象的方法,本质上是一样的,都是引擎调用对象的构造函数来新建出一个对象。 原型与原型链是javascript里面最最核心的内容,如果不能理解它们之间的存在关系的话,那么我们是不能理解这门语言的。 在JS中,主要有两种创建对象的方法, 分别是对象字面量以及调用构造函数 //对象字面量 va...

    includecmath 评论0 收藏0
  • 浅谈JavaScript原型原型

    摘要:在讲原型之前,先简单介绍一下几个概念构造函数例如上述代码创建的函数就被称为构造函数。同一个构造函数实例化得到的多个对象具有相同的原型对象,所以经常使用原型对象来实现继承。实例对象通过操作构造函数所创建的对象是实例对象。 对于很多前端开发者而言,JavaScript的原型实在是很让人头疼,所以我这边就整理了一下自己对应原型的一点理解,分享给大家,供交流使用 原型 说起原型,那就不得不说p...

    Zoom 评论0 收藏0
  • 浅谈JavaScript原型原型

    摘要:在讲原型之前,先简单介绍一下几个概念构造函数例如上述代码创建的函数就被称为构造函数。同一个构造函数实例化得到的多个对象具有相同的原型对象,所以经常使用原型对象来实现继承。实例对象通过操作构造函数所创建的对象是实例对象。 对于很多前端开发者而言,JavaScript的原型实在是很让人头疼,所以我这边就整理了一下自己对应原型的一点理解,分享给大家,供交流使用 原型 说起原型,那就不得不说p...

    Yang_River 评论0 收藏0
  • js面向对象浅谈(二)

    摘要:我们通过这个构造函数为原型对象添加其他方法和属性。这个属性存在与实例与构造函数的原型对象上直接,而不存在于实例与构造函数之间。李小花班花张全蛋张全蛋李小花李小花我们在遍历对象的的属性的时候,经常需要判断属性是否来自于对象的原型还是属性。 引言 上面说了创建对象有字面量方式和工厂模式还有构造函数模式,结果发现他们都各自有缺点,所以下面再给大家介绍几种创建对象的方式,争取能找到一种无痛的模...

    Yuanf 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<