js–原型和原型链相关问题
- 2021 年 4 月 18 日
- 筆記
- javascript
前言
阅读本文前先来思考一个问题,我们在 js 中创建一个变量,我们并没有给这个变量添加一些方法,比如 toString() 方法,为什么我们可以直接使用这个方法呢?如以下代码,带着这样的问题,我们来学习本节的原型和原型链的一些知识。
正文
1.构造函数创建对象问题
function Person(name, age) { this.name = name; this.age = age; this.sayName = function () { console.log(this.name); }; } var person = new Person("xiaoming", 18); person.sayName(); // xiaoming
当使用构造函数创建对象person时,即使用new操作符构造一个实例对象的时候,首先会创建一块新的内存空间,标记为person实例,执行构造函数会创建一个对象,会将对象的原型指向构造函数的 prototype 属性,然后执行上下文中的this指向这个对象,最后再执行整个函数,如果返回值不是对象,则返回新建的对象,创建的对象有一个构造函数 constructor 属性。其实质就是创建一个 object 引用类型的实例,然后把实例保存在变量 person 中。
总结 :new 操作符调用构造函数经历了以下四步:
(1)创建一个新对象;
(2)将构造函数的作用域赋值给新对象(因此 this 指向这个新对象);
(3)执行构造函数中的代码(为这个构造的新对象添加属性);
(4)返回这个新对象。
2.原型相关问题
我们创建的每个函数都有一个 prototype 属性,这个属性是一个指针,指向一个对象,而这个对象包含了通过该构造函数实例的对象所共享的属性和方法。那么,prototype 就是通过调用构造函数而创建的那个对象实例的原型对象。
function Person() {} Person.prototype.name = "xioming"; Person.prototype.sayHello = function () { console.log(this.name); }; var person1 = new Person(); person1.sayHello(); //xiaoming var person2 = new Person(); person2.sayHello(); //xiaoming
上面的代码,我们将 sayHello() 方法和所有属性直接添加到了 Person 的 prototype 属性中,构造函数变成了空函数,通过此构造函数创建的新对象实例,具有相同的属性和方法,与纯构造函数创建的对象不同的是所有实例共享了这些属性和方法。
(1)理解原型对象
无论什么时候,只要创建一个新函数,就会根据一种特定的规则为该函数创建一个 prototype 属性,这个属性指向函数的原型对象,默认情况下,所有原型对象都会自动获得一个 constructor (构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。例如上面的例子中 Person.prototype.constructor 指向 Person,同样,我们可以继续为原型对象添加别的属性和方法。创建了自定义构造函数之后,其原型只会取得constructor属性,其他方法都是通过从object继承而来,当调用构造函数创建一个新实例后,该实例内部会包含一个指针指向构造函数的原型对象,这个指针叫 __proto__ ,因此可以通过下面的图来表示上面例子的代码。
因此通过上面的图不难得出,js 中获取原型的方法有如下三种:
(1)person1.__proto__
(2)Object.getPrototype(person1)
(3)person1.constructor.prototype
同样可以通过 isPrototypeOf() 方法来确定对象之间是否存在原型关系,Person.prototype.isPrototypeOf( person1 )返回值为 true 。当然也可以如下使用Object.getPrototype(person1).name返回值为“ xiaoming ”。再来看下下面的这段代码:
function Person() {} Person.prototype.name = "xioming"; Person.prototype.sayHello = function () { console.log(this.name); }; var person1 = new Person(); person1.name="xiaohong" console.log(person1.name);//“xiaohong”
通过上面的代码不难得出对象属性的访问顺序,每当代码中读取某个对象的属性是,都会执行一次搜索,目标是给定名字的属性,搜索首先从对象实例本身开始,如果在实例中找到了具有给定名字的属性,则返回该属性的值,如果没有找到,则会继续搜索__proto__指针指向的原型对象,在原型对象中找到具有相同名字的属性。虽然可以通过对象实例访问保存在原型对象中的值,但是不能通过独享实例重新原型中的值,如果我们在实例中添加一个属性,而该属性与原型中的属性同名,那么就在实例中创建该属性,该属性就会屏蔽原型对象中的那个属性。即添加了同名属性后,这个属性就会阻止我们访问原型中的属性。即使我们把这个属性值设置为null,也只会在实例中访问这个属性,不会恢复对原型的同名属性的访问,要想恢复,只能使用 delete 操作符完全删除该实例属性。因此,官方也提供了一个 hasOwnProperty() 方法来判断该属性是否属于实例对象,如果是则返回 true,否则返回 false 。
最后总结得出原型,构造函数,实例对象三者之间的关系如下:
(2)原型与 in 操作符
使用 in 操作符有两种情况,一种是直接使用,另外一种是 for- in循环中使用,在单独使用的时候,in 操作符会在通过对象能够访问属性的时候返回 true ,无论该属性存在于对象实例还是原型对象中。“name” in person1 返回 true ,因此结合 hasOwnProperty() 方法可以判断属性是存在于自身实例中,还是存在于原型对象中。使用 for-in 循环时,返回的是所有能够在对象中可以访问,可以枚举的属性,其中既包含实例中的有包含原型中的,tostring(),valueOf()….但是由于浏览器版本限制,这种方式不推荐使用。
要取得对象上所有可枚举的实例属性,可以通过 Object.keys() 方法,该方法接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。
(3)原型的原型关系如下图:
2.原型链
理解原型链之前,先来看如下代码:
function Person() {} Person.prototype.name = "xioming"; Person.prototype.sayHello = function () { console.log(this.name); }; var person1 = new Person(); console.log(person1.toString()); //[object Object]
这就是刚开始讲到的,为什么每一个对象都包含 tostring() 这个方法呢,有了原型的了解,这里用到原型链,当我们访问一个对象的属性或者方法时,如果这个对象实例内部不存在这个属性或者方法,那么他会在原型对象里找这个同名属性或者方法,这个原型对象又有自己的原型,于是这么一层一层找下去,也就产生了原型链这个概念,原型链的尽头一般来说都是 Object.prototype ,所有者就产生了新建的对象都会存在 toString() 等方法。
总结
以上就是本文的全部内容,希望给读者带来些许的帮助和进步,方便的话点个关注,小白的成长之路会持续更新一些工作中常见的问题和技术点。