一、複習
1.1複習上下文
函式的呼叫方式 |
上下文 |
fun() |
window |
obj.fun() |
obj |
box.onclick = fun |
box |
setInterval(fun,1000) setTimeout(fun,1000) |
window |
array[8]() |
array |
new fun() |
祕密建立的新物件 |
要看清楚最終的函式呼叫者是誰。
IIFE也被當做函式直接執行,IIFE的this都是window物件
函式的arguments是類陣列物件,比如傳入的第0項引數是函式,讓它執行:arguments[0](),函式中的上下文是arguments物件。還要知道函式的length和arguments.length的區別。
1.2建構函式
當一個函式用new運算子呼叫時,此時這個函式叫“建構函式”(constructor)
建構函式四步走:
建立一個新空物件
this繫結給這個空物件
執行語句
返回這個物件
new 出來的所有物件擁有相同屬性群,所以可以把建構函式當做類,被new出來的物件都是這個函式的例項。
當建構函式裡面有return語句時,注意:
l 如果是return基本型別值,無視這個return,還是返回祕密建立的物件
l 如果是return引用型別值,則不返回原來的物件,而是返回引用型別值
原型鏈的知識:
建構函式的prototype屬性就是例項的__proto__屬性。對於一個物件來說,__proto__這個屬性叫做自己的原型物件,就是自己的原型鏈。這個原型鏈有查詢功能,當obj.haha時,obj身上沒有haha屬性,此時會查詢obj.__proto__身上有沒有haha屬性,並且會繼續查詢obj.__proto__.__proto__屬性身上有沒有haha屬性……
二、原型鏈
Object.prototype是所有物件原型鏈的終點
任何物件都有原型物件(__proto__),最終指向Object.prototype,但是Object.prototype很特殊,它的__proto__終點是null。
JS中,物件是物件、函式、陣列、正則等是物件,所有引用型別值都是物件,它們都有__proto__屬性。
甚至,xiaoming的__proto__也是一個物件,這個物件也有__proto__。
var obj = {} console.log(obj.__proto__ === Object.prototype); //true
Object()是內建的建構函式,所有的物件,可以認為是Object new出來的
var obj = new Object(); obj.name = "小明"; obj.age = 12; console.log(obj) console.log(obj.__proto__ === Object.prototype);
“{}”物件的__proto__都指向Object.prototype,因為它都是Object new出來的。
function People(name,age){ this.name = name; this.age = age; } var xiaoming = new People("小明",12); console.log(xiaoming.__proto__ === People.prototype); //true console.log(xiaoming.__proto__.__proto__ === Object.prototype); //true console.log(xiaoming.__proto__.__proto__.__proto__);; //null
綜上所述,xiaoming的原型People.prototype也有原型,小明完整的家譜:
Object.prototype是唯一一個沒有__proto__的物件,其他所有物件、函式、陣列、正則等都有__proto__
三、內建建構函式
JS內建了很多建構函式,它們也稱為“基本型別值”、“引用型別值”的包裝類。
3.1引用型別值的建構函式
引用型別值的建構函式:Object()、Function()、Array()、RegExp()
3.1.1 Object()函式
Object()是內建的建構函式,可以直接 new它,返回一個空物件,可以給這個空物件新增屬性:
var obj = new Object(); obj.name = "小明"; obj.age = 12; console.log(obj) console.log(obj.__proto__ === Object.prototype);
等價於:
var obj = { name:"小明", age:12 }
3.1.2 Function()函式
所有function字面量都是它的例項
function sum(a,b){ alert(a+b); } sum(3,5);
等價於:
var sum = new Function("a","b","alert(a+b);alert('算完啦!')"); sum(4,5)
在new Function的時候,先羅列所有形參列表,最後一個引數是函式體,注意,引數都是字串。
console.log(sum.__proto__ === Function.prototype)
任何函式都是Function()建構函式的例項,Object也是Function的例項,Function自己也是自己的例項。Function自己new自己。
console.log(Object.__proto__ === Function.prototype); //true console.log(Function.__proto__ === Function.prototype); //true
Function和Object的關係:
console.log(Function.prototype.__proto__ === Object.prototype); console.log(Function.__proto__.__proto__ === Object.prototype); console.log(Function.__proto__.__proto__ === Object.__proto__.__proto__); console.log(Function.__proto__ === Object.__proto__);
3.1.3 Array()函式
Array()是系統內建的陣列建構函式,任何的陣列都是Array() new出來的。
var arr = new Array(); arr[0] = 100; arr[1] = 200; arr[2] = 300; console.log(arr);
等價於:
var arr = [100,200,300];
函式能填引數,表示陣列長度,但陣列還是空陣列:
var arr = new Array(8);
常用的陣列方法,都定義在Array.prototype身上。
var arr = [3,3,4,4]; console.log(arr.__proto__ === Array.prototype); //true console.log(arr.__proto__.__proto__ === Object.prototype); //true
3.1.4 RegExp()函式
任何正規表示式RegExp()函式的例項。
var reg = /\d/g; //等價於 var reg = new RegExp("d","g");
console.log(reg.__proto__ === RegExp.prototype); //true console.log(reg.__proto__.__proto__ === Object.prototype); //true
3.2基本型別值“包裝類”
【Number()、String()、Boolean()】
基本型別值的建構函式,被稱為“包裝類”。JS體系為了完整,所以就人為造出了這三個包裝類,沒有什麼用。
3.2.1 Number()函式
用於建立數字物件:
var a = new Number(3); console.log(a) console.log(typeof a)
用內建建構函式建立數字的時候,得到一個物件,這物件的原始值屬性是:
[[PrimitiveValue]]: 3 //這個屬性不可被列舉。
它和字面量建立數字的區別:
var a = new Number(3); var b = 3; console.log(a == b); //true console.log(a === b); //false
用Number()建立的物件,可以參與數學運算:
Number的例項是一個物件,但這個物件一旦引數運算,將變為普通Number型別
var a = new Number(3); a = a * 3 console.log(a) console.log(typeof a)
Number()也可以用來把各種值轉換為陣列(不能轉就是NaN),不需要用new呼叫。
console.log(Number("12")); //12 console.log(Number("12年")); //NaN console.log(Number("")); //0 console.log(Number(false)); //0 console.log(Number(true)); //1 console.log(Number({})); //NaN console.log(Number([])); //0 console.log(Number([1,2])); //NaN
任何需要轉為數字的隱式轉換,實際上就是在呼叫Number函式。
3.2.2 String()函式
var str = new String("我喜歡你"); console.log(str)
String()也可以用來轉換:
console.log(String(123)); //"123" console.log(String(true)); //"true" console.log(String([])); //"" console.log(String([1,2,3])); //"1,2,3" console.log(String(NaN)); //"NaN" console.log(String({})); //"[Object Object]"
3.2.3 Boolean()函式
不管值是false還是true,都能通過if的驗證,都是true。
var b = new Boolean(false); console.log(b) console.log(typeof b); if(b){ alert("真的"); }
3.3內建建構函式之間的關係
就三句話,死記:
1、“{}”物件是被Object new出來的。所以它的__proto__就會指向Object.prototype。
2、任何函式都是Function new出來的例項,所以只要它是函式(建構函式也是函式),它的__proto__就會指向Function.prototype。
3、Function是所有建構函式的媽,它自己也是自己的媽。
小明不是Object new出來的,是People new的,它的__proto__指向People.prototype。
console.log(Object.__proto__.__proto__ === Object.prototype); //true console.log(Function.__proto__.__proto__ === Object.prototype); //true console.log(Object.__proto__ === Function.prototype); //true console.log(Function.__proto__ === Function.prototype); //true console.log(Array.__proto__ === Function.prototype); //true console.log(RegExp.__proto__ === Function.prototype); //true console.log(Number.__proto__ === Function.prototype); //true console.log(String.__proto__ === Function.prototype); //true console.log(Boolean.__proto__ === Function.prototype); //true
四、相關的方法、屬性、運算子
4.1 hasOwnProperty()方法
返回布林值(true/false),用來檢測某屬性、某方法是不是在自己身上。
function People(name){ this.name = name; } People.prototype.sayHello = function(){ alert("你好"); } var xiaoming = new People("小明"); console.log(xiaoming.hasOwnProperty("name")); //true console.log(xiaoming.hasOwnProperty("sayHello")); //false console.log(xiaoming.hasOwnProperty("toString")); //false
4.2 in運算子
返回布林值(true/false),in運算子可以檢查某個物件有沒有能力呼叫某屬性、某方法,而不管這個屬性或方法是否定義在自己身上,還是原型身上。
字串 in 物件 function People(name){ this.name = name; } People.prototype.sayHello = function(){ alert("你好"); } var xiaoming = new People("小明"); console.log("name" in xiaoming); //true console.log("sayHello" in xiaoming); //true console.log("toString" in xiaoming); //true
4.3 constructor屬性
每一個函式的prototype物件都有一個constructor屬性,指向建構函式。
function People(name){ this.name = name; } var xiaoming = new People("小明"); console.log(People.prototype) console.log(People.prototype.constructor === People); //true console.log(People.prototype.hasOwnProperty("constructor")); //true console.log(xiaoming.constructor === People); //true console.log(xiaoming.hasOwnProperty("constructor")); //false
4.4 instanceof運算子
返回布林值,用來檢查某個物件是不是某個函式的例項
o instanceof F
如果F.prototype在o的原型鏈上,返回true,否則返回false
function People(name){ this.name = name; } var xiaoming = new People("小明"); function Dog(){ } console.log(xiaoming instanceof People); //true console.log(xiaoming instanceof Object); //true console.log(xiaoming instanceof Dog); //false
小題目:
console.log(Object instanceof Object); //true console.log(Function instanceof Function); //true console.log(Function instanceof Object); //true console.log(Number instanceof Function); //true console.log(Number instanceof Number); //false
我們發現object.prototype是所有物件原型鏈的終點,所以我敢說任何原型X一定true。
x instanceof Object Function的prototype出現在自己的__proto__線上,所以是true console.log(Function instanceof Function); //true
五、練習題
5.1題目1
function A(){} function B(){ return new A(); //返回了引用型別值 } A.prototype = B(); //返回了一個A的例項1 B.prototype = new B();//返回了一個A的例項2 var a = new A(); //返回了一個A的例項,賦給a var b = new B(); //返回了一個A的例項,賦給b console.log(a.__proto__ == b.__proto__); console.log(a instanceof A); //true console.log(a instanceof B); // false console.log(b instanceof A); //true console.log(b instanceof B); //false
5.2題目2
[].constructor.constructor.constructor.constructor.constructor.constructor.constructor.constructor.constructor.constructor.constructor.constructor.constructor.constructor.constructor.constructor.constructor.constructor.constructor.constructor.constructor.constructor.constructor.constructor.constructor === Function
答案:true。
六、繼承
6.1什麼是繼承
計算機領域,關注兩個類的關係,就是看屬性群之間的關係。
比如:人類和狗狗類屬性群,難免會有交集,但是不完全重合,此時可以認為兩個類沒有任何關係。
看看人和學生的屬性群:
l 小學生肯定是人,人的屬性學生全有,人能做的事情學生都能做。
l 但是反過來,小學生的屬性和能力,人不一定有。
我們說:
“學生”細化了、精分了、更具體了“人”。
“學生”的例項要比“人”的例項少。
“學生”一定是人,但“人”不一定是學生。
術語上,我們稱“學生類”繼承(extend)“人類”。人類叫“父類(超類)”,學生類叫“子類”。
人又繼承誰呢?人繼承哺乳動物,哺乳動物繼承“生物”...
此時計算機中小學生類繼承(extend)人類。
A繼承了B,此時要意識到:
A擁有B的所有屬性和方法
A的屬性群比B大
A豐富了B,A把B變得更具體,範圍更小。
6.2 JavaScript實現繼承
JavaScript實現兩個類:People類,Student類,要求People類擁有的屬性和方法,Student類的例項也要擁有People的屬性和方法。Student還能豐富自己類的屬性和方法,很簡單,只要求巧妙設計原型鏈。
//人類 function People(name,age,sex){ this.name = name; this.age = age; this.sex = sex; } People.prototype.sayHello = function(){ alert("你好,我是" + this.name); } People.prototype.sing = function(){ alert("都拉米發騷啦稀~~~"); } // 學生類 function Student(name,age,sex,id,banji,socre){ People.apply(this, arguments) // this.name = name; // this.age = age; // this.sex = sex; this.id = id; this.banji = banji; this.socre = socre; } //下面這條語句可以實現繼承 Student.prototype = new People(); Student.prototype.study = function(){ alert(this.name + "在學習!"); } var xiaoming = new Student("小明",12,"男",100001,"初三一班", 100) xiaoming.study(); xiaoming.sayHello(); xiaoming.sing();
注意:紅色語句一定要出現在Student.prototype.*** = function(){}之前。
jQuery創始人John Resig寫了一個小包,20多行程式碼,解決了JS繼承噁心的問題。
https://johnresig.com/blog/simple-javascript-inheritance/
引包之後,這個包改變我們建立JS類的方式(和jQuery一樣改變了寫JS的方式)
//人類 var People = Class.extend({ init : function(name,age,sex){ this.name = name; this.age = age; this.sex = sex; }, sayHello:function(){ alert("你好,我是" + this.name); }, sing:function(){ alert("都拉米發騷啦希~~~"); } }) //學生類 var Student = People.extend({ init : function(name,age,sex,id,banji,socre){ this._super(name,age,sex); //繼承父類的屬性 // this.name = name; // this.age = age; // this.sex = sex; this.id = id; this.banji = banji; this.socre = socre; }, study :function(){ alert(this.name + "在學習!"); } }) var xiaoming = new Student("小明",12,"男",100001,"初三一班", 100) console.log(xiaoming) xiaoming.study(); xiaoming.sing();
七、上升到物件導向
物件導向是一種程式設計思想,兩個字就能概括:自治(自己管理自己),深入理解,就是封裝。每個物件個體僅需要管理自己即可。
物件導向初學階段,當你遇見大量的結構、功能、性質、什麼都一樣的物件的時候,立刻想到用物件導向技術。
現在要給大家一個思維定式,物件導向的時候怎麼程式設計:
思考初程式中有哪些類,在前期我們的業務僅僅只有一個類,後期類會有多個。
每個類有哪些方法和屬性,就是他們自己有什麼功能
每個類之間如何通訊、互動資料、此時就要用到設計模式,比如中介者模式、釋出訂閱模式(觀察者模式)。
這個類怎麼進行單元測試,如果保證自己這個類魯棒,每個類都魯棒了,整個程式就魯棒。
我們之前的程式設計叫“程式導向”,現在是“物件導向”程式設計(OO)
7.1物件導向-紅綠燈(案例)
考慮一個問題,頁面上要製作一個效果:100個紅綠燈,點選某一個紅綠燈,從紅燈變黃燈,再次點選從黃燈變綠燈,再次點選就綠燈變紅燈...
有一個訊號量,點選按鈕之後,訊號量變化0、1、2、0、1、2、0、1、2...然後讓div的background-position進行變化,你要寫100個訊號量、100個盒子、100個事件。
頁面上出現的東西就是紅綠燈,而且它們擁有相同的樣子、性質、功能,所以可以讓紅綠燈設計成為一個類。
每一個類負責什麼:①狀態量 ②DOM元素。
每個JS物件中有兩個屬性,一個是狀態屬性,另一個是DOM物件
簡單的說,DOM物件現在成為JS物件的一個屬性。
這個類有哪些屬性?
DOM屬性
顏色屬性
哪些方法?
初始化方法 init()
換顏色方法 changeToColor()
繫結事件方法 bindEvent()
第一步:DOM結構和CSS樣式,確保放一個div元素能看見燈出來了。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Document</title> <style type="text/css"> *{ margin: 0; padding: 0;} div.honglvdeng{ position: relative; width: 140px; height: 328px; background: url(./images/honglvdeng.jpg); } </style> </head> <body> <div id="box"> <div class="honglvdeng"></div> </div> </body> </html>
//第二步:建立紅綠燈類 //new Honglvdeng()時,會執行建構函式中的語句 //所以在建構函式中建立一個DOM物件,然後讓它上樹 function Honglvdeng(){ //每一個類中有兩個屬性:狀態量、DOM this.dom = null; //狀態屬性 this.state = 0; //初始化方法 this.init(); //事件監聽方法 this.bindEvent(); } //為了程式美觀,整潔,好更改和可插拔性高,方便維護,將DOM和上樹語句都寫在初始化方法中 Honglvdeng.prototype.init = function(){ //建立DOM this.dom = document.createElement('div'); //給DOM新增類名 this.dom.className = 'honglvdeng'; //上樹 document.getElementById("box").appendChild(this.dom); } //第三步:新增事件監聽 Honglvdeng.prototype.bindEvent = function(){ //備份this,因為事件監聽裡面的this表示dom元素本身 var self = this; this.dom.onmouseenter = function(){ // 改變訊號量 // self.state++; // if(self.state > 2) self.state = 0 //判斷簡寫 self.state = ++self.state % 3; self.dom.style.backgroundPositionX = -155 * self.state +'px'; } } //第四步:例項化100個紅綠燈 var count = 100; while(count--){ new Honglvdeng(); }