最近整理了一部分的JavaScript
知识点,因为js
高级阶段涉及知识点比较复杂,文章一直没更新,这里单独将原型部分的概念拎出来理解下。
经过自定义构造函数的方式,建立小狗对象:数组
function Dog(name, age) { this.name = name; this.age = age; this.say = function() { console.log('汪汪汪'); } } var dog1 = new Dog('哈士奇', 1.5); var dog2 = new Dog('大黄狗', 0.5); console.log(dog1); console.log(dog2); console.log(dog1.say == dog2.say); //输出结果为false
画个图理解下:浏览器
每次建立一个对象的时候,都会开辟一个新的空间,咱们从上图能够看出,每只建立的小狗有一个say
方法,这个方法都是独立的,可是功能彻底相同。随着建立小狗的数量增多,形成内存的浪费就更多,这就是咱们须要解决的问题。框架
为了不内存的浪费,咱们想要的实际上是下图的效果:函数
解决方法:this
这里最好的办法就是将函数体放在构造函数以外,在构造函数中只须要引用该函数便可。
function sayFn() { console.log('汪汪汪'); } function Dog(name, age) { this.name = name; this.age = age; this.say = sayFn(); } var dog1 = new Dog('哈士奇', 1.5); var dog2 = new Dog('大黄狗', 0.5); console.log(dog1); console.log(dog2); console.log(dog1.say == dog2.say); //输出结果为true
这样写依然存在问题:spa
想要解决上面的问题就须要用到构造函数的原型
概念。firefox
prototype
:原型。每一个构造函数在建立出来的时候系统会自动给这个构造函数建立而且关联一个空的对象。这个空的对象,就叫作原型。
关键点:prototype
构造函数名.prototype
。示例图:调试
示例代码: 给构造函数的原型添加方法code
function Dog(name,age){ this.name = name; this.age = age; } // 给构造函数的原型 添加say方法 Dog.prototype.say = function(){ console.log('汪汪汪'); } var dog1 = new Dog('哈士奇', 1.5); var dog2 = new Dog('大黄狗', 0.5); dog1.say(); // 汪汪汪 dog2.say(); // 汪汪汪
咱们能够看到,自己Dog
这个构造函数中是没有say
这个方法的,咱们经过Dog.prototype.say
的方式,在构造函数Dog
的原型中建立了一个方法,实例化出来的dog1
、dog2
会先在本身的对象先找say
方法,找不到的时候,会去他们的原型对象中查找。
如图所示:
在构造函数的原型中能够存放全部对象共享的数据,这样能够避免屡次建立对象浪费内存空间的问题。
一、使用对象的动态特性
使用对象的动态属性,其实就是直接使用
prototype
为原型添加属性或者方法。
function Person () {} Person.prototype.say = function () { console.log( '讲了一句话' ); }; Person.prototype.age = 18; var p = new Person(); p.say(); // 讲了一句话 console.log(p.age); // 18
二、直接替换原型对象
每次构造函数建立出来的时候,都会关联一个空对象,咱们能够用一个对象替换掉这个空对象。
function Person () {} Person.prototype = { say : function () { console.log( '讲了一句话' ); }, }; var p = new Person(); p.say(); // 讲了一句话
注意:
使用原型的时候,有几个注意点须要注意一下,咱们经过几个案例来了解一下。
对象.属性名
去获取对象属性的时候,会先在自身中进行查找,若是没有,就去原型中查找;// 建立一个英雄的构造函数 它有本身的 name 和 age 属性 function Hero(){ this.name="德玛西亚之力"; this.age=18; } // 给这个构造函数的原型对象添加方法和属性 Hero.prototype.age= 30; Hero.prototype.say=function(){ console.log('人在塔在!!!'); } var h1 = new Hero(); h1.say(); // 先去自身中找 say 方法,没有再去原型中查找 打印:'人在塔在!!!' console.log(p1.name); // "德玛西亚之力" console.log(p1.age); // 18 先去自身中找 age 属性,有的话就不去原型中找了
对象.属性名
去设置对象属性的时候,只会在自身进行查找,若是有,就修改,若是没有,就添加;// 建立一个英雄的构造函数 function Hero(){ this.name="德玛西亚之力"; } // 给这个构造函数的原型对象添加方法和属性 Hero.prototype.age = 18; var h1 = new Hero(); console.log(h1); // {name:"德玛西亚之力"} console.log(h1.age); // 18 h1.age = 30; // 设置的时候只会在自身中操做,若是有,就修改,若是没有,就添加 不会去原型中操做 console.log(h1); // {name:"德玛西亚之力",age:30} console.log(h1.age); // 30
// 建立一个英雄的构造函数 它有本身的 name 属性 function Hero(){ this.name="德玛西亚之力"; } // 给这个构造函数的默认原型对象添加 say 方法 Hero.prototype.say = function(){ console.log('人在塔在!!!'); } var h1 = new Hero(); console.log(h1); // {name:"德玛西亚之力"} h1.say(); // '人在塔在!!!' // 开辟一个命名空间 obj,里面有个 kill 方法 var obj = { kill : function(){ console.log('大宝剑'); } } // 将建立的 obj 对象替换本来的原型对象 Hero.prototype = obj; var h2 = new Hero(); h1.say(); // '人在塔在!!!' h2.say(); // 报错 h1.kill(); // 报错 h2.kill(); // '大宝剑'
画个图理解下:
图中能够看出,实例出来的h1
对象指向的原型中,只有say()
方法,并无kill()
方法,因此h1.kill()
会报错。同理,h2.say()
也会报错。
在js
中以_
开头的属性名为js
的私有属性,以__
开头的属性名为非标准属性。__proto__
是一个非标准属性,最先由firefox
提出来。
一、构造函数的 prototype 属性
以前咱们访问构造函数原型对象的时候,使用的是
prototype
属性:
function Person(){} //经过构造函数的原型属性prototype能够直接访问原型 Person.prototype;
在以前咱们是没法经过构造函数
new
出来的对象访问原型的:
function Person(){} var p = new Person(); //之前不能直接经过p来访问原型对象
二、实例对象的 __proto__ 属性
__proto__
属性最先是火狐浏览器引入的,用以经过实例对象来访问原型,这个属性在早期是非标准的属性,有了__proto__
属性,就能够经过构造函数建立出来的对象直接访问原型。
function Person(){} var p = new Person(); //实例对象的__proto__属性能够方便的访问到原型对象 p.__proto__; //既然使用构造函数的`prototype`和实例对象的`__proto__`属性均可以访问原型对象 //就有以下结论 p.__proto__ === Person.prototype;
如图所示:
三、__proto__属性的用途
__proto__
属性去修改原型的属性或方法;constructor
:构造函数,原型的constructor
属性指向的是和原型关联的构造函数。
示例代码:
function Dog(){ this.name="husky"; } var d=new Dog(); // 获取构造函数 console.log(Dog.prototype.constructor); // 打印构造函数 Dog console.log(d.__proto__.constructor); // 打印构造函数 Dog
如图所示:
获取复杂类型的数据类型:
经过obj.constructor.name
的方式,获取当前对象obj
的数据类型。
在一个的函数中,有个返回值name
,它表示的是当前函数的函数名;
function Teacher(name,age){ this.name = name; this.age = age; } var teacher = new Teacher(); // 假使咱们只知道一个对象teacher,如何获取它的类型呢? console.log(teacher.__proto__.constructor.name); // Teacher console.log(teacher.constructor.name); // Teacher
实例化出来的teacher
对象,它的数据类型是啥呢?咱们能够经过实例对象teacher.__proto__
,访问到它的原型对象,再经过.constructor
访问它的构造函数,经过.name
获取当前函数的函数名,因此就能获得当前对象的数据类型。又由于.__proto__
是一个非标准的属性,并且实例出的对象继承原型对象的方法,因此直接能够写成:obj.constructor.name
。
原型继承
:每个构造函数都有prototype
原型属性,经过构造函数建立出来的对象都继承自该原型属性。因此能够经过更改构造函数的原型属性来实现继承。
继承的方式有多种,能够一个对象继承另外一个对象,也能够经过原型继承的方式进行继承。
一、简单混入继承
直接遍历一个对象,将全部的属性和方法加到另外一对象上。
var animal = { name:"Animal", sex:"male", age:5, bark:function(){ console.log("Animal bark"); } }; var dog = {}; for (var k in animal){ dog[k]= animal[k]; } console.log(dog); // 打印的对象与animal如出一辙
缺点:只能一个对象继承自另外一个对象,代码复用过低了。
二、混入式原型继承
混入式原型继承其实与上面的方法相似,只不过是将遍历的对象添加到构造函数的原型上。
var obj={ name:'zs', age:19, sex:'male' } function Person(){ this.weight=50; } for(var k in obj){ // 将obj里面的全部属性添加到 构造函数 Person 的原型中 Person.prototype[k] = obj[k]; } var p1=new Person(); var p2=new Person(); var p3=new Person(); console.log(p1.name); // 'zs' console.log(p2.age); // 19 console.log(p3.sex); // 'male'
面向对象思想封装一个原型继承
咱们能够利用面向对象的思想,将面向过程进行封装。
function Dog(){ this.type = 'yellow Dog'; } // 给构造函数 Dog 添加一个方法 extend Dog.prototype.extend = function(obj){ // 使用混入式原型继承,给 Dog 构造函数的原型继承 obj 的属性和方法 for (var k in obj){ this[k]=obj[k]; } } // 调用 extend 方法 Dog.prototype.extend({ name:"二哈", age:"1.5", sex:"公", bark:function(){ console.log('汪汪汪'); } });
三、替换式原型继承
替换式原型继承,在上面已经举过例子了,其实就是将一个构造函数的原型对象替换成另外一个对象。
function Person(){ this.weight=50; } var obj={ name:'zs', age:19, sex:'male' } // 将一个构造函数的原型对象替换成另外一个对象 Person.prototype = obj; var p1=new Person(); var p2=new Person(); var p3=new Person(); console.log(p1.name); // 'zs' console.log(p2.age); // 19 console.log(p3.sex); // 'male'
以前咱们就说过,这样作会产生一个问题,就是替换的对象会从新开辟一个新的空间。
替换式原型继承时的bug
替换原型对象的方式会致使原型的constructor
的丢失,constructor
属性是默认原型对象指向构造函数的,就算是替换了默认原型对象,这个属性依旧是默认原型对象指向构造函数的,因此新的原型对象是没有这个属性的。
解决方法:手动关联一个constructor
属性
function Person() { this.weight = 50; } var obj = { name: 'zs', age: 19, sex: 'male' } // 在替换原型对象函数以前 给须要替换的对象添加一个 constructor 属性 指向本来的构造函数 obj.constructor = Person; // 将一个构造函数的原型对象替换成另外一个对象 Person.prototype = obj; var p1 = new Person(); console.log(p1.__proto__.constructor === Person); // true
四、Object.create()方法实现原型继承
当咱们想把对象1
做为对象2
的原型的时候,就能够实现对象2
继承对象1
。前面咱们了解了一个属性:__proto__
,实例出来的对象能够经过这个属性访问到它的原型,可是这个属性只适合开发调试时使用,并不能直接去替换原型对象。因此这里介绍一个新的方法:Object.create()
。
语法: var obj1 = Object.create(原型对象);
示例代码: 让空对象obj1
继承对象obj
的属性和方法
var obj = { name : '盖伦', age : 25, skill : function(){ console.log('大宝剑'); } } // 这个方法会帮咱们建立一个原型是 obj 的对象 var obj1 = Object.create(obj); console.log(obj1.name); // "盖伦" obj1.skill(); // "大宝剑"
兼容性:
因为这个属性是
ECMAScript5
的时候提出来的,因此存在兼容性问题。
利用浏览器的能力检测
,若是存在Object.create
则使用,若是不存在的话,就建立构造函数来实现原型继承。
// 封装一个能力检测函数 function create(obj){ // 判断,若是浏览器有 Object.create 方法的时候 if(Object.create){ return Object.create(obj); }else{ // 建立构造函数 Fun function Fun(){}; Fun.prototype = obj; return new Fun(); } } var hero = { name: '盖伦', age: 25, skill: function () { console.log('大宝剑'); } } var hero1 = create(hero); console.log(hero1.name); // "盖伦" console.log(hero1.__proto__ == hero); // true
对象有原型,原型自己又是一个对象,因此原型也有原型,这样就会造成一个链式结构的原型链。
示例代码: 原型继承练习
// 建立一个 Animal 构造函数 function Animal() { this.weight = 50; this.eat = function() { console.log('蜂蜜蜂蜜'); } } // 实例化一个 animal 对象 var animal = new Animal(); // 建立一个 Preson 构造函数 function Person() { this.name = 'zs'; this.tool = function() { console.log('菜刀'); } } // 让 Person 继承 animal (替换原型对象) Person.prototype = animal; // 实例化一个 p 对象 var p = new Person(); // 建立一个 Student 构造函数 function Student() { this.score = 100; this.clickCode = function() { console.log('啪啪啪'); } } // 让 Student 继承 p (替换原型对象) Student.prototype = p; //实例化一个 student 对象 var student = new Student(); console.log(student); // 打印 {score:100,clickCode:fn} // 由于是一级级继承下来的 因此最上层的 Animate 里的属性也是被继承的 console.log(student.weight); // 50 student.eat(); // 蜂蜜蜂蜜 student.tool(); // 菜刀
如图所示:
咱们将上面的案例经过画图的方式展示出来后就一目了然了,实例对象animal
直接替换了构造函数Person
的原型,以此类推,这样就会造成一个链式结构的原型链。
完整的原型链
结合上图,咱们发现,最初的构造函数
Animal
建立的同时,会建立出一个原型,此时的原型是一个空的对象。结合原型链的概念:“原型自己又是一个对象,因此原型也有原型”,那么这个空对象往上还能找出它的原型或者构造函数吗?
咱们如何建立一个空对象? 一、字面量:{}
;二、构造函数:new Object()
。咱们能够简单的理解为,这个空的对象就是,构造函数Object
的实例对象。因此,这个空对象往上面找是能找到它的原型和构造函数的。
// 建立一个 Animal 构造函数 function Animal() { this.weight = 50; this.eat = function() { console.log('蜂蜜蜂蜜'); } } // 实例化一个 animal 对象 var animal = new Animal(); console.log(animal.__proto__); // {} console.log(animal.__proto__.__proto__); // {} console.log(animal.__proto__.__proto__.constructor); // function Object(){} console.log(animal.__proto__.__proto__.__proto__); // null
如图所示:
一、描述出数组[]的原型链结构
// 建立一个数组 var arr = new Array(); // 咱们能够看到这个数组是构造函数 Array 的实例对象,因此他的原型应该是: console.log(Array.prototype); // 打印出来仍是一个空数组 // 咱们能够继续往上找 console.log(Array.prototype.__proto__); // 空对象 // 继续 console.log(Array.prototype.__proto__.__proto__) // null
如图所示:
二、扩展内置对象
给
js
原有的内置对象,添加新的功能。
注意:这里不能直接给内置对象的原型添加方法,由于在开发的时候,你们都会使用到这些内置对象,假如你们都是给内置对象的原型添加方法,就会出现问题。
错误的作法:
// 第一个开发人员给 Array 原型添加了一个 say 方法 Array.prototype.say = function(){ console.log('哈哈哈'); } // 第二个开发人员也给 Array 原型添加了一个 say 方法 Array.prototype.say = function(){ console.log('啪啪啪'); } var arr = new Array(); arr.say(); // 打印 “啪啪啪” 前面写的会被覆盖
为了不出现这样的问题,只需本身定义一个构造函数,而且让这个构造函数继承数组的方法便可,再去添加新的方法。
// 建立一个数组对象 这个数组对象继承了全部数组中的方法 var arr = new Array(); // 建立一个属于本身的构造函数 function MyArray(){} // 只须要将本身建立的构造函数的原型替换成 数组对象,就能继承数组的全部方法 MyArray.prototype = arr; // 如今能够单独的给本身建立的构造函数的原型添加本身的方法 MyArray.prototype.say = function(){ console.log('这是我本身添加的say方法'); } var arr1 = new MyArray(); arr1.push(1); // 建立的 arr1 对象可使用数组的方法 arr1.say(); // 也可使用本身添加的方法 打印“这是我本身添加的say方法” console.log(arr1); // [1]
当经过
对象名.属性名
获取属性是 ,会遵循如下属性搜索的原则:
null
为止。