JavaScript 高級筆記
面向對象與面向過程
面向過程是指分析結局問題的步驟,然後按照分析的步驟一步步實現.
面向對象是指把事物分解成一個個對象,然後由對象之間分工合作.
面向過程 | 面向對象 | |
---|---|---|
優點 | 性能比面向對象高,適合跟硬件聯繫很緊密的東西,例如單片機就採用的面向過程編程。 | 易維護、易複用、易擴展,由於面向對象有封裝、繼承、多態性的特性,可以設計出低耦合的系統,使系統 更加靈活、更加易於維護 |
缺點 | 不易維護、不易複用、不易擴展 | 性能比面向過程低 |
面向對象的特點
封裝 繼承 多態
對象和類
對象是由屬性和方法組成:是一個無序鍵值對的集合,是指一個具體的事物
(對象如果想批量生產出來,可以用類似現實生活中的圖紙和車的關係一樣
圖紙:類 ; 車:對象)
屬性:事物的特徵,在對象中用屬性來表示(常用名詞)
方法:事物的行爲,在對象中用方法來表示(常用動詞)
//創建對象的三種簡單方式: 1.字面量: var obj={ age :18,//屬性名:屬性值 uname :'Amy' fn:function(){ console.log('你好') }//方法名:方法 } 2.構造函數: function Person(name,age){ this.name = name; this.age = age; this.sayHi=function(){ console.log('你好') } } var xm = new Person('小明',20) 3.自定義構造函數: var obj = new Object(); obj.name='小明';//給對象追加屬性 obj.sayHi=function(){ console.og('你好') }//給對象追加方法
工廠模式 | 自定義構造函數 | |
---|---|---|
不同 | 函數名是小寫 有new, 有返回值 new之後的對象是當前的對象 直接調用函數就可以創建對象 | 函數名是大寫(首字母) 沒有new 沒有返回值 this是當前的對象 通過new的方式來創建對象 |
相同 | 都是函數,都可以創建對象,都可以傳入參數 |
<script> // 工廠模式 function fn(name, age) { var obj = {} obj.name = name obj.age = age return obj; } var zxy = fn('張學友', 20) console.log(zxy); // 構造函數 ----->實例化對象 function Star(name, age) { this.name = name this.age = age this.sing=function(){ console.log('我會唱歌') } } var ldh = new Star('劉德華', 20) console.log(ldh); /* 實例對象和構造函數之間的關係: * 1. 實例對象是通過構造函數來創建的---創建的過程叫實例化 如何判斷對象是不是這個數據類型? 1) 通過構造器的方式 實例對象.構造器==構造函數名字 hero.constructor === Hero 2) 對象 instanceof 構造函數名字儘可能的使用第二種方式來識別 */ </script>
實例成員與靜態成員
//實例成員是對象的成員 hero.name = 'zs'; //靜態成員是直接給構造函數添加的成員 Hero.version = '1.0'; // 靜態成員不能使用對象的方式來調用 console.log(hero.version); // 靜態成員使用構造函數來調用 console.log(Hero.version); //實例成員 是構造函數內部通過this添加的成員 如下列代碼中uname age sing 就是實例成員,實例成員只能通過實例化的對象來訪問 function Star(uname, age) { this.uname = uname; this.age = age; this.sing = function() { console.log('我會唱歌'); } } var ldh = new Star('劉德華', 18); console.log(ldh.uname);//實例成員只能通過實例化的對象來訪問 //靜態成員 在構造函數本身上添加的成員 如下列代碼中 sex 就是靜態成員,靜態成員只能通過構造函數來訪問 function Star(uname, age) { this.uname = uname; this.age = age; this.sing = function() { console.log('我會唱歌'); } } Star.sex = '男'; var ldh = new Star('劉德華', 18); console.log(Star.sex);//靜態成員只能通過構造函數來訪問
類
ES6 中新增加了類的概念,可以使用 class 關鍵字聲明一個類,之後以這個類來實例化對象。類抽象了對象的公共部分,它泛指某一大類(class)對象特指某一個,通過類實例化一個具體的對象.
1.創建類:語法 //使用class關鍵字 class name { //class student } //使用定義的類創建實例 使用new關鍵字 var xm = new name(); 2.示例 class Star { // constructor在NEW的時候會自動被調用,並且只能調用一次 // 公共的方法在new的時候不會被調用,只能我們自己主動調用 // 類的共有屬性放到 constructor 裏面 constructor是 構造器或者構造函數 constructor(uname, age) { this.uname = uname; this.age = age; } // 公共方法 // 類中所有的函數都不需要用function,並且不需要逗號隔開 sing(song) { console.log('我會唱' + song); } } //利用類創建對象 new var ldh = new Star('劉德華', 20); ldh.sing('笨小孩'); /* 注意點 1. 通過class 關鍵字創建類, 類名我們還是習慣性定義首字母大寫 2. 類裏面有個constructor 函數,可以接受傳遞過來的參數,同時返回實例對象 3. constructor 函數 只要 new 生成實例時,就會自動調用這個函數, 如果我們不寫這個函數,類也會自動生成這個函數 4. 多個函數方法之間不需要添加逗號分隔 5. 生成實例 new 不能省略 6. 語法規範, 創建類 類名後面不要加小括號,生成實例 類名後面加小括號, 構造函數不需要加function */
類的繼承
語法: // 父類 class Father{ } // 子類繼承父類 class Son extends Father { } 示例: //繼承 class Father { constructor(uname, age) { this.uname = uname; this.age = age; this.money1 = 300; } sing1() { return '我會唱歌'; } eat() { console.log('我還會喫飯'); } sleep() { console.log('我會睡覺'); } } // sing1() // extends繼承父類 class Son extends Father { constructor(uname, age, money) { // super()調用父類的 constructor構造函數 super(uname, age, money); this.money = money; } hobby() { console.log('我喜歡看書' + super.sing1()); } salary() { console.log(this.money + this.money1); } } var xm = new Son('小明', 22, 100); // console.log(xm); xm.salary();//400 xm.hobby();//我喜歡看書我會唱歌 /* 注意: 1. 繼承中,如果實例化子類輸出一個方法,先看子類有沒有這個方法,如果有就先執行子類的 2. 繼承中,如果子類裏面沒有,就去查找父類有沒有這個方法,如果有,就執行父類的這個方法(就近原則) 3. 如果子類想要繼承父類的方法,同時在自己內部擴展自己的方法,利用super 調用父類的構造函數,super 必須在子類this之前調用 4. 時刻注意this的指向問題,類裏面的共有的屬性和方法一定要加this使用. 1. constructor中的this指向的是new出來的實例對象 2. 自定義的方法,一般也指向的new出來的實例對象 3. 綁定事件之後this指向的就是觸發事件的事件源 5. 在 ES6 中類沒有變量提升,所以必須先定義類,才能通過類實例化對象 */
tab欄切換案例
<style> * { margin: 0; padding: 0; } .content { width: 50%; height: 400px; border: 1px solid #000; margin: 100px auto; position: relative; } .content .top { height: 70px; border-bottom: 1px solid green; } .content .top ul { overflow: hidden; } .content .top ul li { position: relative; float: left; list-style: none; width: 60px; text-align: center; line-height: 70px; border-right: 1px solid red; } .content .top ul li i { background-color: blue; position: absolute; right: 0; top: 0; line-height: normal; font-style: normal; cursor: pointer; } .content .top .liactive { background-color: pink; } .add { position: absolute; top: 25px; right: 0; } .content .bottom>div { display: none; } .content .bottom>div.dactive { display: block; } </style> </head> <body> <!-- 大盒子 --> <div class="content" id="content"> <!-- 上面的標題部分 --> <div class="top"> <ul> <li class="liactive"><span>1標籤</span><i>x</i></li> <li><span>1標籤</span><i>x</i></li> <li><span>1標籤</span><i>x</i></li> </ul> <button class="add">+</button> </div> <!-- 下面的內容 --> <div class="bottom"> <div class="dactive">1內容</div> <div>2內容</div> <div>3內容</div> </div> </div> <script> // 創建一個類,存放公共的方法 class Tab { constructor(id) { this.main = document.querySelector(id); this.init(); } init() { // 獲取標題的大盒子 this.titleBox = this.main.querySelector('.top ul'); // 獲取標題的列表 this.titleList = this.main.querySelectorAll('.top li'); // 獲取下面存放內容的大盒子 this.contentBox = this.main.querySelector('.bottom '); // 獲取下面存放內容的列表 this.contentList = this.main.querySelectorAll('.bottom>div'); // 添加按鈕 this.addBtn = this.main.querySelector('.add'); // 刪除按鈕 this.removeBtn = this.main.querySelectorAll('li i') this.toggle(); this.add(); this.remove(); this.revise(); } // 切換 toggle() { var that = this; // 排他思想,點擊事件 for (var i = 0; i < this.titleList.length; i++) { // 設置自定義屬性 this.titleList[i].setAttribute('index', i); // 點擊事件開始 this.titleList[i].onclick = function () { for (var j = 0; j < that.titleList.length; j++) { // 點擊的時候先清空類名,確定點擊哪一個的時候把類名給附給哪一個 that.titleList[j].className = ''; that.contentList[j].className = ''; } // 點擊的標籤添加類名 this.className = 'liactive'; // 定義一個index 然後獲取到點擊的索引值 var index = this.getAttribute('index'); // 內容列表相同的下標的頁面添加相同的類名顯示出來 that.contentList[index].className = 'dactive'; } } } // 添加 add() { var that = this; this.addBtn.onclick = function () { // console.log('123'); var newli = '<li><span>新標籤</span><i>x</i></li>'; var newdiv = '<div>新內容</div>'; // console.log(this);//指向add // console.log(that);//指向tab that.titleBox.innerHTML += newli; that.contentBox.innerHTML += newdiv; that.init(); that.titleList[that.titleList.length - 1].click(); } } // 刪除按鈕 remove() { var that = this; for (var i = 0; i < this.removeBtn.length; i++) { this.removeBtn[i].setAttribute('index', i); this.removeBtn[i].onclick = function (e) { e.stopPropagation(); // console.log('123'); // console.log(this); // console.log(that); var index = this.getAttribute('index'); that.titleList[index].remove(); that.contentList[index].remove(); // that.init(); if (index == 0) { // return; if (that.titleList.length == 1) { return; } else { that.init(); that.titleList[0].click(); } } // 如果刪除以後發現選中的還在的話,就不需要切換 if (that.main.querySelectorAll('.liactive').length > 0) { return; } index--; that.titleList[index].click(); } } } // 修改標籤 revise() { var that = this; for (var i = 0; i < this.titleList.length; i++) { this.titleList[i].setAttribute('index', i); this.titleList[i].ondblclick = function () { // console.log('123'); // console.log(this);//指向的是li標籤 var newText = this.innerHTML; this.innerHTML= `<input type="text" >`; var input = this.children[0]; input.value = newText; input.focus() input.onblur = function () { this.parentNode.innerHTML = this.value } console.log(input); console.log(newText); } } } } new Tab('#content'); </script> </body>
原型(原型鏈)
//構造函數 原型對象 實例對象三者的關係 /* * 構造函數可以實例化對象 * 構造函數中有一個屬性叫prototype,是構造函數的原型對象 * 構造函數的原型對象(prototype)中有一個constructor構造器,這個構造器指向的就 是自己所在的原型對象所在的構造函數 * 構造函數的原型對象(prototype)中的方法是可以被實例對象直接訪問的 * 實例對象的原型對象(__proto__)指向的是該構造函數的原型對象 */ /* 實例對象.__proto__ ===>構造函數的原型對象 實例對象.__proto__.__proto__ ===>Object的原型對象 實例對象.__proto__.__proto__.__proto__ ===>null 實例對象 = new 構造函數 構造函數.prototype ===>構造函數的原型對象 原型對象.constructor ===>構造函數 實例對象.constructor ===>構造函數 原型對象.__proto__ ===>Object的原型對象 原型對象.__proto__.constructor ===>Object */
this指向
/*構造函數中的this就是實例對象 原型對象中方法中的this就是實例對象*/ function Mother(name, age) { this.name = name this.age = age } Mother.prototype.fn1 = function(){ console.log(this); } var son = new Mother('小明', 20) son.fn1();
原型添加方法
不需要共享的數據寫在構造函數中,需要共享的數據寫在原型中(實例對象可以直接訪問原型對象中的屬性和方法)
原型的作用: 1. 數據共享, 節省內存空間 2. 實現繼承
/*構造函數裏面的方法會在每次new一個新對象的時候, 都存儲一次該方法, 這樣就會造成內存冗雜 解決1: 把這個方法sayHi寫成全局方法, 在構造函數中this.sayHi = sayHi; 這樣可能會造成函數名重複, 更麻煩 況且如果此時有10個方法需要存儲, 那麼需要在全局作用域裏面寫10個函數, 也比較佔內存 解決2: 原型 每一個構造函數都有一個屬性 原型 / 也叫原型對象 可以給對象動態增加方法*/ function Student(name, age, sex) { this.name = name; this.age = age; this.sex = sex; } Student.prototype.sayHi = function () { console.log('大家好,我是' + this.name); } //通過Student構造函數,創建的對象,可以訪問Student.prototype中的成員 var s1 = new Student('lilei', 18, '男'); var s2 = new Student('hmm', 18, '女'); s1.sayHi(); s2.sayHi(); /*當調用對象的屬性或者方法的時候,先去找對象本身的屬性/方法 ,如果對象沒有該屬性或者方法。此時去調用原型中的屬性/方法 如果對象本身沒有該屬性/方法,原型中也沒有該屬性或者方法,此時會報錯*/ /* 1. 對象的__proto__ 等於 構造函數的Student.prototype 2. s1.__proto__ === Student.prototype //true 3. __proto__屬性是非標準的屬性->生產環境不能使用 4. 在原型對象中有一個屬性 constructor 構造函數 5. constructor 作用記錄了創建該對象的構造函數 記錄了創建該對象的構造函數 6. s1.constructor === Student //true 7. 實例對象可以直接訪問原型對象中的屬性和方法 */
//如果需要增加的方法多的話, 都寫給prototype就太複雜了可寫成 Student.prototype = { sayHi: function () { console.log('sayHi'); }, eat: function () { console.log('eat'); } } //可是這樣的話, Student.prototype就是一個新的對象了, 不能指回Student.prototype的構造函數, 所以必須修改爲: Student.prototype = { constructor: Student, sayHi: function () { console.log('sayHi'); }, eat: function () { console.log('eat'); } } /* // test屬性在原型對象上,而在設置屬性的值的時候,不會搜索原型鏈 // 而是直接給對象新增一個test屬性 s1.test = '123xxx'; 此時s1自身會增加一個test屬性, 並且賦值爲123xxx 而原型鏈裏面也有一個test屬性, 只不過被s1自身的test屬性覆蓋掉了 所以此時輸出s2的test屬性的話, 值仍然是原來的值 */
借用構造函數繼承
function Father(name, age) { this.name = name this.age = age } Father.prototype.fn = function () { console.log(123); } function Son(name, age) { Father.call(this, name, age) } // Son.prototype = Father.prototype; 這樣直接賦值會有問題,如果修改了子原型對象,父原型對象也會跟着一起變化(父類的原型對象會有子類原型對象特有的方法exam,這就是問題) // 通過指定父類實例對象方法解決: Son.prototype = new Father(); // 如果利用對象的形式修改了原型對象,別忘了利用constructor 指回原來的構造函數 Son.prototype.constructor = Son; var xm = new Son('小明', 20) console.log(xm); xm.fn()
this指向問題
調用方法 | this指向 |
---|---|
普通函數 | window |
構造函數 | 實例對象,原型對象裏面的方法也指向實例對象 |
對象方法調用 | 該方法所屬對象 |
事件綁定方法 | 綁定事件對象 |
定時器函數 | window |
立即執行函數 | window |
箭頭函數 | window |
//普通函數 function fn1(){ console.log(this)//window } //window.fn1() fn1() //構造函數 function Person(){ console.log(this) } var xm = new Person() //對象方法調用 var obj = { sayHi:function(){ console.log('對象方法的this:'+this); } } obj.sayHi(); //事件綁定方法 var btn = document.querySelector('button'); btn.onclick = function() { console.log('綁定時間函數的this:' + this); }; // 5. 定時器函數 this 指向的也是window window.setTimeout(function() { console.log('定時器的this:' + this); }, 1000); // 6. 立即執行函數 this還是指向window (function() { console.log('立即執行函數的this' + this); })(); //箭頭函數 // 箭頭函數中不綁定this,箭頭函數中的this指向是它所定義的位置 var uname = '張三' var obj = { uname :'李四', fn: function () { setTimeout(() => console.log(this.uname), 1000)//this指向李四 } } obj.fn()
改變函數內部this指向
改變函數內this指向 js提供了三種方法 call() apply() bind()
//call() var obj = { name: 'andy' } function fn(a, b) { console.log(a + b); console.log(this); } fn(); //此時的this指向的是window fn.call(obj, 2, 3); //此時的this指向的是對象obj // call 第一個可以調用函數 第二個可以改變函數內的this 指向 // call 的主要作用可以實現繼承 function Father(uname,age,sex){ this.uname= uname; this.age = age this.sex = sex; } function Son(uname,age,sex){ Father.call(this,uname,age,sex) } var son = new Son('小米',18,'男') console.log(son); //apply() function fn(a, b) { console.log(a + b); console.log(this); } var o = {} fn() //此時的this指向的是window fn.apply(o, [2, 3]) //此時的this指向的是對象o,參數使用數組傳遞 // 1. 也是調用函數 第二個可以改變函數內部的this指向 // 2. 但是他的參數必須是數組(僞數組) //bind() // 1. 不會調用原來的函數 可以改變原來函數內部的this 指向 // 2. 返回的是原函數改變this之後產生的新函數 // 3. 如果有的函數我們不需要立即調用,但是又想改變這個函數內部的this指向此時用bind function fn() { console.log(this); } var o = { name: '測試' } // fn.bind(o)()相當於以下代碼 var fn1 = fn.bind(o) fn1()
call、apply、bind三者的異同
-
共同點 : 都可以改變this指向
-
不同點:
-
call 和 apply 會調用函數, 並且改變函數內部this指向.
-
call 和 apply傳遞的參數不一樣,call傳遞參數使用逗號隔開,apply使用數組傳遞
-
bind 不會調用函數, 可以改變函數內部this指向.
-
-
應用場景
-
call 經常做繼承.
-
apply經常跟數組有關係. 比如藉助於數學對象實現數組最大值最小值
-
bind 不調用函數,但是還想改變this指向. 比如改變定時器內部的this指向.
-
數組的方法
//forEach 替代for循環,缺點不能提前中止 var arr = [1, 2, 3]; var sum = 0; // forEach不能用break提前終止 arr.forEach(function (value, index, array) { console.log(value); //數組每個元素 console.log(index); //數組每個元素的索引 console.log(array); //數組 sum += value; }) console.log(sum); //map:映射 var arr = [2, 3, 4] var arr1 = arr.map(function (value) { return value * value; }) console.log(arr1); //filter:過濾 var arr = [12, 5, 36, 15, 21] var newArr = arr.filter(function (value, index, arrat) { // return value > 20 return value % 2 == 0 }) console.log(newArr); //some:判斷只要有一個滿足就是true var arr = [12, 5, 36, 15, 21] //返回值是布爾值,只要查找到滿足條件的一個元素就立馬終止循環 // 遍歷數組,判斷是否有滿足條件的元素,如果有返回true,如果沒有返回false var bool = arr.some(function (value, index, arrat) { return value % 2 == 0 }) console.log(bool); //every:只有每一項滿足就是true var arr = [12, 5, 36, 15, 21] //返回值是布爾值,只要查找到滿足條件的一個元素就立馬終止循環 // 遍歷數組,判斷是否有滿足條件的元素,如果有返回true,如果沒有返回false var bool = arr.every(function (value, index, arrat) { return value % 2 == 0 }) console.log(bool); //reduce var arr = [2, 3, 4] var sum = arr.reduce(function (a, b) { console.log(a,b); return a + b; }) console.log(sum); //find let arr = [{ id: 1, name: '張三' }, { id: 2, name: '李四' }, { id: 3, name: '王五' }]; var obj = arr.find(item => item.id == 2) console.log(obj); console.log(obj instanceof Object); //找數組裏面符合條件的值,當數組中元素id等於2的查找出來,注意,只會匹配第一個 //要查找的條件,寫到箭頭函數的函數體中 //find接收的箭頭函數的參數是固定的,item,屬性值 //注意這裏的屬性值item是一個個的對象 //obj是id爲2的item對象 //findIndex let arr = [{ id: 1, name: '張三' }, { id: 2, name: '李四' }, { id: 3, name: '王五' }]; var obj = arr.findIndex((item, index) => item.id == 2) console.log(obj); //1 返回的是索引號 //Array.from var arr = { 0: 1, 1: 2, 2: 3, length: 3 } var arr1 = Array.from(arr); //轉化成真正的數組 console.log(arr1); console.log(arr1 instanceof Array); //true //擴展運算符... 將數組轉爲用逗號分隔的參數序列 let ary = [1, 2, 3]; ...ary // 1, 2, 3 console.log(...ary); // 1 2 3,相當於下面的代碼 console.log(1,2,3); //擴展運算符可以應用於合併數組 // 方法一 let ary1 = [1, 2, 3]; let ary2 = [3, 4, 5]; let ary3 = [...ary1, ...ary2]; // 方法二 ary1.push(...ary2); //方式三 //concat() 方法用於連接兩個或多個數組。 console.log(ary1.concat(ary2)); //將僞數組或可遍歷對象轉換爲真正的數組 let oDivs = document.getElementsByTagName('div'); oDivs = [...oDivs];
數組方法練習---商品案例
<style> * { margin: 0; padding: 0; box-sizing: border-box; } .box { width: 500px; height: 400px; background-color: pink; border: 1px solid black; margin: 100px auto; } .bHead { width: 100%; height: 50px; text-align: center; border: 2px solid green; } .inText { width: 60px; height: 20px; } .bBody { width: 100%; height: 350px; text-align: center; border: 2px solid green; } .bBody table { width: 100%; height: 100%; } </style> </head> <body> <div class="box"> <div class="bHead"> 價格: <input type="text" class="inText" id="star">-<input type="text" class="inText" id="end"> <button id="pBtn">查詢</button> 品牌: <input type="text" class="inText" id="bText"> <button id="tBtn">查詢</button> </div> <div class="bBody"> <table border="1" cellspacing=0> <thead> <tr> <th>ID</th> <th>類型</th> <th>價格</th> </tr> </thead> <tbody> </tbody> </table> </div> </div> <script> var arr = [{ id: 1, name: '小米', price: 3099 }, { id: 2, name: 'oppo', price: 1999 }, { id: 3, name: '華爲', price: 3599 } ] var tbody = document.querySelector('tbody'); var star = document.querySelector('#star'); var end = document.querySelector('#end'); var pBtn = document.querySelector('#pBtn'); var tBtn = document.querySelector('#tBtn'); var inText = document.querySelector('#bText'); function newArr(myArr) { tbody.innerHTML = '' myArr.forEach(function (value) { var tr = document.createElement('tr'); tr.innerHTML = '<td>' + value.id + '</td><td>' + value.name + '</td><td>' + value.price + '</td>'; tbody.appendChild(tr) }) } newArr(arr); pBtn.addEventListener('click', function () { // console.log('123'); var arr1 = arr.filter(function (value) { // console.log(value);//返回的是arr數組中的對象 return value.price >= star.value && value.price <= end.value; }) console.log(arr1); newArr(arr1); }) tBtn.addEventListener('click', function () { // console.log(inText) // console.log('123'); var searchArr = []; arr.some(function (value) { // console.log(value.name, inText.value) if (value.name == inText.value) { searchArr.push(value); console.log(searchArr); } console.log(searchArr) newArr(searchArr); }) }) </script> </body>
trim()方法去除空格
<input type="text"> <button>點擊</button> <script> var input = document.querySelector('input'); var btn = document.querySelector('button'); btn.addEventListener('click', function () { var str = input.value.trim(); // console.log(str); if (str == '') { alert('請輸入內容') } else { console.log(str); console.log(str.length); } }) </script> //正則表達式去除空格 // 自己實現一個trim String.prototype.trim1 = function () { return this.replace(/^\s+|\s$/g, "") } var str = ' 123'; var str1 = ' 456 '; console.log(str.trim1().length);
獲取對象的屬性名和數組的屬性值
Object.keys(對象)和Object.values(對象) 返回值是一個數組
var data = { id: 1, pname: '小米', price: 3999 } var arr1 = Object.keys(data); var arr2 = Object.values(data); console.log(arr1); console.log(arr2);
修改或者設置對象中的屬性Object.defineProperty
Object.defineProperty(對象,修改或新增的屬性名,{ value:修改或新增的屬性的值, writable:true/false, //如果值爲false 不允許修改這個屬性值 enumerable: false, //enumerable 如果值爲false 則不允許遍歷 configurable: false //configurable 如果爲false 則不允許刪除這個屬性 屬性是否可以被刪除或是否可以再次修改特性 }) // writable/enumerable/configurable的默認值都爲true.
函數的進階
高階函數是對其他函數進行操作的函數,它接收函數作爲參數或將函數作爲返回值輸出
//函數作爲參數 function eat(fn) { setTimeout(function () { console.log('喫晚飯'); // 喫完晚飯之後做的事情 fn(); }, 2000); } eat(function () { console.log('去唱歌'); }); //函數作爲返回值輸出 // 第一次調用生成隨機數,以後每次調用都返回第一次的隨機值 function getRandom() { var random = parseInt(Math.random() * 10) + 1; return function () { return random; } } var fn = getRandom(); console.log(fn()); console.log(fn()); console.log(fn());
閉包 在一個作用域中可以訪問另一個作用域的變量
function fn() { var n = 10; return function () { return n; } } var f = fn(); console.log(f()); /*閉包特點:延展了函數的作用域範圍 fn返回值是一個函數, 這個函數的返回值是n, 因爲n將來還需要被使用, 所以fn運行完畢之後不會被系統銷燬, 而是等着將來提供n, 所以產生了閉包*/
遞歸 函數自己調用自己 遞歸,一般都要寫一個結束的條件
var count = 0; //由於遞歸很容易發生“棧溢出”錯誤(stack overflow),所以必須要加退出條件return function fn() { console.log(123); count++; if (count == 6) { return; // 遞歸裏面必須加退出條件 } fn(); //fn自己調用自己,fn就是遞歸函數 } fn(); //遞歸求斐波那契數列 <script> function fn(n) { if (n === 1 || n === 2) { return 1 } return fn(n - 1) + fn(n - 2) } console.log(fn(10)); </script>
拷貝(淺拷貝和深拷貝)
//淺拷貝 /* 淺拷貝, 對象中的基本數據類型拷貝沒問題, 但是如果對象中的複雜數據類型拷貝就有問題, 修改一個值, 都會跟着變化. 如下, 如果拷貝的obj1中修改了c的m, 那麼obj2的name會跟着變化 淺拷貝只拷貝對象的第一層屬性, 只拷貝了引用 */ var obj1 = { a: 2, b: 3, c: { m: 1, n: 2 } } // 思路一:用 for ... in 一個屬性,賦值給新的對象 // var obj2 = {} // for (var key in obj1) { // obj2[key] = obj1[key] // }; // console.log(obj2); // 思路二 var obj2 = Object.assign({}, obj1) obj2.a = 4; obj1.c.m = 5; //此時兩個對象都受影響 console.log(obj1); console.log(obj2); </script> //深拷貝 /* 必須先判斷是否是組數, 再判斷是否是對象, 因爲數組也是對象, 所以反過來的話, 會把所有的數組也當成對象來處理了 */ var obj = { a: 2, b: 3, c: { m: 1, n: 2 }, d: [2, 3, 4, 5, 6] } var o = {} function deepCopy(newobj, oldobj) { for (var k in oldobj) { if (oldobj[k] instanceof Array) { newobj[k] = deepCopy([], oldobj[k]) } else if (oldobj[k] instanceof Object) { newobj[k] = deepCopy({}, oldobj[k]) } else { newobj[k] = oldobj[k] } }; return newobj; } deepCopy(o, obj); console.log(o); console.log(obj);
正則表達式 用於匹配字符串中字符組合的模式
邊界符
邊界符 | 說明 |
---|---|
^ | 表示匹配行首的文本(以誰開始) |
$ | 表示匹配行尾的文本(以誰結束) |
如果 ^和 $ 在一起,表示必須是精確匹配。
var rg = /abc/; // 正則表達式裏面不需要加引號 不管是數字型還是字符串型 // /abc/ 只要包含有abc這個字符串返回的都是true console.log(rg.test('abc'));//true console.log(rg.test('abcd'));//true console.log(rg.test('aabcd'));//true console.log('---------------------------'); var reg = /^abc/; console.log(reg.test('abc')); // true console.log(reg.test('abcd')); // true console.log(reg.test('aabcd')); // false console.log('---------------------------'); var reg1 = /^abc$/; // 精確匹配 要求必須是 abc字符串才符合規範 console.log(reg1.test('abc')); // true console.log(reg1.test('abcd')); // false console.log(reg1.test('aabcd')); // false console.log(reg1.test('abcabc')); // false
字符類
[]表示有一系列字符可供選擇,只要匹配其中一個就可以了
// 中括號外邊的^爲邊界符,代表從什麼開始 var reg = /^[a,b,c]$/ console.log(reg.test('a')); //中括號裏面的^意思爲取反 var reg1 = /^[^a,b,c]$/ console.log(reg1.test('a'));
量詞符
量詞 | 說明 |
---|---|
* | 重複0次或更多次(*,理解爲任意)(0,1,n) |
+ | 重複1次或更多次(+:代表大於等於1的數。以0爲分界線,左邊是負數-,右邊是正數+) |
? | 重複0次或1次 (?問號,理解爲有沒有) |
{n} | 重複n次 |
{n,} | 重複n次或更多次(沒有第二個數字,那就意味着是?,沒有上界) |
{n,m} | 重複n到m次 ({}:區間符號,次數區間) |
// 1次或者多次 >=1 var reg1 = /^a+$/ // 0次或者多次 >=0 var reg2 = /^a*$/ // 0次或者1次 0||1 var reg3 = /^a?$/ // 3次 var reg4 = /^a{3}$/ // 3-5次 var reg5 = /^a{3,5}$/ // 3次以上 var reg6 = /^a{3,}$/ // 只能是數字 字母 下劃線組成 var reg7 = /^[a-zA-Z0-9_]*$/ // 只能是數字 字母 下劃線組成,長度6-18 var reg8 = /^[a-zA-Z0-9_]{6,18}$/
括號總結
1.大括號 量詞符. 裏面表示重複次數
2.中括號 字符集合。匹配方括號中的任意字符.
3.小括號表示優先級
預定義類
理解:
d:digit ,數字
w:word,單詞
s:space,空白,間隔
預定義類 | 含義 |
---|---|
/d | 0-9任一數字,相當於[0-9] |
/D | 0-9數字以外的任意字符,相當於0-9 |
/w | 任意的數字字母以及下劃線 [0-9a-zA-Z_] |
/W | 除去數字字母以及下劃線的字符 0-9a-zA-Z_ |
/s | 匹配空格 (回車 tab 空格) 相當於[\t\r\n\v\f] |
/S | 匹配非空格 字符相當於[6\t\r\n\v\f] |
正則替換replace
replace() 方法可以實現替換字符串操作,用來替換的參數可以是一個字符串或是一個正則表達式。
//過濾敏感詞彙案例 <textarea name="" id="message"></textarea> <button>提交</button> <div></div> <script> var text = document.querySelector('textarea'); var btn = document.querySelector('button'); var div = document.querySelector('div'); btn.onclick = function() { div.innerHTML = text.value.replace(/激情|gay/g, '**'); } </script>
let
特點: 1. let聲明的變量只存在於塊級作用域 2.不存在變量提升 3.存在暫時性死區
//塊級作用域 if (true) { // 大括號可以形成塊級作用域 let a = 10; } console.log(a) //報錯: a is not defined //無變量提升 console.log(a); // a is not defined let a = 20; //暫時性死區 利用let聲明的變量會綁定在這個塊級作用域,不會受外界的影響 var tmp = 123; if (true) { //因爲在此塊級作用域中,使用了let聲明瞭tmp變量,那麼tmp會與此塊級作用域進行綁定,形成死區,不受外界影響(無法訪問外部的tmp) tmp = 'abc';//這裏會報錯,變量未聲明,不能賦值 let tmp; }
let經典面試題
此題的關鍵點在於變量i是全局的,函數執行時輸出的都是全局作用域下的i值。 //沒有用到let var arr = []; for (var i = 0; i < 2; i++) { arr[i] = function () { console.log(i); } } arr[0]();//2 arr[1]();//2 //用到let的時候 let arr = []; for (let i = 0; i < 2; i++) { arr[i] = function () { console.log(i); } } arr[0]();//0 arr[1]();//1 /* - 此題的關鍵點在於每次循環都會產生一個塊級作用域, - 每個塊級作用域中的變量都是不同的, - 函數執行時輸出的是自己上一級(循環產生的塊級作用域)作用域下的i值. */
小結
-
let關鍵字就是用來聲明變量的
-
使用let關鍵字聲明的變量具有塊級作用域
-
在一個大括號中 使用let關鍵字聲明的變量才具有塊級作用域 var關鍵字是不具備這個特點的
-
防止循環變量變成全局變量
-
循環中的i,使用var聲明的話是全局變量,但是這樣不合理,而使用let就是局部變量
-
-
使用let關鍵字聲明的變量沒有變量提升
-
使用let關鍵字聲明的變量具有暫時性死區特性
-
死區:不受外界影響
-
const 聲明常量,常量就是值(內存地址)不能變化的量
常量:常態的量,值是常態的量,值無法改變
(const:constant常量)
1.存在塊級作用域 2.聲明的常量必須賦值,且賦值後無法修改 3.不存在變量提升
//存在塊級作用域 if (true) { const a = 10; } console.log(a) // a is not defined //聲明的常量必須賦值,且賦值後無法修改 const PI = 3.14; PI = 100; // Assignment to constant variable. //給常量重新指定值 const ary = [100, 200]; ary[0] = 'a';//複雜數據類型內部的值,是可以改變的 ary[1] = 'b'; console.log(ary); // ['a', 'b']; ary = ['a', 'b']; // Assignment to constant variable.//但是複雜數據類型變量本身無法改變
小結
-
const聲明的變量是一個常量
-
既然是常量不能重新進行賦值,如果是基本數據類型,不能更改值,如果是複雜數據類型,不能更改地址值
-
複雜數據類型變量中存儲的是地址值
-
-
聲明 const時候必須要給定初始值(因爲沒有辦法重新賦值,所以在聲明時必須有初始值)
var let const 函數級作用域 塊級作用域 塊級作用域 變量提升 不存在變量提升 不存在變量提升 值可更改 值可更改 值不可更改 解構賦值 可以讓我們更快捷的從數組或對象中提取值,然後對變量賦值
//數組 let [a, b, c] = [1, 2, 3]; console.log(a)//1 console.log(b)//2 console.log(c)//3 //如果解構不成功,變量的值爲undefined //對象 let { name, age } = { name: 'zhangsan', age: 20 }; console.log(name); // 'zhangsan' console.log(age); // 20 //注意這裏的name僅僅是用於屬性匹配,不在是變量,myName纔是變量 let {name: myName, age: myAge} = { name: 'zhangsan', age: 20 }; console.log(myName); // 'zhangsan' console.log(name);//無法獲取到zhangsan console.log(myAge); // 20 //如果變量不想與對象的屬性名保持一致,那麼採用第二種方式解構
小結
-
解構賦值就是把數據結構分解,然後給變量進行賦值
-
如果解構不成功,變量跟數值個數不匹配的時候,變量的值爲undefined
-
數組解構用中括號包裹,多個變量用逗號隔開,對象解構用花括號包裹,多個變量用逗號隔開
-
利用解構賦值能夠讓我們方便的去取對象中的屬性跟方法
-
補充:
//不管是數組還是對象在去處理結構賦值時,都是通過key匹配變量,然後進行賦值. let [a, b, c] = [0:1, 1:2, 2:3]; let { name, age } = { name: 'zhangsan', age: 20 };
-
箭頭函數
-
箭頭函數中不綁定this,箭頭函數中的this指向是它所定義的位置
-
理解:箭頭函數不綁定this,箭頭函數沒有自己的this關鍵字,如果在箭頭函數中使用this,this關鍵字將指向箭頭函數定義位置中的this
-
再理解:理解爲當前箭頭函數作用域中沒有this,要向上一級作用域查找
-
-
箭頭函數的優點在於解決了this執行環境所造成的一些問題。
-
比如:解決了匿名函數this指向的問題(匿名函數的執行環境具有全局性),包括setTimeout和setInterval中使用this所造成的問題
-
//語法 /*函數體中只有一句代碼,且代碼的執行結果就是返回值,可以省略大括號 如果形參只有一個,可以省略小括號 箭頭函數不綁定this關鍵字,箭頭函數中的this,指向的是函數定義位置的上下文this */ () => {} //():代表是函數; =>:代表指向,指向哪一個代碼塊;{}:代碼塊,函數體 const fn = () => {}//代表把一個函數賦值給fn //示例 const obj = { name: '張三'} function fn () { console.log(this);//this 指向 是obj對象 return () => { console.log(this); //this 指向 的是箭頭函數定義區域的this,那麼這個箭頭函數定義在fn裏面,而這個fn指向是的obj對象,所以這個this也指向是obj對象 //理解爲當前箭頭函數作用域中沒有this,要向上一級作用域查找 } } const resFn = fn.call(obj); resFn();
string擴展方法
模板字符串中可以寫代碼,html代碼(可以識別空格和換行等格式),js代碼(寫在${})
1.模板字符串中可以解析變量 2.模板字符串中可以換行 3.在模板字符串中可以調用函數 4.同樣是通過${}來調用
let obj = { uname: '張三', age: 18 } ////模板字符串中,通過${變量名}的方式來獲取變量值 var str = `你的名字是${obj.uname},你的年齡是${obj.age},${Math.random()}` console.log(str);//你的名字是張三,你的年齡是18,0.8866876958290935(隨機數) var str1 = `<div> <h3>${obj.uname}</h3></div>` console.log(str1);//<div> <h3>張三</h3></div>
實例方法
startsWith():表示參數字符串是否在原字符串的頭部,返回布爾值
endsWith():表示參數字符串是否在原字符串的尾部,返回布爾值
var str = 'abc.jpg'; var reg = /jpg$/ if (reg.test(str)) { console.log(1); } else { console.log(2); } console.log(str.endsWith('jpg')); //true 以什麼結尾 console.log(str.startsWith('abc')); //true 以什麼開頭
repeat():表示將原字符串重複n次,返回一個新字符串
'x'.repeat(3) // "xxx" 'hello'.repeat(2) // "hellohello"
set數據結構
實例方法:
-
add(value):添加某個值,返回 Set 結構本身
-
delete(value):刪除某個值,返回一個布爾值,表示刪除是否成功
-
has(value):返回一個布爾值,表示該值是否爲 Set 的成員
-
clear():清除所有成員,沒有返回值
const s = new Set(); s.add(1).add(2).add(3); // 向 set 結構中添加值 s.delete(2) // 刪除 set 結構中的2值 s.has(1) // 表示 set 結構中是否有1這個值 返回布爾值 s.clear() // 清除 set 結構中的所有值 //注意:刪除的是元素的值,不是代表的索引
遍歷
Set 結構的實例與數組一樣,也擁有forEach方法,用於對每個成員執行某種操作,沒有返回值。
const s = new Set(); s.add(1).add(2).add(3); s.forEach(value => console.log(value))