在Es6以前,因为javascript没有对类的支持,也就是说它并不具有如传统后台语言(好比java)拥有类的功能,所谓类就是用来描述事物中的属性和行为的,类的特征是由成员组成的,而属性对应的就是类中的成员变量,而方法对应的就是类中的成员方法,这是传统oop语言的描述,然而在javascript中,虽没有类的概念,可是它每每是经过构造函数和原型对象来给对象模拟与类类似的功能,可是这些类似的功能并不必定表现的与类彻底一致,其实建立构造函数的过程,就是建立模板的过程,类必定程度上与此类似,建立多个共享的特定的属性和方法,用于生成对象的饼干工具,主要目的是提升代码的可复用性,也提升了代码的性能,有时候,在咱们无心间就已经在使用了这些特性,什么构造函数,原型,我的以为,初次理解起来非常抽象,也稀里糊涂的以为实际开发中到底有什么卵用,也许后者在不涉及复杂的功能需求时,平时用得很少,显然Es6中已新增了类class的功能,愈来愈严格,愈来愈像后端语言,Es6,Es7,Es8新增的诸多方法也愈来愈强大,标准也愈来愈完善,可是我以为理解构造函数与原型对象是必须的,是js面向对象编程的基础,今天就个人学习和使用跟你们分享一下学习心得javascript
正文从这里开始~html
什么是函数
先看下面一简易代码java
var funA = function(){
console.log("我是匿名函数保存在变量funA中");
}
var funB = [function(){
console.log("我是匿名函数保存在数组funB中");
}]
var funC = {
method:function(){
console.log("我是匿名函数保存在对象funC中");
}
}
// 函数的调用
funA(); // 普通函数的调用
funB[0](); // 函数存入数组中的调用
funC.method(); // 对象调用方法的使用
// 函数能够做为参数传递,也能够做为返回值返回
var funD = function(funParm){
return funParm;
}
var runFunParmPassedToFunD = funD(function(){
console.log("欢迎关注微信itclanCoder公众号");
})
runFunParmPassedToFunD();
// 函数是对象,也就是说函数也拥有属性
var FunE = function(){}
FunE.property = "随笔川迹";
console.log(FunE.property);
// 证实函数式对象
console.log("funA的数据类型是",typeof funA);
console.log("funA具体所属",Object.prototype.toString.call(funA));
console.log("funA是由Object的一个实例对象?",funA instanceof Object);
console.log("funA函数下面的构造器是",funA.constructor);
console.log("funA函数是由Object构造出来的?",funA.constructor == Object); // false
console.log("funA下面的原型",funA.prototype); // funA下面的原型
console.log("Object下的原型",Object.prototype); // Object对象下原型
console.log("funA原型下构造器",funA.prototype.constructor);//function fun(){}
console.log("对象原型下的构造器",Object.prototype.constructor);复制代码
控制台输出结果以下:jquery
结论
:
什么是构造函数
定义:构造函数就是你用new
关键字建立对象时调用的函数
做用(优势):建立多个共享特定属性和行为的对象,主要是用于生成对象的饼干模具
缺点:当实例化多个对象时,会重复的建立对象,形成内存空间的浪费,增大CPU的开销,并无消除代码的冗余,(如后面代码所示,原型正好解决了此类问题)编程
// 声明一构造函数,首字母大写
function Animal(name,age){
// this == new Animal();new会自动的建立this对象,且类型就是该构造安徽省农户的类型,构造函数不须要返回值,由于new会显示的返回,return的值就等于函数名+()的调用
this.name = name; // 自定义属性
this.age = age; // 同上
this.fun = function(){ // 自定义方法
return this.name+" "+this.age+"岁了";
}
}
// 实例化对象
var animal1 = new Animal("cat",2);
var animal2 = new Animal("dog",3); console.log(animal1.name,animal1.age,animal2.name,animal2.age); // cat 2 dog 3
console.log(animal1.fun(),animal2.fun()); // cat 2岁了 dog 3岁了
console.log(animal1.hasOwnProperty("name"));
console.log(animal1.hasOwnProperty("age"));
console.log(animal1 instanceof Animal); // true,证实animal1是Animal1是Animal构造函数建立出来的
console.log(animal2 instanceof Animal);
console.log(animal1.constructor === Animal); // true
console.log(animal2.constructor === Animal); // true
console.log(animal1.fun == animal2.fun); // false复制代码
示例代码截图以下
后端
解决办法1
:将构造函数里面自定义的方法拿出来,独立放在构造函数外
// 声明一构造函数,首字母大写
function Animal(name,age){
this.name = name; // 自定义属性
this.age = age; // 同上
this.fun = fun;
}
// 把构造函数里面自定义的方法拿出来
function fun(){
return this.name+" "+this.age+"岁了";
}
// 实例化对象
var animal1 = new Animal("cat",2);
var animal2 = new Animal("dog",3);
console.log(animal1.fun === animal2.fun); // true复制代码
控制台截图以下所示
设计模式
解决办法2
利用原型正好解决实例化多个对象时,避免构造函数内的方法重复建立(如后面的示例代码所示)
普通函数与构造函数的区别
示例代码以下所示:api
// 声明函数
function Animal(name,age){
this.name = name;
this.age = age;
this.fun = function(){
return this.name+" "+this.age+"岁了";
}
//console.log(this); window
}
// 无new的状况
var animal1 = Animal("cat",2);
var animal2 = Animal("dog",3);
console.log(animal1 instanceof Animal); // false
console.log(animal2 instanceof Animal); // false
console.log(Object.prototype.toString.call(animal1));//[object Undefined]
console.log(Object.prototype.toString.call(animal2));
console.log(name,age); // dog 3
console.log(animal1.name,animal1.age); //报错复制代码
控制台输出结果
数组
针对以上问题,若是想普通函数也具备构造函数的功能,怎么作?以下代码所示
// 声明构造函数
function Animal(name,age){
// 加一this条件判断,用instanceof来检查本身是否被new调用
if(this instanceof Animal){
this.name = name;
this.age = age;
this.fun = function(){
return this.name+" "+this.age+"岁了"; }
}else{
// 以new递归调用本身来为对象建立正确的实例,这样作的目的是在不一样的状况下表现出一致的行为,经常是为了保护那些忘记了使用new的状况
return new Animal(name,age);
}
}
// 无new的状况
var animal1 = new Animal("cat",2);
var animal2 = Animal("dog",3);
console.log(animal1 instanceof Animal); // true
console.log(animal2 instanceof Animal); // true
console.log(Object.prototype.toString.call(animal1));//[object object]
console.log(Object.prototype.toString.call(animal2));//[object object]
console.log(animal1.name,animal1.age);
console.log(animal2.name,animal2.age);复制代码
控制台输出结果以下
为什么内置构造函数无new也能工做
示例代码以下所示
var arr = Array; // 当没有参数时,构造函数后面的圆括号能够省略
var obj = Object({
name:"随笔川迹",
sex:"boy",
fun:function(){
return this.name+" "+this.sex+" "+Object.prototype.toString.call(this);
}});
console.log(obj.fun());复制代码
截图以下所示
缘由
:由于那些内置系统构造函数(Array,Object,RegExp,Date,Error,String等)都被设计为做用域安全的构造函数,也就是说在整个全局范围内都是可见的,一个做用域安全的构造函数无new也能够工做,并返回一样类型的对象
原型对象
protype
:
function ProtoFun(width,height){
this.width = width;
this.height = height;
this.method = function(){
return "我是构造函数下自定义的方法"
}
}
// 构造函数.原型下添加属性
ProtoFun.prototype.color = "red";
// 构造函数.原型下添加方法
ProtoFun.prototype.fun = function(){
return this.width+" "+this.height+" "+this.color;
}
// 上面两个一般能够合并成下面一个
ProtoFun.prototype.init = {
color:"red",
fun:function(){
return this.width+" "+this.height+" "+this.color;
}
}
var elemObj1 = new ProtoFun(100,100);
var elemObj2 = new ProtoFun(200,200);
console.log(elemObj1.width,elemObj1.height); // 100 100
console.log(elemObj2.width,elemObj2.height); // 200 200
console.log(elemObj1.color,elemObj1.fun(),elemObj1.init.color,elemObj1.init.fun());
console.log(elemObj2.color,elemObj2.fun(),elemObj2.init.color,elemObj2.init.fun());
console.log(elemObj1.method===elemObj2.method); // false
console.log(elemObj1.fun === elemObj2.fun); // true复制代码
控制台输出结果以下// 未用原型写法,普通写法求和
var arr1 = [1,2,3,4,5,6,7,8,9,10];
var arr2 = [2,4,6,8,10,12,14,16]
arr1.sum = function(){
var result = 0;
for(var i = 0;i<arr1.length;i++){ // 这里也能够换成this.length
result += this[i];
}
return result; // 返回结果
}
arr2.sum = function(){
var result = 0;
for(var i = 0;i<arr2.length;i++){
result += this[i];
}
return result; // 返回结果
}
console.log("数组arr1和为",arr1.sum()); // 55
console.log("数组arr2和为",arr2.sum()); // 72复制代码
控制台截图以下:
原型写法
// 原型写法
var arr1 = [1,2,3,4,5,6,7,8,9,10];
var arr2 = [2,4,6,8,10,12,14,16]
Array.prototype.sum = function(){
var result = 0;
for(var i = 0;i<this.length;i++){
result += this[i];
}
return result;
}
console.log("数组arr1的和为",arr1.sum()); // 数组arr1的和为55
console.log("数组arr2的和为",arr2.sum()); // 数组arr2的和为72
console.log(arr1.sum === arr1.sum); // true复制代码
//普通函数封装写法,也就是闭包写法
var arr1 = [1,2,3,4,5,6,7,8,9,10];
var arr2 = [2,4,6,8,10,12,14,16]
function AddResult(arr){
arr.sum = function(){
var result = 0;
for(var i = 0;i<this.length;i++){ // 这里也能够换成this.length
result += this[i];
}
return result; // 返回结果
}
return arr.sum();
}
console.log("数组arr1和为",AddResult(arr1)); // 数组arr1和为55
console.log("数组arr2和为",AddResult(arr2)); // 数组arr2和为72复制代码
区分构造函数自定义属性与原型属性
以下示例代码所示:
function Person(name,publicNum){
this.name = name;
this.publicNum = publicNum;
this.userDefined = function(){
return "我是构造函数自定义方法unerDefined"
}
}
Person.prototype.age = 25;
Person.prototype.init = {
city:"beijing",
job:"coder",
method:function(){
return "我是原型下的方法输出"
}
}
// 定义鉴别原型属性方法
function hasPrototypeProperty(object,variable){
return !object.hasOwnProperty(variable) && (variable in object);
}
var person = new Person("随笔川迹","itclancoder");
console.log(person.name,person.publicNum,person.userDefined(),person.init.city,person.init.job,person.init.method());
console.log(hasPrototypeProperty(person,"name"));
console.log(person.hasOwnProperty("name"));
console.log(person.hasOwnProperty("age")); // hasOwnProperty只能检测自定义属性,false
console.log(hasPrototypeProperty(person,"age"));
console.log("age" in person); // true,in操做符既能检测自定义属性也能检测出原型下的属性复制代码
控制台输出结果以下:
使用对象字面量形式改写原型对象会改变构造函数的属性,指向问题
function Person(name,job){
this.name = name;
this.job = job;
}
Person.prototype.init = {
name:"小川",
job:"码男",
outName:function(){
return this.name;
},
outJob:function(){
return this.job;
}
}
var person = new Person("随笔川迹","coder");
console.log(person.name,person.job);
console.log(person.init.outName(),person.init.outJob());
console.log(person.constructor === Person); // true
console.log(person instanceof Person); // true复制代码
控制台输出结果以下:
function Person(name,job){
this.name = name;
this.job = job;
}
// 使用对象字面量形式改写原型对象
Person.prototype ={
name:"小川",
job:"码男",
outName:function(){
return this.name;
},
outJob:function(){
return this.job;
}
}
var person = new Person("随笔川迹","coder");
console.log(person.name,person.job);
console.log(person.outName(),person.outJob());
console.log(person.constructor === Person); // false
console.log(person.constructor === Object); // true
console.log(person instanceof Person); // true复制代码
控制台输出结果以下
正确写法
:当一个函数被建立时,它的prototype属性也被建立,且该原型对象的constructor属性指向该函数,当使用对象字面量形式改写原型对象Person.prototype时,则该constructor指向指向的是Object,为了不这一点,须要手动的改写原型对象手动设置constructor属性,更改以下:
function Person(name,job){
this.name = name;
this.job = job;
}
// 使用对象字面量形式改写原型独享
Person.prototype ={
constructor:Person, // 手动指定这里的指向该构造函数
outName:function(){
return this.name;
},
outJob:function(){
return this.job;
}
}
var person = new Person("随笔川迹","coder");
console.log(person.name,person.job);
console.log(person.outName(),person.outJob());
console.log(person.constructor === Person); // true
console.log(person.constructor === Object); // false
console.log(person instanceof Person); // true复制代码
在原有的对象基础上上,经过prototype进行额外的,封装,拓展
实例代码以下:
// 经过原型prototype对现有的内容进行额外的拓展,给数组Array添加方法
Array.prototype.sum = function(){
return this.reduce(function(m,n){
return m+n;
})
}
var arrNums = [1,2,3,4,5,6,7,8,9,10];
var result = arrNums.sum();
console.log("arrNums的和为",result); // arrNums的和为 55
// 给String添加额外的方法
String.prototype.capitalize = function(){
return this.charAt(0).toUpperCase()+this.substring(1);
}
var message = "suibichuanji hello";
console.log(message.capitalize()); // Suibichuanji hello复制代码
控制台输出以下:
原型中的属性优先级
示例代码以下所示
var arr = [];
arr.name = "随笔川迹";
Array.prototype.name = "川流不息";
console.log(arr.name); // 随笔川迹复制代码
控制台输出以下
构造函数的自定义属性优先于原型属性
(能够把构造函数理解为内联样式),而原型属性或者原型方法能够看作是class)
面向对象小实例
效果图:
css层叠样式代码
*{
padding:0;
margin:0;
}
#wrap{
width:300px;
height:260px;
border:1px solid #ccc;
margin:0 auto;
}
#wrap:after{
content:"";
height:0;
display:block;
clear:both;
zoom:1;
}
#wrap div{
height:100%;
display:none;
text-indent:10px;
background:#2263A3;
color:#fff;
}
#wrap div:nth-of-type(1){
display:block;
}
#wrap input.active{
background:#2263A3;
color:#fff;
}
#wrap input{
width:100px;
height:30px;
background:#abcdef;
text-align:center;
line-height:30px;
outline:none;
border:none;
float:left;
cursor:pointer;
margin-bottom:30px;
}复制代码
html结构
<div id="wrap">
<input type="button" class="active" value="公告" name="">
<input type="button" value="规则" name="">
<input type="button" value="论坛" name="">
<div>欢迎关注微信itclancoder公众号</div>
<div>点击右上方蓝字便可关注</div>
<div>什么都没有留下</div>
</div>复制代码
js代码
// 普通写法
// 获取元素
var oWrap = document.querySelector("#wrap");
var aBtns = oWrap.getElementsByTagName("input");
var aDivs = oWrap.getElementsByTagName("div");
// 循环
for(var i = 0;i<aBtns.length;i++){
aBtns[i].index = i; //添加索引
aBtns[i].onclick = function(){ // 添加事件
for(var j = 0;j<aBtns.length;j++){
aBtns[j].className = ""; // 先去除掉全部的className
aDivs[j].style.display = "none"; // 先隐藏
}
// 添加class
this.className = "active";
aDivs[this.index].style.display = "block"; // 内容显示
}
}复制代码
jquery写法
$(function(){
$(" #wrap input").click(function(){
var $index = $(this).index(); // 获取索引
$(this).addClass("active").siblings().removeClass("active");
$("#wrap div").eq($index).show().siblings("div").hide();
})
})复制代码
面向对象写法
function 构造函数(){
this.属性 // 对象.属性
}
构造函数.原型.方法 = function(){}
var 对象1 = new 构造函数();
对象1.方法();复制代码
面向对象选项卡代码示例以下所示
window.onload = function(){
var t = new TabSelect(); // 实例化对象
t.init(); // 实例化对象调用方法
}
// 声明构造函数
function TabSelect(){
// this == TabSelect,添加自定义属性,获取对象
this.oWrap = document.querySelector("#wrap");
this.aBtns = this.oWrap.getElementsByTagName("input");
this.aDivs = this.oWrap.getElementsByTagName("div");
}
// 构造函数的原型下添加方法(初始化)
TabSelect.prototype.init = function(){
var that = this; // 注意这里的this指向,是TabSelect,用一个局部变量将this给存储起来,其实这种方式是根据词法做用域,闭包的方式来解决的
for(var i = 0;i<this.aBtns.length;i++){
this.aBtns[i].index = i; //添加索引
this.aBtns[i].onclick = function(){
that.change(this);
//console.log(this);匿名函数里面的this指向的是input按钮元素
};
}
}
// 构造器函数原型对象下添加方法
TabSelect.prototype.change = function(obj){
//console.log(obj); // input点击按钮元素
for(var j = 0;j<this.aBtns.length;j++){
this.aBtns[j].className = ""; // 先去除掉全部的className
this.aDivs[j].style.display = "none"; // 先隐藏
}
// 添加class
obj.className = "active";
this.aDivs[obj.index].style.display = "block"; // 内容显示
}复制代码
小结
:
本例从普通写法,jquery写法,在到最后面向对象选项卡写法,完成一简易的选项卡,其中jquery写法最为简单,容易懂,可是这里我只是为了尝试用面向对象的思想去写应用,实际开发中,不管哪一种方式,只要能实现出来就行,从普通的写法,也就是原生js写法,到面向对象写法,能够看出首先经过变形,把局部的功能给拎出来,封装成一个函数,这个过程当中尽可能不要出现函数嵌套函数,由于this是指向是个使人头疼的问题,能够有全局变量,window.onload里面尽可能是实例化对象,与对象的调用的方式,把不是赋值的语句单独放到一个函数当中(好比上文中的获取元素,给TabSelect添加自定义属性),最后就是改变this指向问题,事件或者定时器,让面向对象中的this指向该对象
总结
:
本篇主要是本人对构造器函数与原型对象的一点点理解,new操做符调用的函数为构造函,功能上与内置的函数并无多大的区别,构造函数首字母大写用来区分普通函数仍是构造函数,构造函数中的this指向该实例化的构造函数,主要是建立多个共享特定属性和行为的对象,用于建立模板,做为饼干工具,而原型对象主要是改写构造函数(对象)下面的方法和属性,让公用方法或者属性在内存中存在一份,解决了当建立多个实例化对象时,重复的建立构造函数的过程,目的是减小内存开销,提升性能,还有就是原型在原有的对象基础上,经过prototype进行额外的,封装,拓展,原型是挂载在构造函数下面的,以及最后用面向对象写法实现了一个小实例,其实设计模式中的原型模式就是面向对象的写法,杀鸡焉用牛刀,适合本身的才是好方法,面向对象的写法,对于简单的实例,面向过程就能够了,对于复杂的实例,什么组件,插件,我以为都是面向对象的应用,关于面向对象,我也只是略知皮毛,在不断的学习当中...
如下是本篇提点概要