一、JavaScript特點 二、JavaScript三大部分:(ECMAScript、DOM、BOM) 三、JavaScript的基本語法 變數聲明 Js是一種弱數據類型的語言,任何類型的變數都用關鍵字Var來聲明。 賦值可以在聲明的的同時賦值,也可以在後面賦值。 這兩種方法是一樣的。 同時有一種 ...
一、JavaScript特點
- 解釋性語言——不需要編譯代碼,可以跨平臺,像php、js、jsp都是解釋性語言。
- 單線程——js是單線程的語言,同時只能執行一件事情
- ECMA標準——為了統一js的規則,推出了ECMA標準,因此js也稱為ECMAScript。
二、JavaScript三大部分:(ECMAScript、DOM、BOM)
- ECMAScript是符合ECMA標準的基本javascript。
- DOM是Document Object Model文檔對象模型,可以操作頁面的代碼,可以操作html和css部分。DOM是非常非常重要的部分。
- BOM是Browser Object Model瀏覽器對象模型,操作瀏覽器shell的。因為每一個瀏覽器廠家的不同,導致我們在每一個瀏覽器操作BOM都不一樣。
三、JavaScript的基本語法
-
變數聲明
- Js是一種弱數據類型的語言,任何類型的變數都用關鍵字Var來聲明。
1 var arr = [1,2,3]; 2 var num = 123; 3 var steing = "abc";
- 賦值可以在聲明的的同時賦值,也可以在後面賦值。
1 var num = 123; 2 var num; 3 num = 123;
- 這兩種方法是一樣的。
- 同時有一種單一Var模式。
1 var num1 = 123, 2 num2 = 234, 3 num3 = 456;
- 變數名上下對齊,這樣結構更清晰,也能節省很多代碼。
2.變數命名規則
- 以英文字母開頭或者_和$符開頭。
- 變數名可以包含數字。
- 不可以使用系統自帶的關鍵字,保留字。
3.數據類型(值類型)
- 不可改變的原始值。
- 主要有 Number String Boolean undefined null 堆數據。
- 引用值 數組Array 對象Object 函數function 棧數據。
四、JavaScript語句的基本規則
- 語句後面要用英文“ ;”結束。
- js語法錯誤會引發後續代碼的終止,但不會影響其他的js代碼塊,這裡僅限於邏輯錯誤和低級語法錯誤會導致代碼全部執行不了。
- 書寫規範(不同公司有不同要求)。
五、JavaScript運算符
- “ + ”數學上相加的功能和字元串拼接“- * / % ”數學運算。
- 相同的還有“++ == -- += -= > < ...”等等。
- 邏輯運算符 && || !
- && 的作用是結果是true的時候才會繼續執行,第一個就錯了第二個不會執行,如果都是true的話返回最後一個。
- || 的作用是結果只要有一個表達式是true的,後面的就不走了,並且返回的結果是這個正確的表達式的結果,全是false表達式返回的結果就是false。
- && 可以當做一種短路語言使用;|| 可以當做賦初值的使用。
4.預設為false的值
- unedfined; null; " "; NaN; 0; false 。
六、類型轉換
(一)顯示類型轉換
- 用 typeof 可以檢測數據的類型。
1 console.log(typeof(123)); // Number
- typeof 返回的結果有六種:number string boolear undefined object function 。
- 數組和 null 都屬於object。
- NaN 屬於number ,雖然是非數,但也屬於數字。
- typeof 返回結果是字元串。
2.Number(mix)[混合]
- 這個方法是可以把其他類型的數據轉換成數字類型的數據。
3.parseInt(string,radix)[基數]
- 這個方法是將字元串轉換成整型類型數字的。其中第二個參數radix基底是可以選擇的參數。
- 當radix為空的時候,這個函數的作用僅僅是將字元串轉換成number類型。
- 當參數string裡面既包括數字字元串又包括其他字元串的時候,它會將看到其他字元串就停止了,不會繼續轉換後面的數字型字元串了。
1 parseInt('123abc123') // 123; 2 parseInt('abc123') // NaN; 3 parseInt('123') // 123; 4 parseInt('true') // NaN;
- 當radix不為空的時候這個函數可以用作進位轉換,把第一個參數的數字當成幾進位的數字轉換成十進位。
- radix參考範圍是2--36,
1 var demo = 10; 2 parseInt(demo,16) //16
4.parseFloat(radix)
- 這個方法和parseInt類似,是將字元串轉換成浮點類型的數字,碰到第一個非數字類型停止。
- 只能識別第一個小數點及後面的數字,第二個小數點不能識別。
1 parseFloat('123.2.3') // 123.2 2 parseFloat('132.2abc') // 123.2 3 parseFloat('123.abc1') // 123
5.toString(radix)
- 這個方法和前面的不同,它是對象上的方法,任何數據類型都可以使用,轉換成字元串類型,涉及到包裝類。
- 同樣是radix基地可選參數,為空僅僅將數據轉換成字元串。
1 var demo = 123; 2 typeof demo.toString(); // string123; 3 typeof true.toString(); // stringtrue;
- 當寫了radix時代表要將這個數字轉換成幾進位的數字型字元串。
1 var dome = 10; 2 demo.toString(16) // A
- undefined和null沒有toString方法
6.String(mix)
- 和Number類似把任何類型轉換成字元串
7.Boolean(mix)
- 和Number類似把任何類型轉換為Boolean
(二)隱式類型轉換
1.isNaN()
- 這個方法可以檢測是不是非數類型,調用的Number方法。
2.算數運算符
- ++就是將現有數據調用Number之後,自身加一。
- + - * / 執行之前都會先進行類型轉換,換成數字在運算。
3.邏輯運算符
- && || ! 都會調用Boolean轉換成布爾值看看結果是ture還是false,返回結果還是本身表達式的結果。
- // !abc; // false
4.不發生類型轉換的比較運算符
- ===嚴格等於 !==嚴格不等於
七、預編譯【precompile】
函數聲明提升:函數聲明提升是一種整體提升,他、它會把函數聲明和函數體一起提到前面。
變數聲明提升:變數聲明提升是一種局部提升,它僅僅將變數的聲明提前了,但是並沒有將賦值一起提升。
1.js運行三部曲
- 語法分析
- 預編譯
- 解析執行
2.預編譯前奏
- imply global
- 暗示全局變數,如果任何變數未經聲明就賦值使用,此變數歸window所有,並且成為window對象的一個屬性。
- 一切聲明的全局變數,都是window屬性。
- 未經聲明的全局變數可以用delete操作來刪除。
- 函數在執行的前一刻一個執行上下文,Activeaction Object對象。
- 這個對象是空的,但是裡面有著一些看不見的隱式屬性:this:window屬性和arguments[];屬性。
3.預編譯四步
- 創建AO對象
- 尋找形參和變數聲明並當做屬性名添加到AO對象中,值為undefined。//函數聲明不叫變數。
- 將實參形參相統一
- 在函數體里尋找函數聲明,將函數名當做屬性名,值為這個函數的函數體。
1 function test (a,b) { 2 console.log(a) 3 function a () {} 4 a = 222; 5 console.log(a) 6 function b () {}; 7 console.log(b) 8 var b = 111; 9 var a; 10 } 11 test(1);
- var b = function () {} 這種不叫函數聲明,這個函數是給b賦值的,b變數是聲明。
- 在第四步尋找函數聲明並不會把賦值成function(){},執行到這一行的時候才會賦值成這個函數。
十、函數與作用域與閉包
(一)函數部分
1.函數聲明有3種方式
1 var demo = function () {}; 函數表達式 2 function demo () {}; 函數聲明 3 var demo = function xxx () {};命名函數表達式 //沒用
- 每一個函數裡面都有一個類數組屬性arguments,這個屬性裡面存的就是實參。
- 每一個函數有一個length屬性,這個屬性存的是形參的數量。
- 每一個函數都會有一個return,如果不寫的話函數會自動加一個return。
- return的功能有兩個:返回這個函數的執行結果、終止函數的執行。
1 function test(a,b) { 2 console.log(a + b); 3 return; 4 console.log('hello'); 5 } 6 test(1,2); // 列印結果3 不會列印hello
(二)作用域
- 定義:變數(變數作用域又稱為上下文)和函數生效(能被訪問)的區域。
- JavaScript的函數是可以產生作用域的。
- es5中的作用域只有全局作用域函數作用域兩種,es6新添加的塊級作用域。
1 var demo = 123; // 全局變數 2 function test () { 3 var demo = 234; // 局部變數 4 console.log(demo); 5 var demo1 = 'hello'; 6 } 7 test(demo); // 列印234 就近列印局部變數,沒有局部變數列印全局變數 8 console.log(demo1); // 報錯 我們的全局作用域無法訪問局部作用域
- 函數作用域就好像一個屋子,裡面的可以拿外面的東西,外面的不能拿裡面的東西。
- 在函數作用域里聲明變數沒有用var的話,那麼就生成了一個全局變數。
- 兩個不同的作用域(除了全局作用域)是不能互訪問的。
(三)作用域鏈(scope chain)
- 既然函數存在作用域,函數有可以嵌套,那麼作用域就會產生嵌套關係,這個時候就產生作用域鏈。
- 當代碼在一個環境中執行時,會創建變數的作用域鏈來保證對執行環境有權訪問的變數和函數的有序訪問。
- 作用域鏈第一個對象始終是當前執行代碼所在環境的變數對象。
1 function demo () { 2 var dome_a = 1; 3 function test () { 4 var demo_a = 2; 5 console.log(demo_a); 6 } 7 test(); 8 } 9 demo();
- 本著對執行環境的有權和有序訪問,每個函數的自身作用域總在作用域鏈的最頂層,下一層就是這個函數的父級函數作用域,直到全局作用域。
- 因此test執行的時候列印的demo_a是本身作用域中的是‘2’而不是‘1’,如果自身作用域沒有demo_a的話系統就會沿著作用域鏈向下找demo_a。
(四)閉包【closure】
1.什麼是閉包
- 閉包就是能夠讀取其它函數內部變數的函數。
- 不同的作用有之間不能互相訪問,但是如果在一個函數內部再定義一個函數與外部的變數有所關聯,那麼就可以返回這個函數來訪問外部函數裡面的變數,所以在本質上閉包就是將函數內部與函數外部連接起來的橋梁。
1 function a (){ 2 var dome1 = 123; 3 add = function(){ 4 demo1 ++; 5 } 6 return function(){ 7 console.log(demo1); 8 }; 9 } 10 var demo = a (); 11 demo(); // 123 12 add(); 13 demo(); // 124
- 當函數執行完函數的執行上下文就會被銷毀,自然就無法訪問裡面的變數了,但是我們這個函數返回了一個依賴於這個函數的新函數,也就是說這個沒有被銷毀的新函數的作用域鏈中存在著原本函數作用域的引用,就導致原本函數的上下文不會被銷毀,返回的新函數是原本函數的閉包函數。
2.使用閉包的註意點
- 閉包會使函數的變數都保存在記憶體中,記憶體消耗很大,不能濫用閉包,否則會造成網頁的性能問題,IE會造成記憶體泄漏。解決的方法就是退出函數時,將不使用的局部變數全部刪除。
- 閉包會在父函數外部改變父函數內部的值,如果把閉包當對象使用,那麼就把閉包當做它的公用方法,把內部變數當做它的稀有屬性。(不要隨便改變函數內部變數的值)
1 var name = 'global'; 2 var obj = { 3 name:'obj', 4 getName:function() { 5 return function () { 6 console.log(this.name); 8 } 10 } 11 } 12 obj.getName() ();
- 累加器的例子:
1 function a () { 2 var num = 1; 3 function addNum () { 4 num ++; 5 console.log(num); 6 } 7 return addNum; 8 } 9 var demo = a (); 10 demo(); 11 demo(); 12 var demo1 = a(); 13 demo1(); 14 demo1();
(五)立即執行函數
- 立即執行函數是解閉包的一個重要方法,但是註意閉包是沒有辦法解除的,只能通過一個新的閉包來消除上一個閉包的影響。
- 立即執行函數不需要被定義,直接執行,執行完釋放,經常作用作初始化。
- 函數聲明不能被執行,但是函數表達式可以
1 (function (){}()) 2 function returnB() { 3 var arr = []; 4 for(i = 0; i < 10; i ++){ 5 arr[i] = (function(){ 6 console.log(i); 7 }()) 8 } 9 return arr; 10 } 11 var save = returnB(); 12 console.log(save); 13 for(j = 0; j < 10; j ++){ 14 save[j]; 15 }
十一、對象、構造函數與包裝類
1.對象的創建方式有三點
- 對象字面量。
1 var obj ={};
- 這樣的方式是最簡單最常用的方法。
- 對象裡面有屬性,屬性之間用逗號相隔,每條屬性都有屬性名和值,屬性名和屬性值用分號相隔。
2.構造函數【constructor】
- 構造函數也分為兩種,系統自帶的構造函數和自定義的構造函數。
- 創建對象的構造函數Object()
1 var object = new object();
- 通過這條語句就創建了一個空對象,它的作用和 var obj = {}; 的作用一樣。
- 系統自帶的構造函數還有Number();String();Boolean();Array() 。
3.自定義構造函數
- 自定義的構造函數是最常見的一種構造函數。
1 var function Person () {}; 2 var operson = new Person (); 3 typeof operson // object
- 用new操作符創造出來的對象,儘管使用的是一個構造函數,但是之間沒有聯繫。
1 function Person (name,age) { 2 this.name = name; 3 this.age = age; 4 } 5 var person = new Person('zhangsan',18); 6 console.log(person.name);
- 創建對象的時候只有new才會有this。
- 重點:為什麼可以用new操作符創建出相互獨立的對象呢?
- 用new操作符的時候,這個new在構造函數裡面隱式的創建了一個this對象,並且最後返回了這個this對象。
1 function Person (name) { 2 var this = {}; 3 this.name = name; 4 return this; 5 }
- 如果在構造函數首行手動創建一個對象,比如that對象,然後返回that,那麼裡面的this就沒有了,屬性值就用that了。
1 function Person (name) { 2 var that = { 3 name: 'lisi' 4 }; 5 that.name = name; 6 return that; 7 } 8 var person = new Person ('demo'); 9 console.log(person.name)
- 如果最後返回的是對象,那麼this就失效,但是如果顯示返回的是原始值那麼this還是有效的。
4.屬性的增刪改查
- 增:可以通過對象名+點屬性名的方法來給對象添加新的屬性並且賦值。
1 var obj = {};
2 obj.name = 'xiaoming'
- 改:修改的操作增加是一樣的,只要調用相同的屬性名然後賦一個新值就可以了。
1 var obj = { 2 name:'demo'; 3 } 4 obj.name = 'tan';
- 查:查看屬性的功能console.log(xxx)。
- 刪:刪除屬性需要藉助delete操作符。
1 var obj = { 2 name = 'scerlett' 3 } 4 obj.name; // scerlett 5 delete obj.name; 6 obj.name; // undefined
- 包裝類
十二、原型與原型鏈
(一)原型:prototype
1.原型的定義:原型是function對象的一個屬性,它定義了構造函數製造出來的對象的公有祖先,通過該構造函數產生的對象,可以繼承原型的屬性和方法,原型也是對象。
1 function Person () {}
- 定義一個構造函數,Person.prototype這個屬性就是這個構造函數的原理,這個屬性天生就有的,並且這個屬性的值也是一個對象。
- 可以在person.prototype上面添加屬性和方法,每一構造出來的對象都可以繼承這些屬性和方法。
- 雖然每一個對象都是獨立的,但是它們都有共同的祖先,當訪問這個對象屬性的時候,如果它沒有這個屬性,就會向上查找,找到它原型,然後在原型上訪問這個屬性。
2.利用原型特點概念,可以提取公有屬性
- 可以把每一個對象都有的公有屬性不寫在構造函數裡面,而是提取到原型上,這樣當構造函數構造大量的對象的時候就不需要走多次構造裡面的賦值語句了,而只需走一遍,每個對象調用屬性的時候直接上原型上查找就可以了。
3.對象如何查看原型
- 用構造函數構造對象的時候,就會隱式創建一個this對象,這個this對象裡面有一個預設的屬性叫做proto屬性,這個屬性的值就是指向對象的原型。
- 當查找的屬性是自身沒有的屬性的時候,就會查找proto這個屬性,然後這個屬性指向原型,所以就到原型上查找屬性了。
- 註意:prototype是函數的屬性,proto是對象的屬性。
4.如何查看構造自身的構造函數
- 在prototype裡面,有一個隱式的屬性叫做constructor,這個屬性記錄的就是對象的構造器,裡面存的就是構造函數。
1 console.log(person.constructor); //person();
(二)原型鏈
1.有了原型,原型還是一個對象,那麼這個名為原型的對象自然還有自己的原型,這樣的原型上還有原型的結構就成了原型鏈。
1 Gra.prototype.firsName = 'scarlett' 2 function Gra () { 3 this.name = 'grandfather'; 4 this.sex = 'male'; 5 } 6 var grandfoo = new Gra(); 7 garndfoo.word = 'hello' 8 Foo.prototype = grandfoo; 9 function Foo () { 10 this.age = '18'; 11 this.money = '100'; 12 } 13 var father = new Foo(); 14 function Son () { 15 this.name = 'son'; 16 } 17 Son.prototype = father; 18 var son = new Son();
- Foo創造出來的每一個對象都繼承來自grandfoo對象,son的每一對象都繼承來自father這個由Foo創造出來的對象,這樣的son就可以繼承上面Foo和Gra的所有屬性。
- 這種鏈式的查詢結構就叫做原型,最終的盡頭是Object.prototype這個對象。
- 如果沒有規定原型的對象,它的原型就是Object.prototype。
2.但是並不是所有的對象都有原型,比如使用Object.create方法。
- Object.create()方法需要寫一個參數,這個參數就是對象的原型,如果要構造一個var obj = {};一樣的對象,就需要寫:
1 var obj = Object.create(Object.prototype);
- 也可以寫一個自定義的屬性,讓它成為原型。
3.原型鏈上的增刪改查
1 Person.prototype.arr[1,2,3]; 2 var person1 = new Person(); 3 var person2 = new Person(); 4 person1.arr.push(4); 5 console.log(person2);//1 2 3 4
- 刪:刪除屬性需要藉助delete操作符,對象不能刪除原型上的屬性。
十三、繼承、this
1.this的一些問題:
函數內部的this預設指向window,可以使用call / apply來改變this的指向,區別:後面傳參形式不同。
1 function person () { 2 this.name = 'scarlett'; 3 console.log(this); 4 } 5 person();
現在this指向window,name屬性自然就是window上的全局屬性
var obj = {}; person。call(object)//Object.{name:'scarlett'}
如果這個函數還有參數的話,只要把實參的值寫在call後面並且用逗號隔開
function person(name,age){ this.name = name; this.age = age; } var obj = {}; person.call(obj,'scarlett',18); console.log(obj.name);
apply和call基本沒什麼區別,唯一的區別就是call後面的參數是一個一個傳的,而apply後面的參數是放在數組裡
person.apply(obj['scarlett',18]);
2.繼承【inherit】
聖杯模式
function inherit(Target, Origin) { function F() {}; F.prototype = Origin.prototype; Target.prototype = new F(); Target.prototype.constuctor = Target;//讓constuctor指向自己的 Target.prototype.uber = Origin.prototype; //超類,知道自己繼承的是誰
yahu封裝方法:
1 // var inherit = (function (){ 2 // var F = function () {}; 3 // return function (Target, Origin){ 4 // F.prototype = Origin.prototype; 5 // Target.prototype = new F(); 6 // Target.prototype.constuctor = Target; 7 // Target.prototype.uber = Origin.prototype; 8 // } 9 // }()); 10 // 11 // for (var i = 0; i < 5; i ++) { 12 // var btn = document.createElement('button'); 13 // btn.appendChild(document.createTextNode('Button' + i)); 14 // btn.addEventListener('click', function(){console.log(i); }); 15 // document.body.appendChild(btn); 16 // }
對象的枚舉與this
1.對象的枚舉
查看對象的屬性可以用obj.name查看,也可以用obj['name']類數組方式查看,但事實上是數組模仿了對象的查看方式
2.for-in操作符
要枚舉一個數組的所有屬性只需用一個for迴圈從頭到尾遍歷一遍就可以了。
但是對象並不能用for迴圈來遍歷屬性,所有就要用到for-in操作了
1 // var obj = { 2 // name: 'scarlett', 3 // age: 18, 4 // sex: 'female' 5 // } 6 // for(var prop in obj){ 7 // console.log(prop + ':' + obj[prop]); 8 // }
- for-in迴圈會按照屬性的順序取出屬性名然後賦給prop,所有列印prop都是屬性名,obj【prop】則是性對應的屬性的值
- 註意:這裡不能寫成obj.prop方式,因為在系統底層會轉化成obj【‘prop’】的形式,但是並沒有prop屬性,它只是一個變數,所以會列印 undefined,這裡必須使用obj['prop']。
- 在非嚴格模式中,for-in迴圈都會把原型裡面的一些屬性一起列印出來,但es5的嚴格模式不會。
2.三種操作符
hasOwnProperty這個操作符的作用是查看當前這個屬性是不是對象自身的屬性,在原型鏈上的屬性會被過濾掉,自身的ture
// function Person() { // this.name = 'scarlett' // } // Person.prototype = { // age:18 // } // var oPerson = new Person(); // for (var prop in oPerson) { // if (oPerson.hasOwnProperty(prop)){ // console.log(oPerson[prop]); // } // }
這樣for-in迴圈就只會列印自身的屬性,而不是列印原型上的屬性
in操作符:這個操作符的作用是查看一個屬性是不是在這個對象或者它原型裡面。
1 // 'name' in oPerson; //ture 2 // 'sex' in oPerson; //False
instanceof操作符:作用是查看前面對象是不是後面的構造函數構造出來的,和constructor很像
1 // oPerson intanceof object; // ture 2 // {} instanceof oPerson; // false
3.this
- 預編譯過程中this執行window
- 全局作用域里的this指向window
- call / apply可以改變this指向
- obj.func()func()里的this指向Obj
1 // var obj = { 2 // height:190, 3 // eat:function () { 4 // this.height ++; // eat在沒有執行之前,誰也不知道this指向誰 5 // } 6 // } 7 // obj.eat(); // 誰調用this,this指向誰 8 // eat.call(obj); // eat裡面的this指向obj
如果能理解下麵的這段代碼的this指向問題,那麼就掌握的this的所有知識點了
1 // var name = '222' 2 // var a = { 3 // name:'111', 4 // say:function () { 5 // console.log(this.name); 6 // } 7 // } 8 // var fun = a.say; 9 // fun(); // 此處其實就是把a.say這個函數體賦給fun這個函數,相當於在全局空間寫下了一個fun函數,此時this指向window,列印'222'