本帖最后由 长沙-小知姐姐 于 2019-2-21 15:00 编辑
前言:希望这篇帖子让大家对于原型和原型链的理解有一些帮助
目录 1 对象 2 原型 3 原型链
对象 讲到原型链,那么我们要先认识javascript里面的对象,那么什么是对象? 所谓万物皆对象,这句话是不是很抽象,抽象就对了,这是一个很广的概念;对象指的是一类具体的事物;里面封装了属性(property)和方法(method);
例如: 张三是一个对象, 那么张三的属性就有:身高、体重、肤色等等; 而对应的方法(或者是行为)就有:吃饭、睡觉、打豆豆等等 在程序员的世界里,万事万物我们都可以封装成为对象;了解到了对象,那么我们如何在代码中创建一个对象呢?
1 创建对象的三种方式 1.1 字面量形式创建 var hero = { name:'德玛西亚', attack:function(){} }
1.2 new空构造形式创建 var hero = new Object(); hero.name = '德玛西亚'; hero.attack =function(){};
1.3 自定义构造函数 function Hero(name,attack){ this.name= name; this.attack= attack; } var hero = newHero('德玛西亚',function(){});
1.4 使用构造函数与实例对象的关系 使用构造函数的好处不仅仅在于代码的简洁性,更重要的是我们可以识别对象的具体类型。 在每一个实例对象中同时有一个 constructor 属性,该属性指向创建该实例的构造函数:
var hero1 = new Hero('德玛西亚', function () { }); var hero2 = new Hero('德莱文', function () { }); console.log(hero1.constructor ===Hero);//true console.log(hero2.constructor ===Hero);//true console.log(hero1.constructor===hero2.constructor);//true
• 构造函数是根据具体的事物抽象出来的抽象模板 • 实例对象是根据抽象的构造函数模板得到的具体实例对象 • 每一个实例对象都具有一个 constructor 属性,指向创建该实例的构造函数 • 可以通过实例的 constructor 属性判断实例和构造函数之间的关系
了解到了构造函数与实例对象的关系后,下面我们来看原型
原型 1 为什么会有原型? 我们在使用构造函数的时候,一般都会声明方法,那么就会存在每次实例化一个对象的时候,都会为这些相同的方法创建一块内存区域去存储,这样就会导致内存的浪费;为了解决这个问题,就有了原型 prototype 2 原型与构造函数和示例对象的关系
• 任何函数都具有一个 prototype 属性,该属性是一个对象 • 构造函数的 prototype 对象默认都有一个 constructor 属性,指向 prototype 对象所在函数 • 通过构造函数得到的实例对象内部会包含一个指向构造函数的 prototype 对象的指针 __proto__ • 所有实例都直接或间接继承了原型对象的成员
function Hero() { //定义空的构造函数 } Hero.prototype.sayHi = function() { console.log('你好'); } var hero = new Hero(); hero.sayHi();
上面代码在Hero里面没有定义sayHi的方法,而是在Hero构造函数的prototype属性(得到的是原型对象)里定义的sayHi(),在调用的时候让实例对象进行的调用,那么输出结果如下:
原型链 1 什么是原型链? 方法和属性的搜索原则,用白话来讲,就是我们实例化对象在使用属性,或者调用方法的时候,系统是如何帮我们去查找的
2 如何去搜索的 • 搜索首先从对象实例本身开始,例如hero实例对象 • 如果在hero实例中找到了具有给定名字的属性,则返回该属性的值 • 如果没有找到,则继续搜索hero指针hero.__proto__指向的原型对象,在原型对象中查找具有给定名字的属性 • 如果在原型对象中找到了这个属性,则返回该属性的值,如果还没有找到,那么会从hero的原型对象里面的指针hero原型对象.__proto__上继续寻找 • 依次类推,一直向上找,当向上找到Object的原型的时候,这条原型链就算到头了
实例: function Hero() { //定义空的构造函数 } //给原型对象定义方法 Hero.prototype.sayHi = function() { console.log('你好'); } //在原型对象上定义属性 Hero.prototype.name = '我是一个英雄'; var hero = new Hero(); hero.sayHi(); console.log(hero.name);
输出结果如下:
3 注意事项 接着上面例子,不管创建多少个实例对象,如果构造函数里面没有定义属性和方法,是享有的原型对象的,那么它们获取的属性和方法都是同一个 function Hero() { //定义空的构造函数 } //给原型对象定义方法 Hero.prototype.sayHi = function() { console.log('你好'); } //在原型对象上定义属性 Hero.prototype.name = '我是一个英雄'; //第一个对象 var hero = new Hero(); console.log('我是第一个创建的:' + hero.name); hero.sayHi(); //第二个对象 var hero1 = new Hero(); console.log('我是第二个创建的:' + hero1.name); hero1.sayHi(); 输出结果 如果获取的属性或者是方法,通过原型链的寻找都没有找到,那么就会出现以下问题 实例代码 function Hero() { //定义空的构造函数 } var hero = new Hero(); console.log(hero.name); hero.sayHi();
输出结果
如果获取的属性或者是方法,通过原型链的寻找都没有找到,那么就会出现以下问题
实例代码 function Hero() { //定义空的构造函数 } var hero = new Hero(); console.log(hero.name); hero.sayHi();
输出结果
出现这个结果很好理解,name属性是我们自己定义的,通过原型链向上搜寻原则,找到Object的原型对象,里面还没有,那么就是undefined 未定义的;而方法不一样,当我们找到Object的原型对象,里面没有sayHi()方法,那么就会抛出异常,sayHi不是一个函数;
|