前言
📫 大家好,我是南木元元,热爱技术和分享,欢迎大家交流,一起学习进步!
🍅 个人主页:南木元元
在JavaScript中,原型和原型链是非常重要的知识点,只有理解了它们,才能更好地理解js。
目录
构造函数
创建对象有两种方式:一种是最常见的对象字面量,一种就是通过构造函数来创建。
1.对象字面量
// 对象字面量 let person = { name: "南木元元", age: 22, species: "人类", greet: function() { console.log("Hello!"); } };
2.构造函数
// 构造函数创建对象 function Person(name, age) { this.name = name; this.age = age; this.species = '人类'; this.greet = function() { console.log("Hello!"); }; } let person1 = new Person("南木元元", 22);
构造函数和普通函数本质上没什么区别,只不过使用了new关键字创建对象的函数,被叫做了构造函数。构造函数的首字母一般是大写,用以区分普通函数。任何函数只要使用 new 操作符调用就是构造函数,而不使用 new 操作符调用的函数就是普通函数。
// 作为构造函数 let person1 = new Person("南木元元", 22); person1.greet() //Hello! // 作为函数调用 Person("yuanyuan", 18) window.greet() //Hello!
什么是原型
《javascript高级程序设计》中对原型的描述:
每个函数都会创建一个 prototype 属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。实际上,这个对象就是通过调用构造函数创建的对象的原型。使用原型对象的好处是,在它上面定义的属性和方法可以被对象实例共享。
简单来讲,原型就是一个对象,可以实现对象的属性和方法的继承。
原型对象和构造函数关系
在JavaScript中,每当定义一个函数数据类型(普通函数、类)时候,都会天生自带一个prototype属性,这个属性指向一个对象,这个对象就是此函数的原型对象。
当函数经过new调用时,这个函数就成为了构造函数,返回一个新的实例对象。
这个实例对象有一个__proto__属性,指向构造函数的原型对象。
每个原型对象上都有个constructor属性,指向它的构造函数。
原型对象有什么用呢?最主要的作用就是用来存放实例对象的公有属性和方法。
在上面例子中,species属性和greet方法对于所有people实例来说都一样,放在构造函数里,那每创建一个实例,就会重复创建一次相同的属性和方法,显得有些浪费。这时候,如果把这些公有的属性和方法放在原型对象里共享,就会好很多。
function Person(name, age) { this.name = name; this.age = age; } // 定义在原型对象上 Person.prototype.species = '人类'; Person.prototype.greet = function () { console.log("Hello"); } let person1 = new Person('南木元元', 22); let person2 = new Person('yuanyuan', 18); console.log(person1.species); // 人类 console.log(person2.species); // 人类 person1.greet(); // Hello person2.greet(); // Hello
为什么实例对象person1和person2中可以访问构造函数Person的原型对象上的方法呢?看上图可以知道,是通过__proto__ 这个属性去访问构造函数的原型对象。
有时候,我们会用person1.constructor查看实例对象的构造函数:
console.log(person1.constructor); // Person()
这个constructor是原型对象上的属性,但实例对象也可以使用,原因就是上面所说的。那如果原型对象上也没有找到想要的属性呢?这就要说到原型链了。
原型链
讲原型链之前,我们需要先了解几个概念。
显式与隐式原型
- 显式原型就是利用prototype属性查找原型,prototype属性是函数独有的属性。
- 隐式原型是利用__proto__属性查找原型,这个属性是所有对象都有的属性。
隐式原型__proto__ 的属性值指向它的构造函数的显式原型prototype属性值:
console.log(person1.__proto__ === Person.prototype); // true console.log(person2.__proto__ === Person.prototype); // true
__proto__与[[Prototype]]
[[Prototype]]用于标识对象的原型。我们来打印一下实例对象person1:
console.log(person1)
你会发现,[[Prototype]]其实就是实例对象person1的原型对象,它的值与person1.__proto__是一样的,通过__proto__可以暴露一个对象内部的原型[[Prototype]]的值。对于使用对象字面量创建的对象,该值是Object.prototype。对于使用数组字面量创建的对象,该值是Array.prototype。对于函数,该值是Function.prototype。
原型链的概念
JavaScript中所有对象都会通过 __proto__ 属性指向自己的原型对象,这个原型对象又会有自己的原型,直到指向Object对象为止,这样就形成了一个链条一样的结构,即原型链。当访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型链上找这个属性,直到找到对应属性或到达原型链的末尾。
- 原型链的终点是什么?
原型链的终点是null。原型链上的所有原型都是对象,所有的对象最终都是由 Object 构造的。可以在控制台通过Object.prototype.__proto__ 来打印原型链的终点。
Object.getPrototypeOf()方法
__proto__
属性并不是语言本身的特性,这是各大厂商具体实现时添加的私有属性,虽然目前很多现代浏览器的 JS 引擎中都提供了这个私有属性,但不推荐使用该属性,我们可以使用 Object.getPrototypeOf 方法来获取实例对象的原型,然后再来为原型添加方法/属性。
hasOwnProperty() 方法
使用hasOwnProperty() 方法获得对象非原型链上的属性。
有时候,我们想要判断对象自身中是否具有指定的属性,而不是从原型中继承来的属性,这时可以使用hasOwnProperty() 方法。
function iterate(obj){ var res = []; for(var key in obj){ if(obj.hasOwnProperty(key)){ res.push(key+': '+obj[key]); } } return res; }
结语
🔥如果此文对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏、✍️评论,支持一下博主~