在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進行額外的,封裝,拓展,原型是掛載在構造函數下面的,以及最後用面向對象寫法實現了一個小實例,其實設計模式中的原型模式就是面向對象的寫法,殺雞焉用牛刀,適合本身的纔是好方法,面向對象的寫法,對於簡單的實例,面向過程就能夠了,對於複雜的實例,什麼組件,插件,我以爲都是面向對象的應用,關於面向對象,我也只是略知皮毛,在不斷的學習當中...
如下是本篇提點概要