在典型的OOP的语言中 (如java),都存在类的概念 ,类就是对象的模板 ,但是在ES6(2015年发布的ECMAscript6.0)之前 JS中并没有引入类的概念
在这之前 对象不是基于类创建的 而是用一种被称为 构造函数 的特殊函数来定义对象和他们的特征
构造函数基本介绍:
构造函数
案例
function Zwjs(name, age, sex) { this.name = name; this.age = age; this.sex = sex; this.jn = function () { console.log("睡觉"); }; } let x = new Zwjs('shaco',18,'男'); console.log(x); x.jn()
javascr的构造函数中可以添加一些成员 可以在构造函数本身上添加 也可以在构造函数内部的this上进行添加 通过这两个方法添加的成员 我们分别称之为 静态成员 和 实例成员
静态成员: 在构造函数本身添加的成员称之为静态成员,只能由构造函数本身来访问
实例成员:在构造函数内部创建的对象成员称为实例成员 , 只能由实例化的对象来访问
function Zwjs(name, age, sex) { this.name = name; this.age = age; this.sex = sex; this.jn = function () { console.log("睡觉"); }; } let x = new Zwjs('shaco',18,'男'); // 实例成员就是构造函数内部通过this添加的成员 // 如上面的 name age sex jn就是实例成员 // 实例成员只能通过实例化的对象来访问 // 如 console.log(x.name); // 我们不可以通过构造函数来访问实例成员 //如 // console.log(zwjs.name); 报错 // 静态成员 在构造函数本身上添加的成员 Zwjs.lol = '白银'; // 因为js是动态语言 我们可以动态添加语言 所以我们可以添加属性 // 这类添加的对象就是静态成员 // 静态成员只能通过构造函数来访问 console.log(Zwjs.lol); // 不可以通过实例化的对象来访问 // console.log(x.lol0);//undefined
构造函数虽然很好用 但还是有问题的存在
我们使用构造函数的时候 会开辟一个新的内存空间
但是因为我们的对象中常常有函数作为方法使用 而函数是复杂数据类型 当我们多次使用构造函数的时候 都会开辟一个新的内存空间来存放同一个函数
案例如下 我们用构造函数创建两个对象
function Zwjs(name, age, sex) { this.name = name; this.age = age; this.sex = sex; this.jn = function () { console.log("睡觉"); }; } let x = new Zwjs('shaco',18,'男'); let y = new Zwjs('yasuo', 18 ,'男'); console.log(x.jn===y.jn);
但是他们的方法不相等 因为复杂数据类型的 存储方式是将地址放在栈中 而实际的对象放在堆中 所以他们不相等
简单的说 构造函数大量使用 就会不停的开辟很多空间来存放同一个函数 造成内存浪费 对性能造成很大的影响
所以这时候我们可以用到原型对象来解决
在js中 每一个构造函数都有一个prototype属性 指向另外一个对象
需要注意的是 prototype自己就是一个对象
而这个对象的所有方法和属性 都会被构造函数拥有
我们可以把公共的方法放到原型对象中
构造函数通过原型分配的函数是所有对象共享的
因此我们可以把一些不变的方法 直接定义到prototype对象上 这样所有对象的实例都可以共享这些方法
function Zwjs(name, age, sex) { this.name = name; this.age = age; this.sex = sex; } // console.dir(Zwjs); Zwjs.prototype.jn = function (){ console.log('恰饭'); } let shaco =new Zwjs('shaco',18,'男'); let yasuo =new Zwjs('yasuo',19,'男'); shaco.jn(); yasuo.jn()
如上列代码 我们利用原型对象创建了jn这个方法
然后实例化俩个对象
并让他们使用这个方法 我们来看看结果如何
说明我们实现了方法的共享
一般情况下 我们的公共属性定义到构造函数里面
(如上面代码中的 name,age等属性)
公共的方法我们定义到原型对象身上
(如我们通过原型对象定义的jn方法)
也可以写为[[Prototype]]
实例化对象都会有一个属性__proto__指向构造函数中的原型对象prototype,
之所以我们可以用构造函数prototype 原型对象的属性和方法 就是因为实例化对象有 proto 对象原型的存在
简单的说 实例化对象的__proto__(对象原型) 和构造函数的原型对象 prototype 是一样的
对象原型__proto__的意义就是给对象的查找机制提供一个方向 或者说是一条路线 但是对象原型是一个非标准的属性 所以在我们实际开发的过程中 不可以使用这个属性
它的作用只是从对象的内部指向原型对象 prototype
对象原型prototype和原型对象__proto__中 都有一个属性constructor
我们称constructor为构造函数 因为它指回构造函数本身
我们打印他们的constructor属性 都是指向我们一开始的构造函数
举个例子 当我们使用对象原型来存放多个我们的方法函数的时候,经常会以对象的方式来书写使结构更加清晰
function Zwjs(name, age, sex) { this.name = name; this.age = age; this.sex = sex; } Zwjs.prototype = { sleep: function () { console.log('睡觉'); }, eat: function () { console.log('恰饭'); } }
然后我们再次打印 构造函数的的原型对象的constructor
发现他并没有指向我们的构造函数 而是指向了 一个对象
为了避免可能发生的错误 我们可以调用constructor属性 使原型对象指向我们原来的构造函数
因为每个对象都有自己的对象原型(prototype) 那么我们的原型对象(proto)的对象原型是什么呢
function Zwjs(name, age, sex) { this.name = name; this.age = age; this.sex = sex; } console.log(Zwjs.prototype.__proto__);
结果如下 我们可以理解为内置对象
所以我们吧原型链升级
我们在打印内置对象的构造函数
console.log(Object.constructor);
得到一个构造函数
由此我们可以再次完事原型链
顺藤摸瓜 我们在试试内置对象的原型对象的对象原型是什么
console.log(Object.prototype.__proto__);
得到
由此 原型链完成
当我们访问一个对象的属性或者方法的时候 首先先查找这个对象本身有没有这种属性或者方法
如果没有的话 旧查找他的对象原型(也就是实例化对象使用原型对象__proto__取对象原型prototype中查找)
如果还没有 就去查找内置对象的原型(内置对象的原型对象)
最后如果还没找到就返回underfind或者null
案例如下
我们注释x.sex
我们在注释Zwjs.prototype.sex =‘女’
最后我们注释掉 Object.prototype.sex = ‘人妖’;
首先我们需要理解的是
在构造函数中 里面的this指向的是对象实例
我们创建一个变量x 但不赋值
然后加入方法中 使他等于this 于是我们实例化对象调用原型对象中的方法使
x 就等于this
然后分别打印
this的代表(得到构造函数 然后因为构造函数指向对象实例)
所以我们打印this是否等于实例对象 得到true
function Zwjs(name, age, sex) { this.name = name; this.age = age; this.sex = sex; } let x ; Zwjs.prototype.jn = function (){ console.log('恰饭'); x=this; } let shaco =new Zwjs('shaco',18,'男'); let yasuo =new Zwjs('yasuo',19,'男'); shaco.jn() console.log(x); console.log(x===shaco);