一、對象(Object) 1.1 認識對象 對象在JS中狹義對象、廣義對象兩種。 廣義:相當於巨集觀概念,是狹義內容的升華,高度的提升,範圍的拓展。狹義:相當於微觀概念,什麼是“狹”?因為內容狹隘具體,範圍窄所以稱為“狹” l 狹義對象 就是用{}這種字面量的形式定義的對象,它是一組屬性的無序集合 上 ...
一、對象(Object)
1.1 認識對象
對象在JS中狹義對象、廣義對象兩種。
廣義:相當於巨集觀概念,是狹義內容的升華,高度的提升,範圍的拓展。
狹義:相當於微觀概念,什麼是“狹”?因為內容狹隘具體,範圍窄所以稱為“狹”
l 狹義對象
就是用{}這種字面量的形式定義的對象,它是一組屬性的無序集合
var obj = { name : "小明", age : 12, sex : "男", hobby : ["足球","刺繡","打麻將"] }
上面這個對象,表示一個“人”裡面有4個屬性,換句話說,這個對象裡面除了4個屬性,別的什麼都沒有。
比如不用對象,而用數組來存儲一組剛纔的值:
var arr = ["小明",12,"男",["足球","刺繡","打麻將"]] console.log(arr)
數組只能存儲“值”,不能存儲“鍵”。
換句話說,數組中的值“語義”不詳。對象除了能存儲值,還能存儲值的“語義”,術語叫“鍵(key)”
其實,對象就是一組值和值的語義的封裝。
【廣義對象】
DOM元素是對象,但是和剛剛說的“狹義對象裡面只有一組值,別的什麼都沒有”不同
var oBox = document.getElementById("box"); //得到一個DOM對象 oBox.xingming = "小明"; oBox.age = 12; oBox.sex = "男"; oBox.hobby = ["足球","刺繡","打麻將"]; console.log(oBox.hobby); console.log(typeof oBox); //object
通過DOM方法得到一個DOM對象,此時可以通過“.”點語法,給這個對象添加屬性,用oBox訪問age。
此時這個對象不僅僅只有4個屬性,還有別的,因為oBox畢竟有一個HTML標簽實體在頁面上。
數組也是對象:
var arr = [1,2,3,4,5]; //也可以通過“點”語法,給數組添加屬性 arr.xingming = "小明"; arr.age = 12; arr.sex = "男"; console.log(typeof arr); //object console.log(arr.xingming); //小明
說明數組有對象的一切特征,能添加屬性,但是你不能說這個數組只有name、age、sex三個屬性,別的什麼都沒有,畢竟它有一組數。
函數也是對象:
typeof檢測類型返回結果是function不是object,這是系統規定,但function也是object對象,後面詳解。
function fun(){ } console.log(typeof fun); fun.xingming = "小明"; fun.age = 12; fun.sex = "男"; console.log(fun.xingming)
此時對象添加了4個屬性,但是你不能說這個fun對象只有4個屬性別的什麼都沒有,因為它畢竟是一個函數。能夠圓括弧執行。
正則表達式也是對象:
var regexp = /\d/g; console.log(typeof regexp) regexp.xingming = "小明"; regexp.age = 12; regexp.sex = "男"; console.log(regexp.xingming)
能添加屬性成功,但是你不能說只有3個屬性,畢竟是一個正則。
系統內置的所有引用類型值,都是對象,都能添加自定義屬性,並且能夠訪問這些屬性:
function 函數 Array 數組 RegExp 正則表達式 DOM元素 window、document、Math、Date對象 Number()、Sting()內置包裝構造函數
這些對象除了一組屬性之外,還有其他的東西。比如數組還有一組值;比如函數還有一組語句,能夠圓括弧執行。
什麼不是對象?就是系統的基本類型:
數字不能添加屬性,因為數字是基本類型,不是對象
var a = 100; //試圖添加屬性 a.xingming = "小明"; a.age = 12; console.log(a.age); //undefined
這幾天研究的就是對象,只有JS提供(除了屬性還有別的東西)的對象,我們不能創建,也就是說,對開發者而言,我們只能創建狹義對象。
那麼到底有什麼性質,就稱它為是對象?
能添加屬性,特別的微觀層面,只要這個東西能存放在堆記憶體中,就可以認為是一個對象。
1.2對象的方法
如果一個對象的屬性是函數,我們稱這個屬性叫這個對象的方法(methods)
當一個函數當作對象的方法被調用時,這個函數裡面的this表示這個對象。
//下麵這個對象有一個屬性,叫sayHello,它的值是函數,所以可以把它叫做obj的方法 var obj = { xingming : "小明", age : 12, sex :"男", sayHello : function(){ alert("你好,我是" + this.xingming +",今年" + this.age) } }; obj.sayHello()
現在調用sayHello函數時,是通過obj打點調用的,所以這個sayHello函數的上下文就是obj對象。
即sayHello函數內部的this指向obj。
但是千萬不要認為寫在對象裡面的函數,上下文就是這個對象!!!
比如:
var xingming = "小紅"; var age = 18; var obj = { xingming : "小明", age : 12, sex : "男", sayHello : function(){ alert("你好,我是" + this.xingming +",今年" + this.age) } }; var fn = obj.sayHello; fn(); //直接()調用,不是對象打點調用,所以this上下文是window
函數的上下文是什麼,取決於函數是怎麼調用的,而不是函數如何定義。
函數的上下文是函數的調用時表現的性質,不是函數定義時寫死的性質。
1.3對象和JSON的區別
JSON(JavaScript Object Notation, JS 對象標記) 是一種輕量級的數據交換格式,JS對象表示法。
JSON是JS對象的嚴格子集。
區別就是引號:JSON要求所有的鍵都必須加引號,而JS對象實際上不要求加引號。
這是一個標準的JSON:
var obj = { "name" : "小明", "age" : 12, "sex" : "男" }
實際上不加引號也合法: var obj = { name : "小明", age : 12, sex : "男" }
為什麼JSON規定要加上引號呢?因為JSON是一個數據交互格式,它是前端和PHP、Java等後臺語言的信息交換媒介,後臺工程師可以從資料庫得到數據,組建JSON,前臺通過Ajax拿到這個JSON之後,解析JSON渲染頁面。
所以是其他語言要求這個JSON有引號,否則其他語言會報錯,不是JS要求的,JSON天生為通信而生!!
但是,有一種必須加引號,就是不符合命名規範的鍵名,必須加引號,否則報錯。
比如下麵的鍵名都不符合標識符的命名規範,必須加引號:
var obj = { "-" : 18, "@#$" : 20, "2018" : 100, "哈哈" : 200, "key" : 888, "true" : 999 } console.log(obj["-"]); //訪問屬性時,也要加引號,表示鍵名 console.log(obj["@#$"]); console.log(obj["2018"]); console.log(obj.哈哈); console.log(obj["哈哈"]);
特別的是,如果用一個變數存儲一個key,此時必須用[]枚舉,並且[]不能加引號
var key = 2018; var k = 3 < 8; //true console.log(obj.key); //888 點語法只能以字元串形式訪問對象的屬性,key不能是變數 console.log(obj["key"]); //888 console.log(obj[key]); //2018的100,實際上讀取的是obj["2018"],[]會隱式轉換為字元串 console.log(obj[k]); //999
1.4全局變數是window對象的屬性
var a = 100; var b = 200; var c = 300; var d = 400; alert(window.a) alert(window.b) alert(window.c) alert(window.d)
二、函數的上下文(context)
所謂的上下文就是指函數裡面的this是誰。就說“函數的上下文是什麼”
函數中的this是誰,看這個函數是如何調用的,而不是看這個函數如何定義的。
舉個例子:踢足球的時候,球進對方的門,看誰最後碰到球,我方球員射門的那一腳踢到了門柱,反彈給對方球員進門,就是烏龍球。
2.1規則1:函數直接圓括弧調用,上下文是window對象
直接兩個字,表示這個函數代號之前,沒有任何標識符,沒有小圓點,沒有方括弧。通常從數組、對象中“提”出函數的操作(把函數賦給變數):
在obj對象中定義一個函數,叫fun,這個是obj的屬性:
var a = 888; var obj = { a : 100, fun : function(){ alert(this.a); } }
如直接對象打點調用函數:此時彈出100,說明函數上下文是obj對象本身。
obj.fun();
但如果這個函數被一個變數接收(讓變數直接指向這個對象的方法)
var fn = obj.fun; fn(); //這個叫()直接運行
然後調用,函數的this將是window對象,this.a就是訪問全局的a變數是888
l 註意,所有IIFE,都屬於直接調用範圍,裡面的this都是window。
不管IIFE寫的有多深,不管所在的環境多複雜,上下文一律是window對象。
比如下麵obj對象中的b,是IIFE:
var a = 888; var obj = { a : 100, b : (function(){ alert(this.a); })() } obj.b; //888
小題目:
var xiaoming = { name :"小明", age : 23, chengwei: (function(){ return this.age >= 18 ? "先生" : "小朋友" })() } alert(xiaoming.name + xiaoming.chengwei)
2.2規則2:定時器直接調用函數,上下文是window對象
這個fn的最終調用者是定時器
var a = 100; function fn(){ console.log(this.a++); } setInterval(fn,1000)
註意臨門一腳誰踢的,是誰最終調用那個函數,比如:
var a = 100; var obj = { a : 200, fn : function(){ console.log(this.a++); } } setInterval(obj.fn, 1000); //obj.fn沒有()執行,是定時器調用的
var a = 100; var obj = { a : 200, fn : function(){ console.log(this.a++); } } setInterval(function(){ obj.fn(); //obj.fn()直接調用,上下文的this是obj }, 1000);
2.3規則3:DOM事件處理函數的this,指的是觸發事件的這個元素
var box1 = document.getElementById("box1"); var box2 = document.getElementById("box2"); var box3 = document.getElementById("box3"); var btn1 = document.getElementById("btn1"); var btn2 = document.getElementById("btn2"); var btn3 = document.getElementById("btn3"); function setColor(){ this.style.backgroundColor = 'red'; } box1.onclick = setColor; box2.onclick = setColor; box3.onclick = setColor; btn1.onclick = setColor; btn2.onclick = setColor; btn3.onclick = setColor;
此時點擊上面的元素,上面元素就是函數的上下文。
var box1 = document.getElementById("box1"); box1.onclick = function(){ var self = this; //備份this setTimeout(function(){ //這裡this指向window,所以先在外面備份this,再用 self.style.background = 'red'; },1000) }
2.4規則4:call()和apply()設置函數的上下文
普通函數function的this是指向window。
function fun(){ console.log(this); } fun();
我們說函數的上下文看函數是如何調用的,但任何函數可以通過call()和apply()這兩個內置方法來調用函數的同時,還能改變它的this指向。
l 公式:函數將以某對象為上下文運行。
函數.call(某對象);
函數.apply(某對象);
var oBox = document.getElementById('box'); function fun(){ console.log(this); this.style.backgroundColor = 'red'; } //call和apply作用都一樣,有兩個作用: //1、執行fun函數 //2、改變fun函數的this指向div fun.call(oBox) fun.apply(oBox)
call、apply功能是一樣的,都是讓函數調用,並且設置函數this指向誰。區別在於函數傳遞參數的語法不同。
l call需要用逗號隔開羅列所有參數
l apply是把所有參數寫在數組裡面,即使只有一個參數,也必須寫在數組中。
var obj = { a:100 } function fun(a,b,c){ console.log(a,b,c) console.log(this); } fun.call(obj,10,20,30); fun.apply(obj,[10,20,30]);
比如有一個函數叫變性函數(bianxing),它能夠將自己上下文的sex屬性改變。
此時小明對象(xiaoming),迫切要變性,xiaoming就成為bianxing的上下文:
function bianxing(){ if(this.sex == '男'){ this.sex = '女' }else{ this.sex = '男' } } var xiaoming = { name : "小明", sex : "男", // bianxing : bianxing } // xiaoming.bianxing() bianxing.call(xiaoming); bianxing.apply(xiaoming); console.log(xiaoming)
call和apply方法幫我們做了兩件事:
l 調用bianxing函數
l 改變bianxing函數的this指向為xiaoming
小題目:
apply通常用於一個函數調用另一個函數的時,將自己所有的參數都傳入一個函數:
function fun1(){ fun2.apply(obj, arguments) } function fun2(a,b,c){ console.log(this === obj);//true console.log(a) console.log(b) console.log(c) } var obj = {} fun1("蘋果","西瓜","哈密瓜")
比如要求數組中的最大值
// Math.max(); 方法可以返回所有參數的最大值 // Math.min(); 方法可以返回所有參數的最小值 console.log(Math.max(312,432,64,654,88,213,888,999)); console.log(Math.min(312,432,64,654,88,213,888,999));
但是,如果給你一個數組呢?此時迫切要將數組拆解為裸寫的一個個的參數。
那麼apply足夠好用,這裡不能用call,因為call是裸寫參數,不是傳數組。
var arr = [31,88,543,999,777,42] console.log(Math.max.apply(null, arr)) console.log(Math.min.apply(null, arr))
2.5規則5:從對象或數組中枚舉的函數,上下文是這個對象或數組
來看一個最基本的模型,就是對象中的方法,方法中出現this。
如果調用是:對象.方法(),此時函數中this就是指向這個對象。
var obj = { a : 100, b : function(){ alert(this.a) } } obj.b(); //100
數組也一樣,如果一樣函數是從數組中枚舉的,加圓括弧執行,數組[0](); 此時上下文就是數組
var arr = [ "A", "B", function(){ alert(this.length) } ] arr[2](); //輸出3,這寫法是從數組中枚舉出來的,所以是數組在調用函數。 var f = arr[2]; f(); //0 全局沒有length長度 console.log(arr)
知識複習
函數的length值是函數的:形參列表的長度
function f(a,b,c,d,e,f,g,h){ } console.log(f.length); //8
arguments.length表示函數的:實參列表的長度
function f(a,b,c,d,e,f,g,h){ console.log(arguments); console.log(arguments.length); //5 console.log(arguments.callee); //callee等價於函數本身f console.log(arguments.callee.length); //8 } f(1,2,3,4,5)
function fun1(a,b,c){ console.log(arguments[0].length); //5 } function fun2(a,b,c,d,e) { } fun1(fun2)
小題目1:
//arguments枚舉出了第0項,就是傳入的fun2函數,加()執行。 //這裡就符合規律5的內容,所以fun2的上下文this執行的是fun1的arguments類數組對象 //所以它的length表示調用fun1的時候傳入的實參長度,是9 function fun1(a,b,c){ arguments[0](); } function fun2(a,b,c,d,e) { alert(this.length); //9 } fun1(fun2,2,3,4,5,6,7,8,9); //9
小題目2:
小題目進階版:
function fun1(a,b,c){ arguments[0](1,2,3,4,5,6); //arguments[0] == fun2函數 } function fun2(a,b,c,d,e) { //這個函數裡面的this表示fun1函數的arguments對象 alert(this.length); //9 fun1的實參個數 alert(this.callee.length); //3 fun1的形參個數 alert(arguments.length); //6 fun2的實參個數 alert(arguments.callee.length); //5 fun2的形參個數 } fun1(fun2,2,3,4,5,6,7,8,9); //9
小題目3:
var m = 1; var obj = { fn1 : function(){ return this.fn2(); }, fn2 : fn2, m : 2 } function fn2(){ return this.m; } alert(obj.fn1()); //2
小題目4:
不管函數的“身世”多複雜,一定要只看調用的哪一下是如何調用的
var length = 1; var obj = { length : 10, b : [{ length:20, fn:function(){ alert(this.length) //this == {} } }] } obj.b[0].fn(); //20 b[0] == {} var o = obj.b[0]; var fn = obj.b[0].fn; o.fn(); //20 fn(); //1
var length = 1; var obj = { length : 10, b : [{ length:20, fn:function(){ alert(this.length) } }] } var arr = [obj, obj.b, obj.b[0], obj.b[0].fn]; arr[0].b[0].fn(); //20 arr[1][0].fn(); //20 arr[2].fn(); //20 arr[3](); //4
如何判斷上下文(this): 規則1:直接圓括弧調用fn(),IIFE調用,此時this是window 規則2:對象打點調用obj.fn(),此時this是obj 規則3:數組中枚舉函數調用arr[3](),此時this是arr 規則4:定時器調用函數setInterval(fn , 10),此時this是window 規則5:DOM事件監聽oBtn.onclick = fn,此時this是oBtn 規則6:call和allpay可以指定,fn.call(obj),此時this是obj 規則7:用new調用函數,new fun(),此時this是秘密新創建的空白對象。
三、構造函數
3.1構造函數
到目前為止,調用一個函數的方法,有很多:直接()圓括弧調用、數組或對象枚舉調用、定時器調用、DOM事件調用,隨著調用的方法不同,函數的上下文也不同。
現在,要介紹一種函數的調用方式,用new來調用。
function fun(){ alert("我調用"); } var obj = new fun(); console.log(obj)
function People(name,age,sex){ //構造函數,可以稱為一個“類”,描述的是一個類對象需要擁有的屬性 this.name = name; this.age = age; this.sex = sex; } //構造函數的實例,也可以稱為“類的實例”,就相當於按照類的要求,實例化了一個個人 var xiaoming = new People("小明",12,"男"); var xiaohong = new People("小紅",13,"女"); var xiaogangpao = new People("小鋼炮",16,"女"); console.log(xiaoming) console.log(xiaohong) console.log(xiaogangpao)
new是一個動詞,表示產生“新”的,會發現,的確這個函數產生了新的對象。
結論:當用new調用一個函數時,會發生4四件事(4步走)
1) 函數內部會創建一個新的空對象“{}”
2) 將構造函數的作用域賦值給新對象(因此this就指向這個新的空對象)
3) 執行構造函數中的代碼(為這個新的空對象添加屬性)
4) 函數執行完畢後,將這個對象返回(return)到外面被接收。(函數將把自己的上下文返回給這個對象)
對象是什麼?一個泛指,JS中萬物皆對象。
l 類:對象的一個具體的細分
l 實例:類中的一個具體事物
例如:自然界萬物皆對象,把自然界中的事物分為幾大類:人類、動物類、植物類...等,而每一個人都是人類中的一個實例。
學習JS,需要給JS分類,然後再研究每一個類別中具體的對象 → 面向對象編程思想。
所有的編程語言都是面向對象開發 → js是一門輕量級的腳本編程語言。
面向對象開發 → 研究類的繼承、封裝、多態
可以認為People是一個人“類(class)”,xiaoming、xiaoming都是這個People類的“實例(instance)”。
會發現產生的對象擁有相同的屬性群,我們稱它們是同一個類型的對象。
當函數被new調用時,此時總會返回同一類型的對象,感覺在構造什麼東西,這個函數就被稱為“構造函數”。
註意:
●函數是構造函數,不是因為函數本身,而是因為它被new調用了
●習慣上:構造函數要用大寫字母開頭,暗示其他程式員這是一個構造函數。但是,記住了,不是說大寫字母開頭的就是構造函數,而是因為被new了
●顧名思義,它能夠構造同一類型的對象,都有相同屬性群。
類比Java等OO語言(面向對象語言),People可以叫做類(class)。
在Java、C++、C#、Python中,這種東西很像“類”的功能。實際上JavaScript沒有類(class)的概念,只有構造函數(constructor)的概念!
JavaScript是基於對象的語言(Base on Object),而不是具體意義上“面向對象”(oriented object)語言。但是,我們將Java的一些概念,給移植過來
灰色部分, 表示屬性在自己身上。
DOM語句也可以寫在構造函數中:
function Star(name,age,sex,url){ this.name = name; this.age = age; this.sex = sex; this.url = "images/star/"+ url +".jpg"; //創建圖片對象 this.img = document.createElement('img'); this.img.src = this.url; //添加圖片地址 //上樹 document.body.appendChild(this.img) } var liudehua = new Star("劉德華", 55, "男", 61); var wanglihong = new Star("王力巨集", 50, "男", 73); console.log(liudehua) console.log(wanglihong)
3.2構造函數return
構造函數不用寫return就能幫你返回一個對象,但如果寫了return怎麼辦?
面試題考察:
●如果return基本類型值,則無視return,函數該返回什麼就返回什麼,但return會結束構造函數的執行。
●如果return引用類型值,就不返回new出的對象了,則返回return的這個,原有return被覆蓋。
function People(name,age,sex){ this.name = name; this.age = age; return 100; //返回基本類型值,所以被忽略 this.sex = sex; //return會打斷程式執行 } var xiaoming = new People("小明",12,"男"); console.log(xiaoming)
類似的,都會被忽略:註意,null此時看作基本類型 return "abc" return false return undefined return null
如果返回引用類型值:
<