javascript
JavaScript 逆向 ( 一 ) --- JavaScript 语法基础
js 逆向:https://www.cnblogs.com/wuxianyu/category/1940304.html
js逆向2:https://www.cnblogs.com/wuxianyu/category/1941486.html
JS 中的類型轉換:https://segmentfault.com/a/1190000013679224
1、JavaScript 基礎
菜鳥教程 JavaScript 教程:https://www.runoob.com/js/js-tutorial.html
- 1. 基礎數據類型:number、string、boolean、null、undefined、object
- 2. 順序、條件、循環、比較
- 3. 函數
- 4. 運算符
js 數組遍歷方法總結:https://www.cnblogs.com/woshidouzia/p/9304603.html
JavaScript 作用域:
?? ??? ?https://www.h5w3.com/57138.html
?? ??? ?https://www.h5w3.com/62830.html
?? ??? ?https://www.h5w3.com/70576.html
?? ??? ?https://www.h5w3.com/34296.html
?? ??? ?https://www.h5w3.com/32056.html
?? ??? ?https://juejin.cn/post/6844904033308655623
強烈推薦書籍《JavaScript高級程序設計 第4版》:https://book.douban.com/subject/35175321/
示例:
1.1 Javascript 函數
:https://www.liaoxuefeng.com/wiki/1022910821149312/1023021053637728
JavaScript 中的函數是`頭等公民`,不僅可以像變量一樣使用它,同時它還具有十分強大的抽象能力;
定義函數的 2 種方式
在JavaScript 中,定義函數的方式如下:
function abs(x) {if (x >= 0) {return x;} else {return -x;} }上述abs()函數的定義如下:
- function指出這是一個函數定義;
- abs是函數的名稱;
- (x)括號內列出函數的參數,多個參數以,分隔;
- { ... }之間的代碼是函數體,可以包含若干語句,甚至可以沒有任何語句。
請注意,函數體內部的語句在執行時,一旦執行到return時,函數就執行完畢,并將結果返回。因此,函數內部通過條件判斷和循環可以實現非常復雜的邏輯。
如果沒有return語句,函數執行完畢后也會返回結果,只是結果為undefined。
由于JavaScript的函數也是一個對象,上述定義的abs()函數實際上是一個函數對象,而函數名abs可以視為指向該函數的變量。
因此,第二種定義函數的方式如下:
var abs = function (x) {if (x >= 0) {return x;} else {return -x;} };在這種方式下,function (x) { ... }是一個匿名函數,它沒有函數名。但是,這個匿名函數賦值給了變量abs,所以,通過變量abs就可以調用該函數。
注意:上述兩種定義完全等價,第二種方式按照完整語法需要在函數體末尾加一個;,表示賦值語句結束。( 加不加都一樣,如果按照語法完整性要求,需要加上?)
調用函數
調用函數時,按順序傳入參數即可:
abs(10); // 返回10 abs(-9); // 返回9由于JavaScript 允許傳入任意個參數而不影響調用,因此傳入的參數比定義的參數多也沒有問題,雖然函數內部并不需要這些參數:
abs(10, 'blablabla'); // 返回10 abs(-9, 'haha', 'hehe', null); // 返回9傳入的參數比定義的少也沒有問題:
abs(); // 返回NaN此時?abs(x)函數的參數x將收到undefined,計算結果為NaN。要避免收到?undefined,可以對參數進行檢查:
function abs(x) {if (typeof x !== 'number') {throw 'Not a number';}if (x >= 0) {return x;} else {return -x;} }arguments
JavaScript 還有一個免費贈送的關鍵字arguments,它只在函數內部起作用,并且永遠指向當前函數的調用者傳入的所有參數。arguments?類似?Array?但它不是一個Array:
'use strict'function foo(x) {console.log('x = ' + x); // 10for (var i=0; i<arguments.length; i++) {console.log('arg ' + i + ' = ' + arguments[i]); // 10, 20, 30} } foo(10, 20, 30);利用arguments,你可以獲得調用者傳入的所有參數。也就是說,即使函數不定義任何參數,還是可以拿到參數的值,實際上arguments最常用于判斷傳入參數的個數。
rest 參數
由于 JavaScrip t函數允許接收任意個參數,于是我們就不得不用arguments來獲取所有參數:
'use strict'function foo(a, b) {var i, rest = [];if (arguments.length > 2) {for (i = 2; i<arguments.length; i++) {rest.push(arguments[i]);}}console.log('a = ' + a);console.log('b = ' + b);console.log(rest); }為了獲取除了已定義參數a、b之外的參數,我們不得不用arguments,并且循環要從索引2開始以便排除前兩個參數,這種寫法很別扭,只是為了獲得額外的rest參數,有沒有更好的方法?
ES6 標準引入了rest參數,上面的函數可以改寫為:
function foo(a, b, ...rest) {console.log('a = ' + a);console.log('b = ' + b);console.log(rest); }foo(1, 2, 3, 4, 5); // 結果: // a = 1 // b = 2 // Array [ 3, 4, 5 ]foo(1); // 結果: // a = 1 // b = undefined // Array []rest參數只能寫在最后,前面用...標識,從運行結果可知,傳入的參數先綁定a、b,多余的參數以數組形式交給變量rest,所以,不再需要arguments我們就獲取了全部參數。
如果傳入的參數連正常定義的參數都沒填滿,也不要緊,rest參數會接收一個空數組(注意不是undefined)。
因為rest參數是ES6新標準,所以你需要測試一下瀏覽器是否支持。請用rest參數編寫一個sum()函數,接收任意個參數并返回它們的和:
'use strict'function sum(...rest) {console.log(rest) } sum(1,2,3,4,5)測試:
// 測試: var i, args = []; for (i=1; i<=100; i++) {args.push(i); } if (sum() !== 0) {console.log('測試失敗: sum() = ' + sum()); } else if (sum(1) !== 1) {console.log('測試失敗: sum(1) = ' + sum(1)); } else if (sum(2, 3) !== 5) {console.log('測試失敗: sum(2, 3) = ' + sum(2, 3)); } else if (sum.apply(null, args) !== 5050) {console.log('測試失敗: sum(1, 2, 3, ..., 100) = ' + sum.apply(null, args)); } else {console.log('測試通過!'); }小心你的 return 語句
JavaScript 引擎有一個在行末自動添加分號的機制,
這可能讓你栽到return語句的一個大坑。。。
示例:
function foo() {return { name: 'foo' }; }foo(); // { name: 'foo' }如果把 return 語句拆成兩行:
function foo() {return{ name: 'foo' }; }foo(); // undefined要小心了,由于 JavaScript 引擎在行末自動添加分號的機制,上面的代碼實際上變成了:
function foo() {return; // 自動添加了分號,相當于return undefined;{ name: 'foo' }; // 這行語句已經沒法執行到了 }所以正確的多行寫法是:
function foo() {return { // 這里不會自動加分號,因為{表示語句尚未結束name: 'foo'}; }變量作用域與解構賦值
在 JavaScript 中,用var聲明的變量實際上是有作用域的。
如果一個變量在函數體內部聲明,則該變量的作用域為整個函數體,在函數體外不可引用該變量:
'use strict';function foo() {var x = 1;x = x + 1; }x = x + 2; // ReferenceError! 無法在函數體外引用變量x如果兩個不同的函數各自聲明了同一個變量,那么該變量只在各自的函數體內起作用。換句話說,不同函數內部的同名變量互相獨立,互不影響:
'use strict';function foo() {var x = 1;x = x + 1; }function bar() {var x = 'A';x = x + 'B'; }由于 JavaScript 的函數可以嵌套,此時,內部函數可以訪問外部函數定義的變量,反過來則不行:
'use strict';function foo() {var x = 1;function bar() {var y = x + 1; // bar可以訪問foo的變量x!}var z = y + 1; // ReferenceError! foo不可以訪問bar的變量y! }如果內部函數和外部函數的變量名重名怎么辦?來測試一下:
'use strict'function foo() {var x = 1;function bar() {var x = 'A';console.log('x in bar() = ' + x); // 'A'}console.log('x in foo() = ' + x); // 1bar(); }foo(); /* x in foo() = 1 x in bar() = A */這說明 JavaScript 的函數在查找變量時從自身函數定義開始,從“內”向“外”查找。如果內部函數定義了與外部函數重名的變量,則內部函數的變量將“屏蔽”外部函數的變量。
變量提升
JavaScript的函數定義有個特點,它會先掃描整個函數體的語句,把所有申明的變量“提升”到函數頂部:
'use strict';function foo() {var x = 'Hello, ' + y;console.log(x);var y = 'Bob'; }foo();雖然是strict模式,但語句var x = 'Hello, ' + y;并不報錯,原因是變量y在稍后申明了。但是console.log顯示Hello, undefined,說明變量y的值為undefined。這正是因為JavaScript引擎自動提升了變量y的聲明,但不會提升變量y的賦值。
對于上述foo()函數,JavaScript引擎看到的代碼相當于:
function foo() {var y; // 提升變量y的申明,此時y為undefinedvar x = 'Hello, ' + y;console.log(x);y = 'Bob'; }由于JavaScript的這一怪異的“特性”,我們在函數內部定義變量時,請嚴格遵守“在函數內部首先申明所有變量”這一規則。最常見的做法是用一個var申明函數內部用到的所有變量:
function foo() {varx = 1, // x初始化為1y = x + 1, // y初始化為2z, i; // z和i為undefined// 其他語句:for (i=0; i<100; i++) {...} }全局作用域
不在任何函數內定義的變量就具有全局作用域。實際上,JavaScript 默認有一個全局對象?window,全局作用域的變量實際上被綁定到?window?的一個屬性。 在 nodejs 中,是綁定到?global 這個變量中
'use strict';var course = 'Learn JavaScript'; alert(course); // 'Learn JavaScript' alert(window.course); // 'Learn JavaScript'因此,直接訪問全局變量course和訪問window.course是完全一樣的。
你可能猜到了,由于函數定義有兩種方式,以變量方式var foo = function () {}定義的函數實際上也是一個全局變量,因此,頂層函數的定義也被視為一個全局變量,并綁定到window對象:
'use strict';function foo() {alert('foo'); }foo(); // 直接調用foo() window.foo(); // 通過window.foo()調用進一步大膽地猜測,我們每次直接調用的alert()函數其實也是window的一個變量:
'use strict';window.alert('調用window.alert()'); // 把alert保存到另一個變量: var old_alert = window.alert; // 給alert賦一個新函數: window.alert = function () {} // alert('無法用alert()顯示了!');恢復 alert?
// 恢復alert: window.alert = old_alert; alert('又可以用alert()了!');這說明JavaScript實際上只有一個全局作用域。任何變量(函數也視為變量),如果沒有在當前函數作用域中找到,就會繼續往上查找,最后如果在全局作用域中也沒有找到,則報ReferenceError錯誤。
名字空間
全局變量會綁定到window上,不同的JavaScript文件如果使用了相同的全局變量,或者定義了相同名字的頂層函數,都會造成命名沖突,并且很難被發現。
減少沖突的一個方法是把自己的所有變量和函數全部綁定到一個全局變量中。例如:
// 唯一的全局變量MYAPP: var MYAPP = {};// 其他變量: MYAPP.name = 'myapp'; MYAPP.version = 1.0;// 其他函數: MYAPP.foo = function () {return 'foo'; };把自己的代碼全部放入唯一的名字空間MYAPP中,會大大減少全局變量沖突的可能。
許多著名的JavaScript庫都是這么干的:jQuery,YUI,underscore等等。
局部作用域
由于JavaScript的變量作用域實際上是函數內部,我們在for循環等語句塊中是無法定義具有局部作用域的變量的:
'use strict';function foo() {for (var i=0; i<100; i++) {//}i += 100; // 仍然可以引用變量i }為了解決塊級作用域,ES6引入了新的關鍵字let,用let替代var可以申明一個塊級作用域的變量:
'use strict';function foo() {var sum = 0;for (let i=0; i<100; i++) {sum += i;}// SyntaxError:i += 1; }常量
由于var和let申明的是變量,如果要申明一個常量,在ES6之前是不行的,我們通常用全部大寫的變量來表示“這是一個常量,不要修改它的值”:
var PI = 3.14;ES6標準引入了新的關鍵字const來定義常量,const與let都具有塊級作用域:
'use strict';const PI = 3.14; PI = 3; // 某些瀏覽器不報錯,但是無效果! PI; // 3.14解構賦值
從ES6開始,JavaScript引入了解構賦值,可以同時對一組變量進行賦值。
什么是解構賦值?我們先看看傳統的做法,如何把一個數組的元素分別賦值給幾個變量:
var array = ['hello', 'JavaScript', 'ES6']; var x = array[0]; var y = array[1]; var z = array[2];現在,在ES6中,可以使用解構賦值,直接對多個變量同時賦值:
'use strict'// 如果瀏覽器支持解構賦值就不會報錯 // x, y, z分別被賦值為數組對應元素: var [x, y, z] = ['hello', 'JavaScript', 'ES6']; console.log('x = ' + x + ', y = ' + y + ', z = ' + z);注意,對數組元素進行解構賦值時,多個變量要用[...]括起來。
如果數組本身還有嵌套,也可以通過下面的形式進行解構賦值,注意嵌套層次和位置要保持一致:
let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']]; x; // 'hello' y; // 'JavaScript' z; // 'ES6'解構賦值還可以忽略某些元素:
let [, , z] = ['hello', 'JavaScript', 'ES6']; // 忽略前兩個元素,只對z賦值第三個元素 z; // 'ES6'如果需要從一個對象中取出若干屬性,也可以使用解構賦值,便于快速獲取對象的指定屬性:
'use strict'var person = {name: '小明',age: 20,gender: 'male',passport: 'G-12345678',school: 'No.4 middle school' }; var {name, age, passport} = person; // name, age, passport分別被賦值為對應屬性: console.log('name = ' + name + ', age = ' + age + ', passport = ' + passport);對一個對象進行解構賦值時,同樣可以直接對嵌套的對象屬性進行賦值,只要保證對應的層次是一致的:
var person = {name: '小明',age: 20,gender: 'male',passport: 'G-12345678',school: 'No.4 middle school',address: {city: 'Beijing',street: 'No.1 Road',zipcode: '100001'} }; var {name, address: {city, zip}} = person; name; // '小明' city; // 'Beijing' zip; // undefined, 因為屬性名是zipcode而不是zip // 注意: address不是變量,而是為了讓city和zip獲得嵌套的address對象的屬性: address; // Uncaught ReferenceError: address is not defined使用解構賦值對對象屬性進行賦值時,如果對應的屬性不存在,變量將被賦值為undefined,這和引用一個不存在的屬性獲得undefined是一致的。如果要使用的變量名和屬性名不一致,可以用下面的語法獲取:
var person = {name: '小明',age: 20,gender: 'male',passport: 'G-12345678',school: 'No.4 middle school' };// 把passport屬性賦值給變量id: let {name, passport:id} = person; name; // '小明' id; // 'G-12345678' // 注意: passport不是變量,而是為了讓變量id獲得passport屬性: passport; // Uncaught ReferenceError: passport is not defined解構賦值還可以使用默認值,這樣就避免了不存在的屬性返回undefined的問題:
var person = {name: '小明',age: 20,gender: 'male',passport: 'G-12345678' };// 如果person對象沒有single屬性,默認賦值為true: var {name, single=true} = person; name; // '小明' single; // true有些時候,如果變量已經被聲明了,再次賦值的時候,正確的寫法也會報語法錯誤:
// 聲明變量: var x, y; // 解構賦值: {x, y} = { name: '小明', x: 100, y: 200}; // 語法錯誤: Uncaught SyntaxError: Unexpected token =這是因為JavaScript引擎把{開頭的語句當作了塊處理,于是=不再合法。解決方法是用小括號括起來:
({x, y} = { name: '小明', x: 100, y: 200});使用場景
解構賦值在很多時候可以大大簡化代碼。例如,交換兩個變量x和y的值,可以這么寫,不再需要臨時變量:
var x=1, y=2; [x, y] = [y, x]快速獲取當前頁面的域名和路徑:
var {hostname:domain, pathname:path} = location;如果一個函數接收一個對象作為參數,那么,可以使用解構直接把對象的屬性綁定到變量中。例如,下面的函數可以快速創建一個Date對象:
function buildDate({year, month, day, hour=0, minute=0, second=0}) {return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second); }它的方便之處在于傳入的對象只需要year、month和day這三個屬性:
buildDate({ year: 2017, month: 1, day: 1 }); // Sun Jan 01 2017 00:00:00 GMT+0800 (CST)也可以傳入hour、minute和second屬性:
buildDate({ year: 2017, month: 1, day: 1, hour: 20, minute: 15 }); // Sun Jan 01 2017 20:15:00 GMT+0800 (CST)使用解構賦值可以減少代碼量,但是,需要在支持ES6解構賦值特性的現代瀏覽器中才能正常運行。目前支持解構賦值的瀏覽器包括Chrome,Firefox,Edge等。
補充幾個兩數交換的方法
// 方法 1 b=[a,a=b][0];// 方法 2 a=a+b-(b=a);// 方法 3 a=a^b; b=b^a; a=a^b;方法( 對象中的函數叫做方法 )
在一個對象中綁定函數,稱為這個對象的方法。
在JavaScript中,對象的定義是這樣的:
var xiaoming = {name: '小明',birth: 1990 };但是,如果我們給xiaoming綁定一個函數,就可以做更多的事情。比如,寫個age()方法,返回xiaoming的年齡:
var xiaoming = {name: '小明',birth: 1990,age: function () {var y = new Date().getFullYear();return y - this.birth;} };xiaoming.age; // function xiaoming.age() xiaoming.age(); // 今年調用是25,明年調用就變成26了綁定到對象上的函數稱為方法,和普通函數也沒啥區別,但是它在內部使用了一個this關鍵字,這個東東是什么?
在一個方法內部,this是一個特殊變量,它始終指向當前對象,也就是xiaoming這個變量。所以,this.birth可以拿到xiaoming的birth屬性。
讓我們拆開寫:
function getAge() {var y = new Date().getFullYear();return y - this.birth; }var xiaoming = {name: '小明',birth: 1990,age: getAge };xiaoming.age(); // 25, 正常結果 getAge(); // NaN單獨調用函數getAge()怎么返回了NaN?請注意,我們已經進入到了JavaScript的一個大坑里。
JavaScript的函數內部如果調用了this,那么這個this到底指向誰?
答案是,視情況而定!
如果以對象的方法形式調用,比如xiaoming.age(),該函數的this指向被調用的對象,也就是xiaoming,這是符合我們預期的。
如果單獨調用函數,比如getAge(),此時,該函數的this指向全局對象,也就是window。
坑爹啊!
更坑爹的是,如果這么寫:
var fn = xiaoming.age; // 先拿到xiaoming的age函數 fn(); // NaN也是不行的!要保證this指向正確,必須用obj.xxx()的形式調用!
由于這是一個巨大的設計錯誤,要想糾正可沒那么簡單。ECMA決定,在strict模式下讓函數的this指向undefined,因此,在strict模式下,你會得到一個錯誤:
'use strict';var xiaoming = {name: '小明',birth: 1990,age: function () {var y = new Date().getFullYear();return y - this.birth;} };var fn = xiaoming.age; fn(); // Uncaught TypeError: Cannot read property 'birth' of undefined這個決定只是讓錯誤及時暴露出來,并沒有解決this應該指向的正確位置。
有些時候,喜歡重構的你把方法重構了一下:
'use strict';var xiaoming = {name: '小明',birth: 1990,age: function () {function getAgeFromBirth() {var y = new Date().getFullYear();return y - this.birth;}return getAgeFromBirth();} };xiaoming.age(); // Uncaught TypeError: Cannot read property 'birth' of undefined結果又報錯了!原因是this指針只在age方法的函數內指向xiaoming,在函數內部定義的函數,this又指向undefined了!(在非strict模式下,它重新指向全局對象window!)
修復的辦法也不是沒有,我們用一個that變量首先捕獲this:
'use strict';var xiaoming = {name: '小明',birth: 1990,age: function () {var that = this; // 在方法內部一開始就捕獲thisfunction getAgeFromBirth() {var y = new Date().getFullYear();return y - that.birth; // 用that而不是this}return getAgeFromBirth();} };xiaoming.age(); // 25用var that = this;,你就可以放心地在方法內部定義其他函數,而不是把所有語句都堆到一個方法中。
apply
雖然在一個獨立的函數調用中,根據是否是strict模式,this指向undefined或window,不過,我們還是可以控制this的指向的!
要指定函數的this指向哪個對象,可以用函數本身的apply方法,它接收兩個參數,第一個參數就是需要綁定的this變量,第二個參數是Array,表示函數本身的參數。
用apply修復getAge()調用:
function getAge() {var y = new Date().getFullYear();return y - this.birth; }var xiaoming = {name: '小明',birth: 1990,age: getAge };xiaoming.age(); // 25 getAge.apply(xiaoming, []); // 25, this指向xiaoming, 參數為空另一個與apply()類似的方法是call(),唯一區別是:
-
apply()把參數打包成Array再傳入;
-
call()把參數按順序傳入。
比如調用Math.max(3, 5, 4),分別用apply()和call()實現如下:
Math.max.apply(null, [3, 5, 4]); // 5 Math.max.call(null, 3, 5, 4); // 5對普通函數調用,我們通常把this綁定為null。
裝飾器
利用apply(),我們還可以動態改變函數的行為。
JavaScript的所有對象都是動態的,即使內置的函數,我們也可以重新指向新的函數。
現在假定我們想統計一下代碼一共調用了多少次parseInt(),可以把所有的調用都找出來,然后手動加上count += 1,不過這樣做太傻了。最佳方案是用我們自己的函數替換掉默認的parseInt():
'use strict'var count = 0; var oldParseInt = parseInt; // 保存原函數var window = global window.parseInt = function () {count += 1;return oldParseInt.apply(null, arguments); // 調用原函數 };// 測試: parseInt('10'); parseInt('20'); parseInt('30'); console.log('count = ' + count); // 3高階函數
:https://www.liaoxuefeng.com/wiki/1022910821149312/1023021271742944
高階函數英文叫Higher-order function。那么什么是高階函數?
JavaScript的函數其實都指向某個變量。既然變量可以指向函數,函數的參數能接收變量,那么一個函數就可以接收另一個函數作為參數,這種函數就稱之為高階函數。
一個最簡單的高階函數:
function add(x, y, f) {return f(x) + f(y); }當我們調用add(-5, 6, Math.abs)時,參數x,y和f分別接收-5,6和函數Math.abs,根據函數定義,我們可以推導計算過程為:
x = -5; y = 6; f = Math.abs; f(x) + f(y) ==> Math.abs(-5) + Math.abs(6) ==> 11; return 11;map / reduce
map()方法定義在JavaScript的Array中,我們調用Array的map()方法,傳入我們自己的函數,就得到了一個新的Array作為結果:
'use strict'function pow(x) {return x * x; } var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; var results = arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81] console.log(results);filter
用于把Array的某些元素過濾掉,然后返回剩下的元素。
和map()類似,Array的filter()也接收一個函數。和map()不同的是,filter()把傳入的函數依次作用于每個元素,然后根據返回值是true還是false決定保留還是丟棄該元素。
例如,在一個Array中,刪掉偶數,只保留奇數,可以這么寫:
var arr = [1, 2, 4, 5, 6, 9, 10, 15]; var r = arr.filter(function (x) {return x % 2 !== 0; }); r; // [1, 5, 9, 15]把一個Array中的空字符串刪掉,可以這么寫:
var arr = ['A', '', 'B', null, undefined, 'C', ' ']; var r = arr.filter(function (s) {return s && s.trim(); // 注意:IE9以下的版本沒有trim()方法 }); r; // ['A', 'B', 'C']可見用filter()這個高階函數,關鍵在于正確實現一個“篩選”函數。
回調函數
filter()接收的回調函數,其實可以有多個參數。通常我們僅使用第一個參數,表示Array的某個元素。回調函數還可以接收另外兩個參數,表示元素的位置和數組本身:
var arr = ['A', 'B', 'C']; var r = arr.filter(function (element, index, self) {console.log(element); // 依次打印'A', 'B', 'C'console.log(index); // 依次打印0, 1, 2console.log(self); // self就是變量arrreturn true; });利用filter,可以巧妙地去除Array的重復元素:
'use strict'var r, arr = ['apple', 'strawberry', 'banana', 'pear', 'apple', 'orange', 'orange', 'strawberry']; r = arr.filter(function (element, index, self) {return self.indexOf(element) === index; }); console.log(r.toString());去除重復元素依靠的是indexOf總是返回第一個元素的位置,后續的重復元素位置與indexOf返回的位置不相等,因此被filter濾掉了。
Array?對象的其他高階函數
對于數組,除了map()、reduce、filter()、sort()這些方法可以傳入一個函數外,Array對象還提供了很多非常實用的高階函數。
every
every()方法可以判斷數組的所有元素是否滿足測試條件。
例如,給定一個包含若干字符串的數組,判斷所有字符串是否滿足指定的測試條件:
'use strict'var arr = ['Apple', 'pear', 'orange']; console.log(arr.every(function (s) {return s.length > 0; })); // true, 因為每個元素都滿足s.length>0console.log(arr.every(function (s) {return s.toLowerCase() === s; })); // false, 因為不是每個元素都全部是小寫find
find()方法用于查找符合條件的第一個元素,如果找到了,返回這個元素,否則,返回undefined:
var arr = ['Apple', 'pear', 'orange']; console.log(arr.find(function (s) {return s.toLowerCase() === s; })); // 'pear', 因為pear全部是小寫console.log(arr.find(function (s) {return s.toUpperCase() === s; })); // undefined, 因為沒有全部是大寫的元素findIndex
findIndex()和find()類似,也是查找符合條件的第一個元素,不同之處在于findIndex()會返回這個元素的索引,如果沒有找到,返回-1:
var arr = ['Apple', 'pear', 'orange']; console.log(arr.findIndex(function (s) {return s.toLowerCase() === s; })); // 1, 因為'pear'的索引是1console.log(arr.findIndex(function (s) {return s.toUpperCase() === s; })); // -1forEach
forEach()和map()類似,它也把每個元素依次作用于傳入的函數,但不會返回新的數組。forEach()常用于遍歷數組,因此,傳入的函數不需要返回值:
var arr = ['Apple', 'pear', 'orange']; arr.forEach(console.log); // 依次打印每個元素閉包
:https://www.liaoxuefeng.com/wiki/1022910821149312/1023021250770016
函數作為返回值:高階函數除了可以接受函數作為參數外,還可以把函數作為結果值返回。
返回閉包時牢記的一點就是:返回函數不要引用任何循環變量,或者后續會發生變化的變量。
箭頭函數
ES6 標準新增了一種新的函數:Arrow Function(箭頭函數)。
為什么叫Arrow Function?因為它的定義用的就是一個箭頭:
x => x * x上面的箭頭函數相當于:
function (x) {return x * x; }在繼續學習箭頭函數之前,請測試你的瀏覽器是否支持 ES 6的 Arrow Function:
var fn = x => x * x;箭頭函數相當于匿名函數,并且簡化了函數定義。箭頭函數有兩種格式,
- 一種像上面的,只包含一個表達式,連{ ... }和return都省略掉了。
- 還有一種可以包含多條語句,這時候就不能省略{ ... }和return:
如果參數不是一個,就需要用括號()括起來:
// 兩個參數: (x, y) => x * x + y * y// 無參數: () => 3.14// 可變參數: (x, y, ...rest) => {var i, sum = x + y;for (i=0; i<rest.length; i++) {sum += rest[i];}return sum; }如果要返回一個對象,就要注意,如果是單表達式,這么寫的話會報錯:
// SyntaxError: x => { foo: x }因為和函數體的{ ... }有語法沖突,所以要改為:
// ok: x => ({ foo: x })this
箭頭函數看上去是匿名函數的一種簡寫,但實際上,箭頭函數和匿名函數有個明顯的區別:箭頭函數內部的this是詞法作用域,由上下文確定。
回顧前面的例子,由于JavaScript函數對this綁定的錯誤處理,下面的例子無法得到預期結果:
var obj = {birth: 1990,getAge: function () {var b = this.birth; // 1990var fn = function () {return new Date().getFullYear() - this.birth; // this指向window或undefined};return fn();} };現在,箭頭函數完全修復了this的指向,this總是指向詞法作用域,也就是外層調用者obj:
var obj = {birth: 1990,getAge: function () {var b = this.birth; // 1990var fn = () => new Date().getFullYear() - this.birth; // this指向obj對象return fn();} }; obj.getAge(); // 25如果使用箭頭函數,以前的那種hack寫法:
var that = this;就不再需要了。
由于this在箭頭函數中已經按照詞法作用域綁定了,所以,用call()或者apply()調用箭頭函數時,無法對this進行綁定,即傳入的第一個參數被忽略:
var obj = {birth: 1990,getAge: function (year) {var b = this.birth; // 1990var fn = (y) => y - this.birth; // this.birth仍是1990return fn.call({birth:2000}, year);} }; obj.getAge(2015); // 25generator
:https://www.liaoxuefeng.com/wiki/1022910821149312/1023024381818112
generator(生成器)是ES6標準引入的新的數據類型。一個generator看上去像一個函數,但可以返回多次。
ES6定義generator標準的哥們借鑒了Python的generator的概念和語法,如果你對Python的generator很熟悉,那么ES6的generator就是小菜一碟了。如果你對Python還不熟,趕快惡補Python教程!。
1.2?Javascript 定義 類(class)的幾種方法
在面向對象編程中,類(class)是對象(object)的模板,定義了同一組對象(又稱"實例")共有的屬性和方法。類是對象的抽象,而對象是類的具體實例。類是抽象的,不占用內存,而對象是具體的,占用存儲空間。
早期的javascript需求都很簡單, 不支持面向對象,基本都是寫成函數的,然后是面向過程的寫法,后來慢慢的引入面向對象開發思想,再后來就慢慢寫成?類。
在 class 概念引入之前,js通過原型對象來實現類和類的繼承,具體可以參考前文(?面向對象的 JavaScript:封裝、繼承與多態:https://zhuanlan.zhihu.com/p/112779427?)
在 ECMAScript 6 出現 class 的概念之后,才算是告別了直接通過原型對象來模擬類和類繼承,但class 也只是基于 JavaScript 原型繼承的語法糖,并沒有引入新的對象繼承模式,所以理解原型以及原型繼承是非常重要的。通過 class 來創建對象,可以讓代碼更為簡潔,復用性更高。
在js中,寫成類的本質基本都是?構造函數+原型。下面,就討論一下js類的幾種寫法:
構造函數 法
這是經典方法,也是教科書必教的方法。它用構造函數模擬 "類",在其內部用 this 關鍵字指代實例對象。
function Cat() {this.name = "大毛"; }生成實例的時候,使用 new 關鍵字。
var cat1 = new Cat(); alert(cat1.name); // 大毛類的屬性和方法,還可以定義在構造函數的 prototype 對象之上。
Cat.prototype.makeSound = function(){alert("喵喵喵"); }關于這種方法的詳細介紹,查看系列文章《Javascript 面向對象編程》,這里就不多說了。它的主要缺點是,比較復雜,用到了 this 和 prototype,編寫和閱讀都很費力。
From:https://www.cnblogs.com/lidabo/archive/2011/12/17/2291238.html
一:定義類并創建類的實例對象
在Javascript中,我們用function來定義類,如下:
function Shape(){var x = 1 ;var y = 2 ; }你或許會說,疑?這個不是定義函數嗎?沒錯,這個是定義函數,我們定義了一個 Shape 函數,并對x和y進行了初始化。不過,如果你換個角度來看,這個就是定義一個Shape類,里面有兩個屬性x和y,初始值分別是1和2,只不過,我們定義類的關鍵字是 function 而不是 class。然后,我們可以創建Shape類的對象aShape,如下:
var?aShape?=?new?Shape();二:定義 公有屬性、私有屬性
我們已經創建了aShape對象,但是,當我們試著訪問它的屬性時,會出錯,如下:
aShape.x = 1 ;這說明,用 var 定義的屬性是私有的。我們需要使用 this 關鍵字來定義公有的屬性
function Shape(){this .x = 1 ;this .y = 2 ; }這樣,我們就可以訪問Shape的屬性了,如:aShape.x?=?2?;
總結得到:用 var 可以定義類的private屬性,而用 this 能定義類的 public 屬性。
三:定義 公有方法、私有方法
在Javascript中,函數是 Function 類的實例,Function 間接繼承自 Object,所以,函數也是一個對象,因此,我們可以用賦值的方法創建函數,當然,我們也可以將一個函數賦給類的一個屬性變量,那么,這個屬性變量就可以稱為方法,因為它是一個可以執行的函數。代碼如下:
function Shape() {var x = 0 ;var y = 1 ;this.draw = function (){// print;}; }我們在上面的代碼中定義了一個 draw,并把一個 function 賦給它,下面,我們就可以通過 aShape 調用這個函數,OOP 中稱為 公有方法,如:aShape.draw();
如果用 var 定義,那么這個 draw 就變成私有的了,OOP 中稱為私有方法,如
function Shape() {var x = 0 ;var y = 1 ;var draw = function (){// print;}; }這樣就不能使用aShape.draw調用這個函數了。
三:構造函數
Javascript 并不支持 OOP,當然也就沒有構造函數了,不過,我們可以自己模擬一個構造函數,讓對象被創建時自動調用,代碼如下:
function Shape() {var init = function (){// 構造函數代碼};init(); }在Shape的最后,我們人為的調用了init函數,那么,在創建了一個Shape對象是,init總會被自動調用,可以模擬我們的構造函數了。
四:帶參數的構造函數
如何讓構造函數帶參數呢?其實很簡單,將要傳入的參數寫入函數的參數列表中即可,如
function Shape(ax,ay) {var x = 0 ;var y = 0 ;var init = function (){// 構造函數x = ax;y = ay;};init(); }這樣,我們就可以這樣創建對象:var?aShape?=?new?Shape(?0?,?1?);
五:靜態屬性、靜態方法
在 Javascript 中如何定義靜態的屬性和方法呢?如下所示
function Shape(ax,ay){var x = 0 ;var y = 0 ;var init = function (){// 構造函數x = ax;y = ay;};init(); } Shape.count = 0 ; // 定義一個靜態屬性count,這個屬性是屬于類的,不是屬于對象的。 Shape.staticMethod = function (){}; // 定義一個靜態的方法有了靜態屬性和方法,我們就可以用類名來訪問它了,如下
alert(aShape.count); aShape.staticMethod();注意:靜態屬性和方法都是公有的,目前為止,我不知道如何讓靜態屬性和方法變成私有的~
六:在方法中訪問本類的公有屬性和私有屬性
在類的方法中訪問自己的屬性,Javascript對于公有屬性和私有屬性的訪問方法有所不同,請大家看下面的代碼
function Shape(ax,ay){var x = 0 ;var y = 0 ;this .gx = 0 ;this .gy = 0 ;var init = function (){x = ax; // 訪問私有屬性,直接寫變量名即可y = ay;this .gx = ax; // 訪問公有屬性,需要在變量名前加上this.this .gy = ay;};init(); }七:this 的注意事項
在 JavaScript 中,類中的 this 并不是一直指向我們的這個對象本身的,主要原因還是因為Javascript 并不是 OOP 語言,而且,函數 和 類 均用 function 定義,當然會引起一些小問題。
this 指針指錯的場合一般在事件處理上面,我們想讓某個對象的成員函數來響應某個事件,當事件被觸發以后,系統會調用我們這個成員函數,但是,傳入的 this 指針已經不是我們本身的對象了,當然,這時再在成員函數中調用this當然會出錯了。
解決方法是我們在定義類的一開始就將this保存到一個私有的屬性中,以后,我們可以用這個屬性代替this。我用這個方法使用this指針相當安全,而且很是省心~
我們修改一下代碼,解決this問題。對照第六部分的代碼看,你一定就明白了
示例:
/***定義類***/ var Class = function(){var _self = this;//把本身引用負值到一變量上var _Field = "Test Field"; //私有字段var privateMethod = function(){ //私有方法alert(_self.Property); //調用屬性}this.Property = "Test Property"; //公有屬性this.Method = function(){ //公有方法alert(_Field); //調用私用字段privateMethod(); //調用私用方法}/***構造函數***/var init = function(){privateMethod();}init(); }// 使用這個類 var c = new Class(); c.Method(); // 使用方法關于 Javascript 中的 OOP 實現就聊到這里,以上是最實用的內容,一般用 Javascript 定義類,創建對象用以上的代碼已經足夠了。當然,你還可以用 mootools 或 prototype 來定義類,創建對象。我用過mootools框架,感覺很不錯,它對 Javascript 的類模擬就更完善了,還支持類的繼承,有興趣的讀者可以去嘗試一下。當然,如果使用了框架,那么在你的網頁中就需要包含相關的js頭文件,因此我還是希望讀者能夠在沒有框架的情況下創建類,這樣,代碼效率較高,而且你也可以看到,要創建一個簡單的類并不麻煩~
示例 1:
/*** 封裝類方法* @methodOf Clazz.prototype*/ var Clazz = function() {}; /*** [給基類的原型賦值一個方法 當作類的構造器]* @return {[Object]} [description]*/ Clazz.prototype.construct = function() {}; /*** 創建類* @example* var MyClass = Clazz.extend({* //構造器,new時執行* construct: function(myParam){* // 編寫你的代碼邏輯* }* });** 繼承類* var MySubClass = MyClass.extend({* construct: function(myParam){* // 使用這個來調用父類的構造函數* arguments.callee.$.construct.apply(this, arguments);* // 編寫你的代碼邏輯* }* });*/ Clazz.extend = function(def) {var classDef = function() {if (arguments[0] !== Clazz) { this.construct.apply(this, arguments); }};var proto = new this(Clazz);var superClass = this.prototype;for (var n in def) {var item = def[n];if (item instanceof Function) item.$ = superClass;proto[n] = item;}classDef.prototype = proto;//給這個新類相同的靜態擴展方法classDef.extend = this.extend;return classDef; };//========使用實例========= var MyClass = Clazz.extend({construct: function(options){this.name = 'MyClass ';this.myClassName = 'myClassName ';},getName: function(){return this.name;},setName: function(name){if(name) this.name = name;} }); //繼承MyClass 類 var SubClass1 = MyClass .extend({construct: function(){//未調用父類的構造函數this.name = 'SubClass ';} }); //繼承MyClass 類 var SubClass2 = MyClass .extend({construct: function(){//調用父類構造函數arguments.callee.$.construct.apply(this, arguments);this.name = 'SubClass ';} });var myClass = new MyClass(); var subClass1 = new SubClass1(); var subClass2 = new SubClass2();console.log(myClass.getName()); //MyClass console.log(myClass.myClassName); //myClassNameconsole.log(subClass1.getName()); //SubClass1 console.log(subClass1.myClassName); //undefinedconsole.log(subClass2.getName()); //SubClass2 console.log(subClass2.myClassName); //myClassName示例 2:
/** * Person類:定義一個人,有name屬性和getName方法 */ <script>function Person(name){this.name = name;this.getName = function(){return this.name;}}//我們在這里實例化幾個對象var p1 = new Person("trigkit4");var p2 = new Person("mike");console.log(p1 instanceof Person);//trueconsole.log(p2 instanceof Person);//true </script>由上面控制臺輸出結果可知,p1和p2的確是類Person的實例對象。instanceof操作符左邊是待檢測類的對象,右邊是定義類的構造函數。這里,instanceof用來檢測對象p1是否屬于Person類。
這種方式的優點是:我們可以根據參數來構造不同的對象實例 ,缺點是每次構造實例對象時都會生成getName方法,造成了內存的浪費 。
我們可以用一個外部函數來代替類方法,達到了每個對象共享同一個方法。改寫后的類如下:
//外部函數 <script>function getName() {return this.name;}function Person(name){this.name = name;this.getName = getName;//} </script>原型方式
<script>function Person(){};Person.prototype.name = "trigkit4";//類的屬性都放在prototype上Person.prototype.getName = function(){return " I'm " + this.name;}var p1 = new Person();var p2 = new Person();console.log(p1.name);//trigkit4console.log(p2.getName());//I'm trigkit4 </script>原型方式:
- 缺點 就是不能通過參數來構造對象實例 (一般每個對象的屬性是不相同的) ,
- 優點 是所有對象實例都共享getName方法(相對于構造函數方式),沒有造成內存浪費 。
構造函數 + 原型方式
取前面兩種的優點:
- a、用構造函數來定義類屬性(字段)。
- b、用原型方式來定義類的方法。
這樣,我們就既可以構造不同屬性的對象,也可以讓對象實例共享方法,不會造成內存的浪費。為了讓js代碼風格更緊湊,我們讓prototype方法代碼移到?function Person?的大括號內。
<script>function Person(name){this.name = name;Person.prototype.getName = function(){return this.name;}}var p1 = new Person('trigkit4');console.log(p1.getName());//trigkit4 </script>示例:
/* 例1 */ // 定義一個構造函數 function Range(from, to){this.from = from; this.to = to; } // 所有Range類的實例化對象都會繼承構造函數Range的prototype屬性 Range.prototype = {toString: function(){return this.from + '....' + this.to; }, includes: function(x){return x >= this.from && x <= this.to;} };// 實例化一個對象 var r = new Range(1, 3); // 因為 r 繼承了Range.prototype, 所以可以直接調用里面的方法 r.toString()?由 例1 和 例2 可以總結出javascript中定義類的步驟:
- 第一步:先定義一個構造函數,并設置初始化新對象的實例屬性
- 第二步:給構造函數的prototype對象定義實例方法
- 第三步:給構造函數定義類字段和類屬性?
繼承
?新語法定義類,以及繼承類
Object.create() 法
為了解決 "構造函數法" 的缺點,更方便地生成對象,Javascript的國際標準?ECMAScript?第五版(目前通行的是第三版),提出了一個新的方法?Object.create()。
用這個方法,"類" 就是一個 對象,不是 函數。
var Cat = {name: "大毛",makeSound: function(){ alert("喵喵喵"); } };然后,直接用 Object.create() 生成實例,不需要用到 new。
var cat1 = Object.create(Cat); alert(cat1.name); // 大毛 cat1.makeSound(); // 喵喵喵目前,各大瀏覽器的最新版本(包括IE9)都部署了這個方法。如果遇到老式瀏覽器,可以用下面的代碼自行部署。
if (!Object.create) {Object.create = function (o) {function F() {}F.prototype = o;return new F();}; }這種方法比 "構造函數法" 簡單,但是不能實現私有屬性和私有方法,實例對象之間也不能共享數據,對"類"的模擬不夠全面。
極簡主義法 ( 推薦的方法?)
荷蘭程序員 Gabor de Mooij提出了一種比 Object.create() 更好的新方法,他稱這種方法為"極簡主義法"(minimalist approach)。也是推薦的方法。
這種方法不使用 this 和 prototype,代碼部署起來非常簡單,這大概也是它被叫做 "極簡主義法" 的原因。首先,它也是用一個對象模擬 "類"。在這個類里面,定義一個構造函數 createNew(),用來生成實例。
var Cat = {createNew: function(){// some code here} };然后,在 createNew() 里面,定義一個實例對象,把這個實例對象作為返回值。
var Cat = {createNew: function(){var cat = {};cat.name = "大毛";cat.makeSound = function(){ alert("喵喵喵"); };return cat;} };使用的時候,調用 createNew() 方法,就可以得到實例對象。
var cat1 = Cat.createNew(); cat1.makeSound(); // 喵喵喵這種方法的好處是,容易理解,結構清晰優雅,符合傳統的"面向對象編程"的構造,因此可以方便地部署下面的特性。
繼承
讓一個類繼承另一個類,實現起來很方便。只要在前者的 createNew() 方法中,調用后者的createNew() 方法即可。
先定義一個 Animal 類。
var Animal = {createNew: function(){var animal = {};animal.sleep = function(){ alert("睡懶覺"); };return animal;} };然后,在 Cat 的 createNew() 方法中,調用 Animal 的 createNew() 方法。
var Cat = {createNew: function(){var cat = Animal.createNew();cat.name = "大毛";cat.makeSound = function(){ alert("喵喵喵"); };return cat;} };這樣得到的 Cat 實例,就會同時繼承 Cat類 和 Animal類。
var cat1 = Cat.createNew(); cat1.sleep(); // 睡懶覺私有屬性 和 私有方法
在 createNew() 方法中,只要不是定義在 cat 對象上的方法和屬性,都是私有的。
var Cat = {createNew: function(){var cat = {};var sound = "喵喵喵";cat.makeSound = function(){ alert(sound); };return cat;} };上例的內部變量 sound,外部無法讀取,只有通過 cat 的公有方法 makeSound() 來讀取。
var cat1 = Cat.createNew(); alert(cat1.sound); // undefined數據共享
有時候,我們需要所有實例對象,能夠讀寫同一項內部數據。這個時候,只要把這個內部數據,封裝在類對象的里面、createNew()方法的外面即可。
var Cat = {sound : "喵喵喵",createNew: function(){var cat = {};cat.makeSound = function(){ alert(Cat.sound); };cat.changeSound = function(x){ Cat.sound = x; };return cat;} };然后,生成兩個實例對象:
var cat1 = Cat.createNew(); var cat2 = Cat.createNew(); cat1.makeSound(); // 喵喵喵這時,如果有一個實例對象,修改了共享的數據,另一個實例對象也會受到影響。
cat2.changeSound("啦啦啦"); cat1.makeSound(); // 啦啦啦使用關鍵字 class?
參考資料:
- MDN Classes:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Classes
- JavaScript 類完整指南:https://zhuanlan.zhihu.com/p/101988767
- JavaScript 類(class):https://www.runoob.com/.js/js-class-intro.html
- JavaScript 中的類:https://zhuanlan.zhihu.com/p/127798747
以前都是通過構造函數 function 和 原型prototype 來實現類的效果,在ES6中新增了 class 關鍵字用來定義類,使用 class 關鍵字定義類的寫法更加清晰,更像面向對象的語法。但是可以看作是語法糖,因為它還是構造函數和原型的概念。
類聲明
定義類有2中方式,類聲明 和 類表達式:
// 類聲明 class Student {} // 類表達式 const Student = class {}ECMAScript 6 中定義一個類示例代碼:
class Animal {constructor(name) {this.name = name;}sayHi() {console.log(`hello,${this.name}`);} } Animal.prototype.constructor === Animal; // true let dog = new Animal('dog'); dog.sayHi(); // hello, dogconstructor 方法用來創建和初始化對象,而且一個類中有且只能有一個consctuctor方法,默認為constructor(){}。dog 就是Animal實例化的對象。
ES5 中創建類
在前面說到,class 只是基于現有的 JavaSript 實現類的語法糖,那先讓我們簡單使用 ES5 中的方法來模擬類,并實例化。
function Animal(name) {this.name = name; } Animal.prototype.sayHi = function() {console.log(`hello,${this.name}`); } let dog = new Animal('dog'); dog.sayHi(); // hello, dog可以看出,class 中的 constructor() 方法就相當于 Animal() 構造函數,而在 class 中定義屬性就相當于直接在原型對象上定義屬性。我們不妨這樣試一試:
class Animal {constructor(name) {this.name = name;}sayHi() {console.log(`hello, ${this.name}`);} } let dog = new Animal('dog'); dog.sayHi(); // hello, dogdog.__proto__ === Animal.prototype; // truedog.__proto__.sayHi = function() {console.log(`hi, ${this.name}`); } dog.sayHi(); // hi, dog可以看出 class 還是依靠原型對象來實現類的。
為什么說它是語法糖
因為類實際上它是一個 function,區別在于構造函數是函數作用域,類是塊級作用域,類中的方法,都是定義在類的prototype上面,
class Student {take() {} } const a = new Student() console.log(typeof Student) // function console.log(a.take === Student.prototype.take) // true// 同等于 function Student() {} Student.prototype.take = function() {} const a = new Student() console.log(typeof Student) // function console.log(a.take === Student.prototype.take) // true類包含的屬性和方法
類可以包含?構造函數方法、實例方法、獲取函數、設置函數和靜態類方法,但這些都不是必需的。空的類定義照樣有效。
class Student {// 實例屬性 也可以放在這// b = 1// 靜態屬性static a = 1// 構造函數constructor() {// 實例屬性 - 也可以放在類的最頂層this.b = 1}// 獲取函數get myName() {}// 設置函數set myName() {}// 靜態方法 不會被實例繼承static show() {}// 方法take() {}// 私有方法_take() {} }實例屬性必須定義在類方法中:
class Animal {constructor(name) {this.name = name; // 實例屬性} }靜態屬性和原型屬性避必須在類的外面定義:
Animal.age = 18; // “靜態屬性” Animal.prototype.sex = 'male'; // 原型屬性實例屬性顧名思義,就是對象獨有的方法/屬性,靜態屬性就是位于Class本身的屬性,但是ES6中明確說明Class只有靜態方法,沒有靜態屬性,原型屬性也很容易理解也很容易看出,就是位于原型鏈上的屬性/方法。
類的構造函數
類的構造函數關鍵字是 constructor,它同等于原型中的 prototype.constructor。
如果沒有寫 constructor 函數,那么會默認有一個空的 constructor 函數。
當使用 new 操作符創建實例時,會調用 constructor 構造函數。
類的方法
class Student {// 方法take() {} }類的靜態方法
跟類的方法一樣,只不過前面加上static關鍵字。
靜態方法不會被實例繼承。
父類的靜態方法可以被子類繼承。
所有在類中定義的方法會被實例繼承,但是有時候我們并不想所有實例都能繼承某個方法,這時候,static關鍵字就能達到你的目的,在聲明方法前加上static關鍵字,這個方法就不會被實例繼承,而是直接通過類來調用,它被叫做靜態方法,如下:
class Animal {constructor(name) {this.name = name;}sayHi() {console.log(`hello, ${this.name}`);}static bark() {console.log('喵喵喵');} } let dog = new Animal('dog');dog.bark(); // TypeError Animal.bark(); // 喵喵喵靜態方法雖然不能被實例繼承,但是可以被子類繼承,但是子類的實例依舊沒有繼承它:
class Animal {static bark() {console.log('喵喵喵');} } class Dog extends Animal{} Dog.bark(); // 喵喵喵 let dog = new Dog(); dog.bark(); // TypeError類的私有方法
es6中沒有提供這個方法,但是通常都是在方法前面加上下劃線來表示。
class A {// 私有方法_show() {console.log('hi')} }取值函數(getter)和存值函數(setter)
在類中有 set 和 get 關鍵詞,可以對某個屬性設置存值和取值函數,攔截它的存取行為。
class A {constructor () {this.name = '小米'}get name () {return 'get'}set name (val) {console.log('set' + val)} } const b = new A() b.name // get b.name = 123 // set123Class 的 繼承
class 使用 extends 關鍵字來創建子類
class Animal {constructor(name) {this.name = name;}sayHi() {console.log(`hello, ${this.name}`);} }class Dog extends Animal {bark() {console.log(`喵喵喵`);} }let wangcai = new Dog('旺財'); wangcai.bark(); // 喵喵喵 wangcai.sayHi(); // hello, 旺財但如果在子類中定義了 constructor 方法,必須先調用 super() 才能使用 this,因為子類并沒有 this對象,而是繼承父類的 this 對象,所以 super 必須在使用 this 關鍵字之前使用:
class Animal {constructor(name) {this.name = name;}sayHi() {console.log(`hello, ${this.name}`);} }class Dog extends Animal {constructor(name, sound) {this.name = name;this.sound = sound;};bark() {console.log(this.sound);} }let wangcai = new Dog('旺財', '喵喵喵'); wangcai.bark(); // referenceErrorclass Dog extends Animal {constructor(name, sound) {super(name);this.sound = sound;};bark() {console.log(this.sound);} } let wangcai = new Dog('旺財', '喵喵喵'); wangcai.bark(); // 喵喵喵super 方法
注意如果子類如果沒寫constructor構造函數,則會默認有constructor構造函數和super方法,但是如果顯性的寫了constructor構造函數,那么必須在子類的構造函數中添加super方法,添加之后會調用父類的構造函數并得到父類的屬性和方法,如果沒有添加super方法則會報ReferenceError錯誤。
super 不僅可以調用父類的 constructor 函數,還可以調用父類上的方法:
class Animal {constructor(name) {this.name = name;}sayHi() {console.log(`hello, ${this.name}`);} }class Dog extends Animal {bark() {super.sayHi();} }let wangcai = new Dog('旺財'); wangcai.bark(); // hello, 旺財示例:
class A {constructor () {this.name = '小米'}show() {console.log('hi')} } class B extends A {constructor () {super() // 如果不寫super,則會報ReferenceError錯誤} } const c = new B()super 方法中也可以傳參
class A {constructor (name) {this.name = name}show() {console.log('hi')} } class B extends A {constructor () {super('小紅')} } const c = new B() c.name // 小紅方法中的 this 指向
類的方法中如果有 this,那么它指向的是類的實例。但是如果將它單獨拿出來使用那么會報錯。
class A {constructor () {this.name = '小米'}show () {console.log(this.name)} } const b = new A() b.show() // 小米 const { show } = b // Cannot read property 'name' of undefined解決辦法有2種:
- 在構造函數中綁定 this
- 使用箭頭函數
區分是否繼承了這個類
區分是否繼承了這個類使用Object.getPrototypeOf函數。
class A {constructor () {this.name = '小米'}show() {console.log('hi')} } class B extends A {constructor () {super()} } class C {} Object.getPrototypeOf(B) === A // true 是繼承的A類 Object.getPrototypeOf(B) === C // false 沒有繼承C類1.3?詳解 Javascript 中的 Object 對象
From:https://www.jb51.net/article/80177.htm
JS 中的 所有對象 都是 繼承自 Object對象
創建 對象
"對象" 是一組相似數據和功能的集合,用它可以來模擬現實世界中的任何東西。
在 Javascript 中,創建對象的方式通常有兩種方式:
- 構造函數。這種方式使用 new 關鍵字,接著跟上 Object 構造函數,再來給對象實例動態添加上不同的屬性。這種方式相對來說比較繁瑣,一般推薦使用對象字面量來創建對象。 var person = new Object(); person.name = "狼狼的藍胖子"; person.age = 25;
- 對象字面量。對象字面量很好理解,使用 key/value 的形式直接創建對象,通過花括號將對象的屬性包起來,對象的每個屬性之間用逗號隔開。注意:如果是最后一個屬性,后面就不要加逗號,因為在一些舊的瀏覽器下會報錯。 var person = {name: "狼狼的藍胖子",age: 25 };
注意:
obj_1 = {name: 'obj_1' }const {name} = obj_1; // 相當于 name = obj_1.name console.log(name) // obj console.log(name === obj_1.name) // true構造函數 和?對象字面量?這兩種方法有一個缺點就是:如果要創建多個對象,寫起來很繁瑣,所以后來就有了一種創建自定義構造函數的方法來創建對象,如下所示:
function Person(name, age) {this.name = name;this.age = age; } var person = new Person("Jack", 15);這種方式可以很方便的創建多個同樣的對象,也是目前比較常用的方法。
javascript 中 function(){}(), new function(), new Function(), Function
From:https://www.cnblogs.com/pizitai/p/6427433.html
javascript 中的類的構造:
javascript 中有對象的概念,卻沒有類的概念。
- 類?是一種抽象的概念,例如:動物、植物;
- 對象?則是指這種概念中的實體,比如 "中國人、美國人、楊樹、柳樹";
- 實例化 就是指以類為基礎構建一個實體。
類所擁有的特征,其實例化對象,也一定擁有這些特征,而且實例化后可能擁有更多特征。
javascript 在用到對象時,完全沒有類的概念,但是編程的世界里,無奇不有,可以通過 function 構造出一種假想的類,從而實現 javascript 中類的構造。比如,我們通過下面的方法來構造一個類:
//java class Book {private String name;private double price;public Book(name,price) {this.name=name;this.price=price;}public void setName(String name) { this.name = name;}public void setPrice(double price) {this.price = price;}public String getInfo() {...} } Book book1 = new Book('java',13.3);//javascript function Book(name,price) {this.name = name;this.price = price;this.setName = function(name) {this.name = name;};this.setPrice = function(price) {this.price = price};this.getInfo = function() {return this.name + ' ' + this.price;}; } var book1 = new Book('java',13.3);function(){}() 讓變量快速初始化結果
在《javascript立即執行某個函數:插件中function(){}()再思考》一文中,我詳細闡述了 function(){}() 的作用及理解思路。這里不再贅述,現在,我們面臨的新問題是,知道了它的作用,我們如何使用它?讓我們來看一段代碼:
var timestamp = function(){var timestamp = Date.parse(new Date());return timestamp/1000; }();當我們要使用一個變量時,我們希望這個變量在一個環節完成我們的賦值,使用上面的這種方法,可以減少代碼上下文執行邏輯,如果按照我們以前的方法,代碼可能會寫成:
var timestamp = Date.parse(new Data()); timestamp = timestamp/1000;看上去好像比上面的操作簡潔多了,只需要兩行代碼。但是我們仔細去觀察,就會發現第一段代碼其實本身僅是一個賦值操作,在 function 中完成的所有動作將會在function執行完后全部釋放,整個代碼看上去好像只執行了一條語句一樣。
而實際上更重要的意義在于它可以讓一個變量在初始化時,就具備了運算結果的效果。
使用 new function 初始化一個可操作對象
上面講了 javascript 中的類,而使用 new function 可以實例化這個類。但是我們實際上有的時候在為一個變量賦值的時候,希望直接將它初始化為一個可操作的對象,比如像這樣:
// 這里的數據庫操作是我虛擬出來的一種數據庫操作形式 var $db = new function(){var $db = db_connect('127.0.0.1','root','');$db.use('database');this.select = function(table,where) {var result = $db.query('select from ' + table + ' where ' + where);return $db.fetchAll(result);} };當我們要對數據庫 database 進行查詢時,只需要通過?var list = $db.select('table','1=1');
進行操作即可,數據庫的初始化結果已經在$db這個變量中了。
Function 是由 function 關鍵字定義的 函數對象的原型
在 javascript 中,多出了一個原型的概念。所謂原型,其實就是一個對象的本質( 可以理解成 基類?),但復雜就復雜在,原型本身也是對象,因此,任何一個對象又可以作為其他對象的原型。Function 就相當于一個系統原型,可以把它理解為一種 "基本對象類型",是 "對象"?這個概念范疇類的基本數據類型。
除了 Function 之外,其實還有很多類似的首字母大寫的對象原型,例如 Object, Array, Image 等等。有一種說法是:javascript 中所有的一切都是對象(除了基本數據類型,其他的一切全是對象),所有的對象都是 Object 衍生出來的。(按照這種說法,我們應該返回去再思考,上面說的類的假設是否成立。)
極其重要的 prototype 概念
prototype 的概念在 javascript 中極其重要,它是 javascript 中完成上面說的 "一切皆對象"?的關鍵。有了prototype,才有了原型,有了原型,才有了 javascript 五彩繽紛的世界(當然,也有人說是雜亂的)。我們可以這樣去理解 prototype:世界上本沒有 javascript,上帝說要有Object,于是有了 Object,可是要有 Function 怎么辦?只需要對 Object 進行擴展,可是如何擴展?只需要用prototype……當然,這是亂扯的,不過在 javascript 中,只要是 function,就一定會有一個prototype 屬性。實際上確實是這樣
Function.prototype.show = function() {...}在原型的基礎上通過prototype新增屬性或方法,則以該對象為原型的實例化對象中,必然存在新增的屬性或方法,而且它的內容是靜態不可重載的。原型之所以被稱為原型,可能正是因為這種不可重載的特質。
比如上面的這段代碼,會導致每一個實例化的function,都會具備一個show方法。而如果我們自己創建了一個類,則可以通過 prototype 將之轉化為原型:
function Cat() {...} Cat.prototype.run = function() {}; var cat1 = new Cat();這時,對于 cat1 而言,Cat 就是原型,而該原型擁有一個 run 的原始方法,所以無論實例化多少個 Cat,每一個實例化對象都有 run 方法,而且該方法是不能被重載的,通過 cat1.run = function(){} 是無效的。
為了和其他語言的類的定義方法統一,我們可以將這種原型屬性在定義類的時候,寫在類的構造里面:
function Cat() {....Cat.prototype.run = function() {}; }new Function() 是函數原型的一個實例化
在理解了 Function 原型的概念之后,再來看 new Function()就顯得很容易了。首先來看下我們是怎么使用這種奇特的寫法的:
var message = new Function('msg','alert(msg)'); // 等價于: function message(msg) {alert(msg); }new Function(參數1,參數2,…,參數n,函數體),它的本意其實是通過實例化一個Function原型,得到一個數據類型為function的對象,也就是一個函數,而該變量就是函數名。
this 在這類 function 中的指向
this 在 javascript 中真的是無法讓我們捉摸透徹。但是有一個小竅門,就是:一般情況下,this 指向的是當前實例化對象,如果沒有找到該對象,則是指向 window。從使用上來講,我們應該排除new Function 的討論,因為它和我們常用的函數聲明是一致的。
- 普通的函數中this的指向:函數聲明的時候,如果使用了this,那么就要看是把該函數當做一個對象加以返回,還是以僅執行函數體。普通函數執行時,我們完全沒有引入對象、類這些概念,因此,this 指向 window。通過代碼來看下: var msg;
function message(msg) {this.msg = msg;
}
message('ok');
alert(msg);
首先是聲明一個函數message,在函數中this.msg實際上就是window.msg,也實際上就是代碼開頭的msg。因此,當執行完message(‘ok’)的時候,開頭的全局變量msg也被賦值為ok。
-
通過 function 構造類時 this 的指向:如果function被構造為一個類,那么必然存在該類被實例化的一個過程,如果沒有實例化,那么該類實際上并沒有在程序中被使用。而一旦實例化,那么this將指向實例化的對象。
var age = 3; var cat1 = new function() {this.name = 'Tom';this.age = 2;this.weight = function(age) {var age = age * 2;var _age = this.age * 2;return 'weight by age:' + age + '; weight by this.age:' + _age;}(this.age);this.eye = new function() {this.size = '1.5cm';this.color = 'red';};this.catching = function(mouse) {return this.name + ' is catching ' + mouse;}; }; alert(cat1.weight); alert(cat1.eye.color); alert(cat1.catching('Jerry'));上面代碼中標記了4處紅色的this的使用。根據我們的原則,this指向實例化對象,我們來對每一個 this 進行分解。
首先是 cat1.weight,我使用了 function(){}(),直接利用貓咪的年齡進行計算得出體重返回給weight屬性。
第一個 this.age 出現在function(){}(this.age),這個this.age實際上是一個傳值過程,如果你對我之前分析function(){}()比較了解的話,應該知道,this.age實際上是和前面this.age = 2指同一個,這里的this.age的this,首先要去找它所在的function,然后看這個function是否被實例化,最后確認,確實被實例化為cat1,因此this=cat1。
第二個 this.age 出現在function(){this.age}()。同樣,你先需要對function(){}()再次深入了解,實際上,function(){}()就是執行一個函數而已,我們前面提到了,普通函數執行中this=window,所以,這里的this.age實際上是var age = 3。
第三個 this.color 出現在new function(){this.color},這里就比較好玩,由于有一個new,實際上也被實例化了,只不過是對匿名類的實例化,沒有類名,而且實例化僅可能出現這一次。因此,this.color的this要去找new function的主人,也就是this.eye,而this.eye的this=cat1,所以cat1.eye.color=’red’。
第四個 this.name 出現在function(){this.name},它出現在cacthing方法中,它既不是普通的函數執行,也不是實例化為對象,而是正常的類中的方法的聲明,因此this指向要去找它所在的function被實例化的對象,也就是cat1。
對象實例 的 屬性和方法
不管通過哪種方式創建了對象實例后,該 實例 都會擁有 下面的屬性和方法,下面將會逐個說明。
constructor 屬性
"constructor 屬性" 是用來保存當前對象的構造函數的,前面的例子中,constructor 保存的就是 Object 方法。
var obj1 = new Object(); obj1.id = "obj1"; var obj2 = {"id": "obj2" };console.log(obj1.constructor); //function Object(){} console.log(obj2.constructor); //function Object(){} console.log(obj1.constructor === obj2.constructor) // truehasOwnProperty(propertyName) 方法
hasOwnProperty 方法接收一個字符串參數,該參數表示屬性名稱,用來判斷該屬性是否在當前對象實例中,而不是在對象的原型鏈中。我們來看看下面這個例子:
var arr = []; console.log(arr.hasOwnProperty("length")); //true console.log(arr.hasOwnProperty("hasOwnProperty")); //false在這個例子中,首先定義了一個數組對象的 實例arr,我們知道,數組對象實際是通過 原型鏈 ( 下面會介紹?) 繼承了Object 對象,然后擁有自己的一些屬性,可以通過?hasOwnProperty?方法 判斷 length 是 arr 自己的屬性,然而?hasOwnProperty方法?是在 原型鏈 上的屬性。
hasOwnProperty 方法 可以和 for..in 結合起來獲取 對象自己的 key?
isPrototypeOf(Object) 方法
isPrototype 方法接收一個對象,用來判斷當前對象是否在傳入的參數對象的原型鏈上,說起來有點抽象,我們來看看代碼。
function MyObject() {} var obj = new MyObject(); console.log(Object.prototype.isPrototypeOf(obj));上面代碼中 MyObject 是繼承自 Object 對象的,而在JS中,繼承是通過 prototype 來實現的,所以 Object 的 prototype 必定在 MyObject 對象實例的原型鏈上。
propertyIsEnumerable(prototypeName) 方法
prototypeIsEnumerable 用來判斷給定的屬性是否可以被 for..in 語句給枚舉出來。看下面代碼:
var obj = {name: "objName" } for (var i in obj) {console.log(i); }執行這段代碼輸出字符串 “name”,這就說明通過 for…in 語句可以得到 obj 的 name 這個屬性,但是我們知道,obj 的屬性還有很多,比如 constructor,比如hasOwnPrototype 等等,但是它們沒有被輸出,說明這些屬性不能被 for…in 給枚舉出來,可以通過 propertyIsEnumerable 方法來得到。
console.log(obj.propertyIsEnumerable("constructor")); // false判斷 “constructor” 是否可以被枚舉,輸出 false 說明無法被枚舉出來。
toLocaleString() 方法
toLocalString 方法返回對象的字符串表示,和代碼的執行環境有關。
var obj = {}; console.log(obj.toLocaleString()); //[object Object] var date = new Date(); console.log(date.toLocaleString()); //2021/4/15 下午1:30:15toString() 方法
toString 用來返回對象的字符串表示。
var obj = {}; console.log(obj.toString()); //[object Object]var date = new Date(); console.log(date.toString()); //Sun Feb 28 2021 13:40:36 GMT+0800 (中國標準時間)valueOf() 方法
valueOf 方法返回對象的原始值,可能是字符串、數值 或 bool值 等,看具體的對象。
var obj = {name: "obj" }; console.log(obj.valueOf()); //Object {name: "obj"}var arr = [1]; console.log(arr.valueOf()); //[1]var date = new Date(); console.log(date.valueOf()); //1456638436303如代碼所示,三個不同的對象實例調用 valueOf 返回不同的數據。
屬性 的 類型
在 Javascript 中,屬性有兩種類型,分別是
- 數據?屬性
- 訪問器?屬性
我們來看看這兩種屬性具體是什么東西。
數據?屬性
數據屬性:可以理解為我們平時定義對象時賦予的屬性,它可以進行讀和寫。但是,ES5中定義了一些 特性,這些特性是用來描述屬性的各種特征。即 特性的作用 是描述 屬性的各種特征。特性是內部值,不能直接訪問到。特性通過用兩對方括號表示,比如[[Enumerable]]。
屬性的特性會有一些默認值,要修改特性的默認值,必須使用 ES5 定義的新方法 Object.defineProperty 方法來修改
數據屬性有4個描述其特征的特性,下面將依次說明每一個特性:
(1)[[Configurable]]:該特性表示是否可以通過 delete 操作符來刪除屬性,默認值是 true。
var obj = {}; obj.name = "myname";delete obj.name; console.log(obj.name);//undefined這段代碼很明顯,通過 delete 刪除了 obj 的 name 屬性后,我們再訪問 name 屬性就訪問不到了。
我們通過 Object.defineProperty 方法來修改 [[Configurable]] 特性。
var obj = {}; obj.name = "myname"; Object.defineProperty(obj, "name", {configurable: false }) delete obj.name; console.log(obj.name); //myname通過將 configurable 特性設置成 false 之后,delete 就無法刪除 name 屬性了,如果在嚴格模式下,使用 delete 去刪除就會報錯。
(2)[[Enumerable]]:表示是否能夠通過 for…in 語句來枚舉出屬性,默認是 true
我們來看看前面的例子:
var obj = {name: "objName" } for (var i in obj) {console.log(i);//name }這段代碼只輸出了 name 屬性,我們來將 constructor 屬性的 [[Enumerable]] 設置為 true 試試。
var obj = {name: "objName" } Object.defineProperty(obj, "constructor", {enumerable: true })for (var i in obj) {console.log(i);//name,constructor } console.log(obj.propertyIsEnumerable("constructor"));//true這段代碼中,for…in 循環得到了 name 和 constructor 兩個屬性,而通過 propertyIsEnumerable 方法來判斷 constructor 也返回了true。
(3)[[Writable]]:表示屬性值是否可以修改,默認為true。如果 [[Writable]] 被設置成 false,嘗試修改時將沒有效果,在嚴格模式下會報錯
(4)[[Value]]:表示屬性的值,默認為 undefined
我們通過一個簡單的例子來看看這兩個特性:
var obj = {name: "name" }; console.log(obj.name);//name Object.defineProperty(obj, "name", {value: "newValue",writable: false }) console.log(obj.name);//newValueobj.name = "oldValue"; console.log(obj.name);//newValue我們首先定義了 obj 對象的 name 屬性值為 “name”,然后通過 defineProperty 方法來修改值,并且將其設置為不可修改的。接著我們再修改 name 屬性的值,可以發現修改無效。
如果我們通過 defineProperty 來修改 name 屬性的值,是否可以修改呢?答案是可以的:
Object.defineProperty(obj, "name", {value: "oldValue" }) console.log(obj.name); //oldValue訪問器?屬性
訪問器屬性有點類似于 C# 中的屬性,和數據屬性的區別在于,它沒有數據屬性的 [[Writable]] 和 [[Value]] 兩個特性,而是擁有一對 getter 和 setter 函數。
- [[Get]]:讀取屬性時調用的函數,默認是 undefined
- [[Set]]:設置屬性時調用的函數,默認是 undefined
getter 和 setter 是一個很有用的東西,假設有兩個屬性,其中第二個屬性值會隨著第一個屬性值的變化而變化。這種場景在我們平時的編碼中起始是非常常見的。在之前的做法中,我們往往要去手動修改第二個屬性的值,那現在我們就可以通過 get 和 set 函數來解決這個問題。看下面這個例子:
var person = {age: 10 }Object.defineProperty(person, "type", {get: function () {if (person.age > 17) {return "成人";}return "小孩";} })console.log(person.type);//小孩person.age = 18; console.log(person.type);//成人通過修改 age 的值,type 的值也會相應的修改,這樣我們就不用再手動的去修改 type 的值了。
下面這種方式也是可以實現同樣的效果:
var person = {_age: 10,type: "小孩" }Object.defineProperty(person, "age", {get: function () {return this._age;},set: function (newValue) {this._age = newValue;this.type = newValue > 17 ? "成人" : "小孩";} }) console.log(person.type);person.age = 18; console.log(person.type);訪問器?屬性 的 注意點
關于訪問器屬性,有幾點要注意:
- 1、嚴格模式下,必須同時設置 get 和 set
- 2、非嚴格模式下,可以只設置其中一個,如果只設置 get,則屬性是只讀的,如果只設置 set,屬性則無法讀取
- 3、Object.defineProperty 是 ES5 中的新方法,IE9(IE8部分實現,只有dom對象才支持)以下瀏覽器不支持,一些舊的瀏覽器可以通過非標準方法defineGetter()和defineSetter()來設置,這里就不說明了,有興趣的同學可以查找相關資料。
特性?操作的相關方法
ES5 提供了一些讀取或操作屬性特性的方法,前面用到的 Object.defineProperty 就是其中之一。我總結了一些比較常用的方法如下:
(1)Object.defineProperty
定義一個對象的屬性,這個方法前面我們已經用到多次,簡單說說其用法。
Object.defineProperty(obj, propName, descriptor);defineProperty 有點類似于定于在 Object 上的靜態方法,通過 Object 直接調用,它接收3個參數:
- obj:需要定義屬性的對象
- propNane:需要被定義的屬性名稱
- defineProperty:屬性描述符,包含一些屬性的特性定義
例子如下:
var obj = {}; Object.defineProperty(obj, "name", {value: "name",configurable: true,writable: true,enumerable: true });(2)Object.defineProperties
和 defineProperty 類似,是用來定義對象屬性的,不同的是它可以用來同時定義多個屬性,我們通過命名也可以看出來,用法如下:
var obj = {}; Object.defineProperty(obj, {"name": {value: "name",configurable: true,writable: true,enumerable: true},"age": {value: 20} });(3)Object.getOwnPropertyDescriptor
ES5 中還提供了一個讀取特性值的方法,該方法接收對象及其屬性名作為兩個參數,返回一個對象,根據屬性類型的不同,返回對象會包含不同的值。
var person = {_age: 10,type: "小孩" } Object.defineProperty(person, "age", {get: function () {return this._age;},set: function (newValue) {this._age = newValue;this.type = newValue > 17 ? "成人" : "小孩";} })console.log(Object.getOwnPropertyDescriptor(person, "type"));//Object {value: "成人", writable: true, enumerable: true, configurable: true} console.log(Object.getOwnPropertyDescriptor(person, "age")); //Object {enumerable: false, configurable: false, get: function(),set: function ()}Object 的 方法
在 ES5 中,Object 對象上新增了一批方法,這些方法可以直接通過 Object 進行訪問,前面用到的 defineProperty 就是新增的方法之一。除此之外還有很多方法,我將其總結歸納如下:
對象創建型方法?Object.create(proto, [propertiesObject])
在前面我們提到,創建一個對象有兩種方法:構造函數 和 對象字面量。
這兩種方法有一個缺點就是:如果要創建多個對象,寫起來很繁瑣,所以后來就有了一種創建自定義構造函數的方法來創建對象,如下所示:
function Person(name, age) {this.name = name;this.age = age; } var person = new Person("Jack", 15);這種方式可以很方便的創建多個同樣的對象,也是目前比較常用的方法。
ES5 提供的 Object.create 方法也是一個創建對象的方法,這個方法允許為創建的對象選擇原型對象,不需要定義一個構造函數。用法如下:
var obj = Object.create(Object.prototype, {name: {value: "Jack"} }) console.log(obj.name); //Jack這個方法接收的第一個參數作為被創建對象的原型,第二個參數是對象的屬性。
注意:在這個例子中,name屬性是無法被修改的,因為它沒有設置writable特性,默認則為false。
個人看法:Object.create這種創建對象的方式略顯繁瑣,除非是需要修改屬性的特性,否則不建議使用這種方式創建對象。
獲取 屬性?
Object.keys? 獲取自身屬性,但不包括原型中的屬性
Object.keys 是 es5 中新增的方法,用來獲取對象自身所有的可枚舉的屬性名,但不包括原型中的屬性,然后返回一個由屬性名組成的數組。
注意它同 for..in 一樣不能保證屬性按對象原來的順序輸出。
function Parent() {this.lastName = "Black" }function Child(firstName) {this.firstName = firstName; }Child.prototype = new Parent();var son = new Child("Jack"); console.log(Object.keys(son)); //["firstName"]代碼中返回了 firstName,并沒有返回從 prototype 繼承而來的 lastName 和 不可枚舉的相關屬性。
// simple array var arr = ['a', 'b', 'c']; console.log(Object.keys(arr)); // console: ['0', '1', '2']// array like object var obj = {0: 'a', 1: 'b', 2: 'c'}; console.log(Object.keys(obj)); // console: ['0', '1', '2']// array like object with random key ordering var anObj = {100: 'a', 2: 'b', 7: 'c'}; console.log(Object.keys(anObj)); // console: ['2', '7', '100']// getFoo is a property which isn't enumerable var myObj = Object.create({}, {getFoo: {value: function () {return this.foo;}} }); myObj.foo = 1; console.log(Object.keys(myObj)); // console: ['foo']在一些舊的瀏覽器中,我們可以使用 hasOwnProperty 和 for…in 來達到類似的效果。
function Parent() {this.lastName = "Black" }function Child(firstName) {this.firstName = firstName; }Child.prototype = new Parent();var son = new Child("Jack");// 注意:這里如果不支持 Object.keys,則把 Object.keys 定義為一個函數 Object.keys = Object.keys ||function (obj) {var keys = [];for (var key in obj) {if (obj.hasOwnProperty(key)) {keys.push(key);}}return keys;};console.log(Object.keys(son));getOwnPropertyNames? 獲取自身 可枚舉 和 不可枚舉 的 屬性
Object.getOwnPropertyNames 也是 es5 中新增的方法,返回對象自身的所有屬性的屬性名(包括可枚舉和不可枚舉的所有屬性)組成的數組,但不會獲取原型鏈上的屬性。
function Parent() {this.lastName = "Black" }function Child(firstName) {this.firstName = firstName; }Child.prototype = new Parent();var son = new Child("Jack"); Object.defineProperty(son, "age", {enumerable: false }) console.log(Object.keys(son));//["firstName"] console.log(Object.getOwnPropertyNames(son));//["firstName", "age"]我們定義給 son 對象定義了一個不可枚舉的屬性 age,然后通過 keys 和 getOwnPropertyNames 兩個方法來獲取屬性列表,能明顯看出了兩者區別。
屬性?特性型?方法
這個主要是前面提到的三個方法:
- defineProperty,
- defineProperties
- getOwnPropertyDescriptor?
對象 限制型 方法
ES5 中提供了一系列限制對象被修改的方法,用來防止被某些對象被無意間修改導致的錯誤。每種限制類型包含一個判斷方法和一個設置方法。
阻止對象擴展
Object.preventExtensions()? 用來限制對象的擴展,設置之后,對象將無法添加新屬性,用法如下:
Object.preventExtensions(obj);該方法接收一個要被設置成無法擴展的對象作為參數,需要注意兩點:
- 1、對象的屬性不可用擴展,但是已存在的屬性可以被刪除
- 2、無法添加新屬性指的是無法在自身上添加屬性,如果是在對象的原型上,還是可以添加屬性的。
Object.isExtensible 方法用來判斷一個對象是否可擴展,默認情況是 true
將對象密封
Object.seal 可以密封一個對象并返回被密封的對象。
密封對象無法添加或刪除已有屬性,也無法修改屬性的 enumerable,writable,configurable,但是可以修改屬性值。
function Person(name) {this.name = name; }var person = new Person("Jack"); Object.seal(person); delete person.name; console.log(person.name); //Jack將對象密封后,使用 delete 刪除對象屬性,還是可以訪問得到屬性。
通過 Object.isSealed 可以用來判斷一個對象是否被密封了。
凍結對象
Object.freeze 方法用來凍結一個對象,被凍結的對象將無法添加,修改,刪除屬性值,也無法修改屬性的特性值,即這個對象無法被修改。
function Person(name) {this.name = name; }var person = new Person("Jack"); Object.freeze(person);delete person.name; console.log(person.name);//JackPerson.prototype.age = 15; console.log(person.age);//15分析上面的代碼我們可以發現,被凍結的對象無法刪除自身的屬性,但是通過其原型對象還是可以新增屬性的。
通過Object.isFrozen可以用來判斷一個對象是否被凍結了。
可以發現:這三個限制對象的方法的限制程度是依次上升的。
Javascript 的 Object 常用方法總結
參考:Javascript Object常用方法總結 - fozero - 博客園
Object.keys(ojb)?方法
Object.keys(obj) 方法是 JavaScript 中用于遍歷對象屬性的一個方法 。它傳入的參數是一個對象,返回的是一個數組,數組中包含的是該對象所有的屬性名。如:
var cat = {name: 'mini',age: 2,color: 'yellow',desc:"cute" } console.log(Object.keys(cat)); // ["name", "age", "color", "desc"]這里有一道關于 Object.keys 的題目:輸出對象中值大于 2的 key 的數組
var data = {a: 1, b: 2, c: 3, d: 4}; Object.keys(data).filter(function (x) {return 1; })/* 期待輸出:["c","d"] 請問1處填什么? 正確答案:1 :data[x]>2 */Object.keys 是 es5 中新增的方法,用來獲取對象自身所有的可枚舉的屬性名,但不包括原型中的屬性,然后返回一個由屬性名組成的數組。注意它同 for..in 一樣不能保證屬性按對象原來的順序輸出。
Object.getOwnPropertyNames 也是 es5 中新增的方法,返回對象的所有自身屬性的屬性名(包括不可枚舉的屬性)組成的數組,但不會獲取原型鏈上的屬性。
Array.filter(function)
對數組進行過濾返回符合條件的數組。
Object.values() 方法
Object.values 方法返回一個數組,成員是參數對象自身的(不含繼承的)所有可遍歷( enumerable )屬性的鍵值。
var obj = {foo: "bar", baz: 42}; Object.values(obj) // ["bar", 42]返回數組的成員順序,屬性名為數值的屬性,是按照數值大小,從小到大遍歷的,因此返回的順序是b、c、a。Object.values 只返回對象自身的可遍歷屬性。
var obj = {100: 'a', 2: 'b', 7: 'c'}; Object.values(obj) // ["b", "c", "a"]如果 Object.values 方法的參數是一個字符串,會返回各個字符組成的一個數組。
Object.values('foo') // ['f', 'o', 'o']上面代碼中,字符串會先轉成一個類似數組的對象。字符串的每個字符,就是該對象的一個屬性。因此,Object.values返回每個屬性的鍵值,就是各個字符組成的一個數組。
如果參數不是對象,Object.values 會先將其轉為對象。由于數值和布爾值的包裝對象,都不會為實例添加非繼承的屬性。所以,Object.values 會返回空數組。
Object.create()
Object.create() 方法創建一個新對象,使用現有的對象來提供新創建的對象的 __proto__ 。
語法:Object.create(proto, [propertiesObject])
參數
? ? ? ? :proto? 新創建對象的原型對象。
? ? ? ? :propertiesObject? 可選。如果沒有指定為 undefined,則是要添加到新創建對象的可枚舉屬性(即其自身定義的屬性,而不是其原型鏈上的枚舉屬性)對象的屬性描述符以及相應的屬性名稱。這些屬性對應Object.defineProperties()的第二個參數。
返回值:一個新對象,帶著指定的原型對象和屬性。如:
var parent = {x: 1,y: 1 } var child = Object.create(parent, {z: { // z會成為創建對象的屬性writable: true,configurable: true,value: "newAdd"} }); console.log(child)//{z: "newAdd"}z: "newAdd"__proto__: x: 1y: 1__proto__: ObjectObject.create()??創建繼承
function A() {this.a = 1;this.b = 2; }A.prototype.drive = function () {console.log('drivvvvvvvvvv'); }//方式1 function B() { }B.prototype = Object.create(new A()); //這里采用了new 一個實例 //方式2 function C() {A.call(this); }C.prototype = Object.create(A.prototype) //這里使用的是父類的原型以上兩種方式有什么區別?
1 的缺點:
? ? ? ? 執行了 new,相當于運行了一遍 A ,如果在 A 里做了一些其它事情(如改變全局變量)就會有副作用。
? ? ? ? 用 A 創建的對象做原型,里面可能會有一些冗余的屬性。
2 模擬了 new 的執行過程
Object.hasOwnProperty() 方法
判斷對象自身屬性中是否具有指定的屬性。這個方法是不包括對象原型鏈上的方法的。
判斷某個對象是否擁有某個屬性,判斷的方法有很多,常用的方法就是 object.hasOwnProperty('×××')
var obj = {name: 'fei' } console.log(obj.hasOwnProperty('name')) // true console.log(obj.hasOwnProperty('toString')) // false以上,obj 對象存在的 name 屬性的時候,調用這個方法才是返回 true,我們知道其實每個對象實例的原型鏈上存在 toString 方法,在這里打印 false,說明這個方法只是表明實例對象的屬性,不包括原型鏈上的屬性。
Object.getOwnPropertyNames() 方法
Object.getOwnPropertyNames() 方法返回對象的所有自身屬性的屬性名(包括不可枚舉的屬性)組成的數組,但不會獲取原型鏈上的屬性。
function A(a, aa) {this.a = a;this.aa = aa;this.getA = function () {return this.a;} }// 原型方法 A.prototype.aaa = function () { };var B = new A('b', 'bb'); B.myMethodA = function () { }; // 不可枚舉方法 Object.defineProperty(B, 'myMethodB', {enumerable: false,value: function () {} });Object.getOwnPropertyNames(B); // ["a", "aa", "getA", "myMethodA", "myMethodB"]Object.getOwnPropertyNames 和 Object.keys?區別
Object.getOwnPropertyNames 和 Object.keys 的區別,
- Object.keys 只適用于可枚舉的屬性,
- Object.getOwnPropertyNames 返回對象自動的全部屬性名稱。
es6 中?JavaScript 對象方法 Object.assign()
Object.assign 方法用于對象的合并,將源對象( source )的所有可枚舉屬性,復制到目標對象( target )。
var target = {a: 1}; var source1 = {b: 2}; var source2 = {c: 3}; Object.assign(target, source1, source2); target // {a:1, b:2, c:3}1、如果目標對象與源對象有同名屬性,或多個源對象有同名屬性,則后面的屬性會覆蓋前面的屬性。
2、如果只有一個參數,Object.assign 會直接返回該參數。
3、如果該參數不是對象,則會先轉成對象,然后返回。
4、由于undefined和null無法轉成對象,所以如果它們作為參數,就會報錯。
5、Object.assign方法實行的是淺拷貝,而不是深拷貝。也就是說,如果源對象某個屬性的值是對象,那么目標對象拷貝得到的是這個對象的引用。
上面代碼中,源對象obj1的a屬性的值是一個對象,Object.assign拷貝得到的是這個對象的引用。這個對象的任何變化,都會反映到目標對象上面。
常見用途
( 1 )為對象添加屬性
class Point {constructor(x, y) {Object.assign(this, {x, y});} }上面方法通過Object.assign方法,將x屬性和y屬性添加到Point類的對象實例。
( 2 )為對象添加方法
Object.assign(SomeClass.prototype, {someMethod(arg1, arg2) {//···},anotherMethod() {//···} }); // 等同于下面的寫法 SomeClass.prototype.someMethod = function (arg1, arg2) {//··· }; SomeClass.prototype.anotherMethod = function () {//··· };上面代碼使用了對象屬性的簡潔表示法,直接將兩個函數放在大括號中,再使用 assign 方法添加到 SomeClass.prototype 之中。
( 3 )克隆對象
function clone(origin) { return Object.assign({}, origin); }上面代碼將原始對象拷貝到一個空對象,就得到了原始對象的克隆。不過,采用這種方法克隆,只能克隆原始對象自身的值,不能克隆它繼承的值。
( 4 )合并多個對象
將多個對象合并到某個對象。
const merge =(target, ...sources) => Object.assign(target, ...sources);如果希望合并后返回一個新對象,可以改寫上面函數,對一個空對象合并。
const merge =(...sources) => Object.assign({}, ...sources);( 5 )為屬性?指定?默認值
const DEFAULTS = {logLevel: 0,outputFormat: 'html' };function processContent(options) {let options = Object.assign({}, DEFAULTS, options); }DEFAULTS 對象是默認值,options 對象是用戶提供的參數。Object.assign 方法將 DEFAULTS 和 options 合并成一個新對象,如果兩者有同名屬性,則 option 的屬性值會覆蓋 DEFAULTS 的屬性值。注意,由于存在深拷貝的問題,DEFAULTS 對象 和 options對象的所有屬性的值,都只能是簡單類型,而不能指向另一個對象。否則,將導致DEFAULTS 對象的該屬性不起作用。
參考 es6 javascript 對象方法Object.assign():es6 javascript對象方法Object.assign()_現在學習也不晚-CSDN博客_object.assign
Object.defineProperty() 方法理解
Object.defineProperty 可以用來定義新屬性或修改原有的屬性
使用構造函數定義對象和屬性
var obj = new Object; //obj = {} obj.name = "張三"; //添加描述 obj.say = function(){}; //添加行為語法:Object.defineProperty(obj, prop, descriptor)
參數說明
- obj:必需。目標對象
- prop:必需。需定義或修改的屬性的名字
- descriptor:必需。目標屬性所擁有的特性
給對象的屬性添加特性描述,目前提供兩種形式:
- 數據描述
- 存取器描述
數據?描述
修改或定義對象的某個屬性的時候,給這個屬性添加一些特性, 數據描述中的屬性都是可選的
var obj = {test:"hello" }//對象已有的屬性添加特性描述 /* Object.defineProperty(obj,"test",{configurable:true | false,enumerable:true | false,value:任意類型的值,writable:true | false }); *///對象新添加的屬性的特性描述 /* Object.defineProperty(obj,"newKey",{configurable:true | false,enumerable:true | false,value:任意類型的值,writable:true | false }); */- value: 設置屬性的值
- writable: 值是否可以重寫。true | false
- enumerable: 目標屬性是否可以被枚舉。true | false
- configurable: 目標屬性是否可以被刪除或是否可以再次修改特性 true | false
存取器?描述
使用存取器描述屬性的特性的時候,允許設置以下特性屬性, 當使用了getter 或 setter 方法,不允許使用 writable 和 value 這兩個屬性
var obj = {}; Object.defineProperty(obj, "newKey", {get: function () {} | undefined,set: function (value) {} | undefined,configurable: true | false,enumerable: true | false });getter / setter
getter 是一種獲得屬性值的方法。setter是一種設置屬性值的方法。使用 get/set 屬性來定義對應的方法
var obj = {}; var initValue = 'hello'; Object.defineProperty(obj, "newKey", {get: function () {//當獲取值的時候觸發的函數return initValue;},set: function (value) {//當設置值的時候觸發的函數,設置的新值通過參數value拿到initValue = value;} });//獲取值console.log(obj.newKey); //hello //設置值 obj.newKey = 'change value'; console.log(obj.newKey); //change value原型鏈
- 1.?每個函數(?函數也是對象 )都有 prototype 和 __proto__
- 2.?每一個對象?/?構造函數的實例(這個也是對象)都有 __proto__
- 3.?實例 的? __proto__? 指向 構造函數 的 prototype。這個稱為 構造函數的原型對象
- 4. JavaScript?引擎會沿著 __proto__? --->??ptototype 的順序一直往上方查找,找到?window.Object.prototype 為止,Object 為原生底層對象,到這里就停止了查找。
? ? ? 如果沒有找到,就會報錯或者返回 undefined - 5.?而構造函數的 __proto__? 指向 Function.prototype? ? () { [native code] } 【構造器函數,但這個叫法? 并不準確,它目前沒有一個合適的中文名】
- 6.?__proto__ 是瀏覽器廠商實現的,W3C規范中并沒有這個東西
- 1. JS 代碼還沒運行的時候,JS 環境里已經有一個 window 對象了。
- 2. window 對象有一個 Object 屬性,window.Object 是一個 函數對象
- 3. window.Object 這個函數對象有一個重要屬性是 prototype
- 4. window.Object.prototype 里面有一堆屬性
- 5. 所有的實例函數的 __proto__ 都會指向 構造函數的 prototype
- 6. constructor 是 反向的prototype
上面定義了一個 空對象obj,當調用 obj 的 toString() 理論上會報 undefined 錯誤,但是實際上不會報錯
運算符 -- JavaScript 標準參考教程(alpha):運算符 -- JavaScript 標準參考教程(alpha)
JavaScript 中 === 和 == 的區別 ( 參考:Javascript 中 == 和 === 區別是什么? - 知乎 ):
- "==="? 叫做?恒等?運算符。( 即 類型和值 都相等?)(?其實叫?全等運算符 更合適。即內存中每個bit位都一樣?)
? ? 嚴格相等運算符 === 的運算規則如下:
? ? (1) 不同類型值。如果兩個值的類型不同,直接返回false。
? ? (2) 同一類的原始類型值。同一類型的原始類型的值(數值、字符串、布爾值)比較時,值相同就返回true,值不同就返回false。
? ? (3) 同一類的復合類型值。兩個復合類型(對象、數組、函數)的數據比較時,不是比較它們的值是否相等,而是比較它們是否指向同一個對象。
? ? (4) undefined 和 null。undefined 和 null 與自身嚴格相等。
? ? ????null === null ?//true
? ? ????undefined === undefined ?//true - "=="? 叫做 相等 運算符。( 只 判斷數據的值 )
? ? 相等運算符 == 在比較相同類型的數據時,與嚴格相等運算符完全一樣。
? ? 在比較不同類型的數據時,相等運算符會先將數據進行類型轉換,然后再用嚴格相等運算符比較。
? ? 類型轉換規則如下:
? ? (1) 原始類型的值。原始類型的數據會轉換成數值類型再進行比較。字符串和布爾值都會轉換成數值。
? ? (2) 對象與原始類型值比較。對象(這里指廣義的對象,包括數值和函數)與原始類型的值比較時,對象轉化成原始類型的值,再進行比較。
? ? (3) undefined和null。undefined和null與其他類型的值比較時,結果都為false,它們互相比較時結果為true。
? ? (4) 相等運算符的缺點。相等運算符會隱藏類型的轉換,然后帶來一些違反直覺的結果。 - 因為"=="不嚴謹,可能會帶來一些違反直覺的后果,建議盡量不要使用 相等運算符 == ,而是使用 嚴格相等運算符 ===?
示例:
'' == '0' ?// false
0 == '' ???// true。'' 會先轉換成數值0,再和0比較,所以是 true
0 === 0 ???// true
false == 'false' ?// false
false == 0 ???????// true
false == undefined ?// false
false == null ??????// false
null == undefined ??// true
'\t\r\n' == 0
var a = undefined; if(!a){console.log('a'); // 1 }if(a == null){console.log('a'); // 1 }if( a === null ){console.log('a'); // 無輸出 }js中 !== 和 != 的區別:
- !=? ? 會轉換成相同類型 進行比較。即 在表達式兩邊的數據類型不一致時,會隱式轉換為相同數據類型,然后對值進行比較;
- !==? ?不會進行類型轉換,在比較時除了對值進行比較以外,還比較兩邊的數據類型, 它是 恒等運算符 === 的非形式。
解釋:上面是定義?obj 變量指向一個空對象,當對象定義的一瞬間,就會瞬間產生一個?__proto__ 的屬性?,這個屬性指向 window.Object.prototype。
上面這個搜索的過程是由 __proto__ 組成的鏈子一直走下去的,這個過程就叫做 原型鏈
上面是一個 "鏈",下面繼續深入,看下數組
var arr = [] arr.push(1) // [1]再復雜一下,arr.valueOf() 做了什么?
- arr 自身沒有 valueOf,于是去 arr.__proto__ 上找
- arr.__proto__ 只有 pop、push 也沒有 valueOf,于是去 arr.__proto__.__proto__ 上找
- arr.__proto__.__proto__ 就是 window.Object.prototype
- 所以 arr.valueOf 其實就是 window.Object.prototype.valueOf
- arr.valueOf() 等價于 arr.valueOf.call(arr)
- arr.valueOf.call(arr) 等價于 window.Object.prototype.valueOf.call(arr)
函數進階
JS中一切皆對象。對象是擁有屬性和方法的數據。JS函數也是對象
當創建一個函數的時候,發生了什么?
實際上,函數?是?Function類型 的 實例,此時可以把每一個創建出來的函數,當成是Function類型的實例對象,
所以函數本身擁有的對象屬性是來源于?Function,Fn.Constructor 即為 Function
但是與此同時要注意:Function.prototype.__proto__ === Object.prototype
可以理解為:構造器函數的構造函數是Object
也可以簡單的理解:函數即對象
如果上面看不懂,可以繼續看下面就會明白。。。
構造函數
每個 函數?都有一個 原型對象(prototype)
原型對象 都包含一個指向 構造函數 的 指針,?
而 實例(instance) 都包含一個指向 原型對象 的 內部指針。
1. 在 JavaScript 中,用 new 關鍵字來調用的函數,稱為構造函數。構造函數首字母一般大寫。
示例:使用構造函數創建一個對象,在這個例子中,Person 就是一個構造函數,然后使用 new 創建了一個 實例對象 person。
function Person() { } var person = new Person(); person.name = 'Kevin'; console.log(person.name) // Kevin示例:
function Person(name, age, job) {this.name = namethis.age = agethis.job = jobthis.sayName = function() {alert(this.name)} } var person1 = new Person('Zaxlct', 28, 'Engineer') var person2 = new Person('Mick', 23, 'Doctor')person1 和 person2 都是 Person 的實例。這兩個實例都有一個 constructor (構造函數)屬性,該屬性(是一個指針)指向 Person。 即:
console.log(person1.constructor == Person) //true console.log(person2.constructor == Person) //true2.構造函數的執行過程
function Person(name, sex, age){this.name = name;this.sex = sex;this.age = age; } p1 = new Person('king', '男', 100);- (1) ?當以 new 關鍵字調用時,會創建一個新的內存空間,標記為 Person 的實例
- (2) ?函數體內部的 this 指向該內存,每當創建一個實例的時候,就會創建一個新的內存空間
- (3) ?給 this 添加屬性,就相當于給實例添加屬性
- (4) ?由于函數體內部的 this 指向新創建的內存空間,默認返回 this ,就相當于默認返回了該內存空間
prototype
每個 函數 都有一個 prototype 屬性,就是我們經常在各種例子中看到的那個 prototype ,指向調用該構造函數而創建的 實例的原型,
比如:上面例子中的 person1 和 person2 的原型
function Person() {} Person.prototype.name = 'Zaxlct' Person.prototype.age = 28 Person.prototype.job = 'Engineer' Person.prototype.sayName = function() {alert(this.name) }var person1 = new Person() person1.sayName() // 'Zaxlct'var person2 = new Person() person2.sayName() // 'Zaxlct'console.log(person1.sayName == person2.sayName) //true示例:
function Person() { } // 雖然寫在注釋里,但是你要注意: // prototype是函數才會有的屬性 Person.prototype.name = 'Kevin'; var person1 = new Person(); var person2 = new Person(); console.log(person1.name) // Kevin console.log(person2.name) // Kevin那這個函數的 prototype 屬性到底指向的是什么呢?是這個函數的原型嗎?
?其實,函數的 prototype 屬性指向了一個對象,這個對象正是調用該構造函數?而創建 的 實例的原型,也就是這個例子中的 person1 和 person2 的原型。
那什么是原型呢 ?
可以這樣理解:每一個JavaScript對象(null除外)在創建的時候就會與之關聯另一個對象,這個對象就是我們所說的原型,每一個對象都會從原型"繼承"屬性。
讓我們用一張圖表示構造函數和實例原型之間的關系:
在這張圖中我們用 Object.prototype 表示實例原型。
那么我們該怎么表示實例與實例原型,也就是 person 和 Person.prototype 之間的關系呢,這時候我們就要講到第二個屬性:__proto__
JS 的繼承是用過原型鏈實現的
給構造函數添加屬性
var Person = function(a){ this.a = a;return this.a; }person_1 = new Person()Person.prototype.return666 = function(){return 666; }person_2 = new Person(); console.log(person_1.return666);prototype 和 __proto__ 區別 :
- 函數(Function)才有 prototype 屬性,
- 對象(除Object)擁有__proto__。
js 原型鏈 prototype? __proto__:https://www.cnblogs.com/mengfangui/p/9566114.html
var a = {}; // 定義一個對象 console.log(a.prototype); //undefined console.log(a.__proto__); //Object {}var b = function() {} // 定義一個函數 console.log(b.prototype); //b {} console.log(b.__proto__); //function() {}__proto__ 指向
<!DOCTYPE html> <html lang="zh"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta http-equiv="X-UA-Compatible" content="ie=edge" /><title>__proto__指向</title> </head><body><script src="https://cdn.bootcss.com/lodash.js/4.17.10/lodash.min.js"></script><script type="text/javascript">/*1、字面量方式*/var a = {};console.log(a.__proto__); //Object {}console.log(a.__proto__ === a.constructor.prototype); //true/*2、構造器方式*/var A = function() {};var b = new A();console.log(b.__proto__); //A {}console.log(b.__proto__ === b.constructor.prototype); //true/*3、Object.create()方式*/var a1 = {a: 1}var a2 = Object.create(a1);console.log(a2.__proto__); //Object {a: 1}console.log(a2.__proto__ === a2.constructor.prototype); //false(此處即為圖1中的例外情況)</script> </body></html>__proto__
這是每一個 JavaScript 對象 (除了 null ) 都具有的一個屬性,叫 __proto__,這個屬性會指向該對象的原型。
為了證明這一點,我們可以在火狐或者谷歌中輸入:
function Person() { } var person = new Person(); console.log(person.__proto__ === Person.prototype); // true于是我們更新下關系圖:
既然 實例對象 和 構造函數 都可以指向原型,那么 原型 是否有屬性指向 構造函數或者實例 呢?
constructor
- 原型 指向 實例 倒是沒有,因為一個構造函數可以生成多個實例。
-
但是 原型 指向 構造函數 倒是有的,這就要講到第三個屬性:constructor,每個原型都有一個 constructor 屬性指向關聯的構造函數。
為了驗證這一點,我們可以嘗試:
function Person() {} var person1 = new Person() console.log(Person === Person.prototype.constructor) // true console.log(person1.__proto__ === Person.prototype) // true所以再更新下關系圖:?
function Person(){}; ? ?// 定義一個 構造函數
var pp = new Person(); // 定義一個 構造函數的實例
pp.__proto__ === Person.prototype; ? ? ? ? ?// true
pp.constructor === Person; ? ? ? ? ? ? ? ? ?// true
pp.__proto__.__proto__ === Person.prototype.__proto__? ???// true
Person.constructor === Function; ? ? ? ? ? ?// true
Person.prototype.constructor === Person; ? ?// true
Person.__proto__ === Function.prototype; ?//true
Function.constructor === Function; ? ? ? ? ?// true
Function.prototype ? ? ?//? () { [native code] }
Function.__proto__ ? ? ?//? () { [native code] }
Function.__proto__ === Function.prototype; ?// true
function a(){}
a.constructor === Function.constructor ?// true
構造函數 的 constructor 指向 構造函數自身
繼續深入,來看:
? ? ? ? var arr = []
? ? ? ? arr.push(1) // [1]
?復雜一下,arr.valueOf() 做了什么?
arr 自身沒有 valueOf,于是去 arr.__proto__ 上找 arr.__proto__ 只有 pop、push 也沒有 valueOf,于是去 arr.__proto__.__proto__ 上找 arr.__proto__.__proto__ 就是 window.Object.prototype 所以 arr.valueOf 其實就是 window.Object.prototype.valueOf arr.valueOf() 等價于 arr.valueOf.call(arr) arr.valueOf.call(arr) 等價于 window.Object.prototype.valueOf.call(arr)
綜上我們已經得出:
function Person() { } var person = new Person(); console.log(person.__proto__ == Person.prototype) // true console.log(Person.prototype.constructor == Person) // true // 順便學習一個ES5的方法,可以獲得對象的原型 console.log(Object.getPrototypeOf(person) === Person.prototype) // true首先要清楚:JS中所有事物都是對象,對象是擁有屬性和方法的數據。所以函數也是對象
當創建一個函數的時候,發生了什么?
實際上,
- 函數是 Function類型的實例,此時可以把每一個創建出來的函數,當成是Function類型的實例對象,
- 所以函數本身擁有的對象屬性,來源于Function,即?Fn.Constructor?就是?Function
- 但是與此同時要注意:Function.prototype.__proto__ === Object.prototype 可以理解為:構造器函數的構造函數是Object 也可以簡單的理解:函數即對象
?
?了解了構造函數、實例原型、和實例之間的關系,接下來我們講講 實例 和 原型 的關系:
定義對象的兩種方式:
class Cat {constructor(){}toString(){}toValue(){} } 等價于 function Cat(){} Cat.prototype = {constructor(){}toString(){}toValue(){} }原型鏈中的繼承
function Parent(){this.name = "Parent";this.sex = "boy" } function Child(){this.name = "Child"; } Child.prototype = new Parent() child = new Child() child.sex實例 與 原型
當讀取 實例的屬性?時,如果找不到,就會查找與對象關聯的原型中的屬性,如果還查不到,就去找原型的原型,一直找到最頂層為止。
舉個例子:
function Person() { } Person.prototype.name = 'Kevin'; var person = new Person(); person.name = 'Daisy'; console.log(person.name) // Daisy delete person.name; console.log(person.name) // Kevin在這個例子中,我們給實例對象 person 添加了 name 屬性,當我們打印 person.name 的時候,結果自然為 Daisy。但是當我們刪除了 person 的 name 屬性時,讀取 person.name,從 person 對象中找不到 name 屬性,就會從 person 的原型也就是 person.__proto__ ,也就是 Person.prototype 中查找,幸運的是我們找到了 name 屬性,結果為 Kevin。
但是萬一還沒有找到呢?原型的原型又是什么呢?
原型 的 原型
在前面,我們已經講了原型也是一個對象,既然是對象,我們就可以用最原始的方式創建它,那就是:
var obj = new Object();
obj.name = 'Kevin'
console.log(obj.name) // Kevin
所以原型對象是通過 Object 構造函數生成的,結合之前所講,實例的 __proto__? 指向 構造函數的 prototype ,所以我們再更新下關系圖:
原型鏈
那 Object.prototype 的原型呢?
null,不信我們可以打印:console.log(Object.prototype.__proto__ === null) // true
所以查到屬性的時候查到 Object.prototype 就可以停止查找了。所以最后一張關系圖就是
順便還要說一下,圖中由 相互關聯的原型組成的鏈狀結構 就是 原型鏈,也就是藍色的這條線。
補充
最后,補充三點大家可能不會注意的地方:
constructor
首先是 constructor 屬性,我們看個例子:
function Person() { } var person = new Person(); console.log(person.constructor === Person); // true當獲取 person.constructor 時,其實 person 中并沒有 constructor 屬性,當不能讀取到 constructor 屬性時,會從 person 的原型也就是 Person.prototype 中讀取,正好原型中有該屬性,所以:
person.constructor === Person.prototype.constructor__proto__
其次是 __proto__ ,絕大部分瀏覽器都支持這個非標準的方法訪問原型,然而它并不存在于 Person.prototype 中,實際上,它是來自于 Object.prototype ,與其說是一個屬性,不如說是一個 getter/setter,當使用 obj.__proto__ 時,可以理解成返回了 Object.getPrototypeOf(obj)。
真的是繼承嗎?
最后是關于繼承,前面我們講到“每一個對象都會從原型‘繼承'屬性”,實際上,繼承是一個十分具有迷惑性的說法,引用《你不知道的JavaScript》中的話,就是:
繼承意味著復制操作,然而 JavaScript 默認并不會復制對象的屬性,相反,JavaScript 只是在兩個對象之間創建一個關聯,這樣,一個對象就可以通過委托訪問另一個對象的屬性和函數,所以與其叫繼承,委托的說法反而更準確些。
自執行 匿名 函數
- 聲明式函數。會導致函數提升,function會被解釋器優先編譯。即我們用聲明式寫函數,可以在任何區域聲明,不會影響我們調用。function XXX(){}
? ? ? ? fn1();
? ? ? ? function fn1(){}? ? // 可以正常調用 - 函數表達式。函數表達式經常使用,而函數表達式中的?function?則不會出現函數提升。而是JS解釋器逐行解釋,到了這一句才會解釋。因此如果調用在函數表達式之前,則會調用失敗。var k = function(){}
? ? ? ? fn2();
? ? ? ? var fn2 = function(){}? ? // 無法調用
小括號 的 作用:
- 小括號 能把表達式組合分塊,并且每一塊,也就是每一對小括號,都有一個返回值。返回值實際上也就是 小括號中 表達式的返回值。
- 所以,當我們用一對小括號把匿名函數括起來的時候,實際上 小括號對 返回的就是一個匿名函數的 Function 對象。
- 因此,小括號對 加上 匿名函數 就如同 有名字的函數 被我們取得它的引用位置了。所以如果在這個引用變量后面再加上參數列表,就會實現普通函數的調用形式。
- 簡單來說就是:小括號有返回值,也就是小括號內的函數或者表達式的返回值,所以說小括號內的 function 返回值等于小括號的返回值
自執行函數
1. 先來看個最簡單的自執行函數
(function(){}());相當于聲明并調用
var b = function () {} b()2.?自執行函數也可以有名字
function b(){... }()3.?執行函數也可以傳參
function b(i){console.log(i) }(5)總結:自執行函數 在調用上與普通函數一樣,可以匿名,可以傳參。只不過是在聲明的時候自調用了一次
常見的自執行匿名函數:
- 第一種:(function(x,y){return x+y;})(3,4);? ? ? ? ? ? ? // 兩個()() ,function寫在第一個()里面,第二個()用來傳參,這種比較常見。默認返回值 是? undefine
- 第二種:(function(param) { alert(param);})('張三');? ? ? ?// 自執行函數的傳參。默認返回值 是? undefine
- 第三種:(function(param) { console.log(param); return arguments.callee;})('html5')('php');? ?
不常見的自執行匿名函數:
- 第四種:~(function(){ alert('hellow world!'); })();? ? ? ? // 默認返回值 是 -1 ~function(x, y) {return x+y; }(3, 4);
- 第五種:void function(){ alert('hellow world!'); }();? ? ?// 默認返回值 是? undefine。??void function(x) {x = x-1;}(9);
- 第六種:+function(){ alert('hellow world!'); }();? ? ? ? ? // 默認返回值 是 NaN +function(x,y){return x+y; }(3,4);++function(x,y){return x+y; }(3,4);
- 第七種:-function(){ alert('hellow world!'); }();? ? ? ? ???// 默認返回值 是 NaN -function(x,y){return x+y; }(3,4);--function(x,y){return x+y; }(3,4);
- 第八種:!function(){ alert('hellow world!'); }();? ? ? ? ? ?// 默認返回?true,是一個 bool 值
- 第九種:(? function(x,y){return x+y;}(3,4)? );? ? ? ? ??// 一個() ,里面寫 function(){}()? 默認返回值 是? undefine
- 匿名函數的執行放在 [ ] 中: [function(){console.log(this) // 瀏覽器的控制臺輸出window }(this)]
-
匿名函數前加 typeof
typeof function(){console.log(this) // 瀏覽器得控制臺輸出window }(this)
示例:匿名函數自執行 實現 異步函數遞歸
(function async(i) { if (i >= 5){ return }else{setTimeout(() => {console.log(i)i++async(i)}, 1000)} })(0)自執行匿名函數 執行耗時結果:
- new 方法永遠最慢。
- 括號 在測試里表現始終很快,在大多數情況下比感嘆號更快,甚至可以說是最優的。
- 加減號 在chrome表現驚人,而且在其他瀏覽器下也普遍很快,相比加號效果更好。當然這只是個簡單測試,不能說明問題。
但有些結論是有意義的:括號和加減號最優
面向對象 --- 封裝
封裝:把客觀事物封裝成抽象的類,隱藏屬性和方法,僅對外公開接口。
在ES6之前,是不存在?class?這個語法糖類的。所以實現大多采用原型對象和構造函數
- 私有 屬性和方法:只能在構造函數內訪問不能被外部所訪問(在構造函數內使用var聲明的屬性)
- 公有 屬性和方法(或實例方法):對象外可以訪問到對象內的屬性和方法(在構造函數內使用this設置,或者設置在構造函數原型對象上比如Cat.prototype.xxx)
- 靜態 屬性和方法:定義在構造函數上的方法(比如Cat.xxx),不需要實例就可以調用(例如Object.assign())
在ES6之后,存在class這個語法糖類。當你使用class的時候,它會默認調用constructor這個函數,來接收一些參數,并構造出一個新的實例對象(this)并將它返回,因此它被稱為constructor構造方法(函數)。
私有變量、函數
在函數內部定義的變量和函數如果不對外提供接口,那么外部將無法訪問到,也就是變為私有變量和私有函數。
function Obj() {var a = 0 //私有變量var fn = function() {//私有函數} }var o = new Obj() console.log(o.a) //undefined console.log(o.fn) //undefined靜態變量、函數
當定義一個函數后通過 “.”為其添加的屬性和函數,通過對象本身仍然可以訪問得到,但是其實例卻訪問不到,這樣的變量和函數分別被稱為靜態變量和靜態函數。
function Obj() {}Obj.a = 0 //靜態變量Obj.fn = function() {//靜態函數 }console.log(Obj.a) //0 console.log(typeof Obj.fn) //functionvar o = new Obj() console.log(o.a) //undefined console.log(typeof o.fn) //undefine實例變量、函數
在面向對象編程中除了一些庫函數我們還是希望在對象定義的時候同時定義一些屬性和方法,實例化后可以訪問,JavaScript也能做到這樣。
function Obj(){this.a=[]; //實例變量this.fn=function(){ //實例方法 } }console.log(typeof Obj.a); //undefined console.log(typeof Obj.fn); //undefinedvar o=new Obj(); console.log(typeof o.a); //object console.log(typeof o.fn); //function測試
function Foo() {getName = function() {alert(1)}return this } Foo.getName = function() {alert(2) } Foo.prototype.getName = function() {alert(3) } var getName = function() {alert(4) } function getName() {alert(5) }請寫出以下輸出結果:
Foo.getName() getName() Foo().getName() getName() new Foo.getName() new Foo().getName() new new Foo().getName()解讀:首先定義了一個叫 Foo 的函數,之后為 Foo 創建了一個叫 getName 的靜態屬性存儲了一個匿名函數,之后為 Foo 的原型對象新創建了一個叫 getName 的匿名函數。之后又通過函數變量表達式創建了一個 getName 的函數,最后再聲明一個叫 getName 函數。
先來劇透一下答案,再來看看具體分析
//答案: Foo.getName() // 2 getName() // 4 Foo().getName() // 1 getName() // 1 new Foo.getName() // 2 new Foo().getName() // 3 new new Foo().getName() // 3- 第一問:Foo.getName 自然是訪問 Foo 函數上存儲的靜態屬性,自然是 2
- 第二問:直接調用 getName 函數。既然是直接調用那么就是訪問當前上文作用域內的叫 getName 的函數,所以跟 1 2 3 都沒什么關系。但是此處有兩個坑,一是變量聲明提升,二是函數表達式。關于函數變量提示,此處省略一萬字。。。。題中代碼最終執行時的是
- 第三問:?Foo().getName(); 先執行了 Foo 函數,然后調用 Foo 函數的返回值對象的 getName 屬性函數。這里 Foo 函數的返回值是 this,this 指向 window 對象。所以第三問相當于執行 window.getName()。 然而這里 Foo 函數將此變量的值賦值為function(){alert(1)}。
- 第四問:直接調用 getName 函數,相當于 window.getName(),答案和前面一樣。
后面三問都是考察 js 的運算符優先級問
面向對象 --- 繼承
繼承:繼承就是子類可以使用父類的所有功能,并且對這些功能進行擴展。
比如我有個構造函數A,然后又有個構造函數B,但是B想要使用A里的一些屬性和方法,一種辦法就是讓我們自身化身為CV俠,復制粘貼一波。還有一種就是利用繼承,我讓B直接繼承了A里的功能,這樣我就能用它了。
- 1. 原型鏈繼承
- 2. 構造繼承
- 3. 組合繼承
- 4. 寄生組合繼承
- 5. 原型式繼承
- 6. 寄生繼承
- 7. 混入式繼承
- 8. class中的extends繼承
原型鏈繼承
instanceof 關鍵字
instanceof 運算符用于檢測構造函數的 prototype 屬性是否出現在某個實例對象的原型鏈上。?
A instanceof B?
實例對象A instanceof 構造函數B
檢測A的原型鏈(__proto__)上是否有B.prototype,有則返回true,否則返回false
class中的繼承:extends、super
面向對象 ---?多態
多態的實際含義是:同一操作作用于不同的對象上,可以產生不同的解釋和不同的執行結果。
class中的多態:extends、super
對于js多態的詳細解釋:🍃【何不三連】JS面向對象最后一彈-多態篇(羽化升仙) - 掘金
this 與 new
this 永遠指向最后調用它的那個對象
this,是指當前的本身,在非嚴格模式下this指向的是全局對象window,而在嚴格模式下會綁定到undefined。
this?的?5種綁定方式:
- 默認綁定 ( 非嚴格模式下this指向全局對象,嚴格模式下 this 會綁定到 undefined )
- 隱式綁定 ( 當函數引用有上下文對象時, 如 obj.foo() 的調用方式,foo內的 this指向obj )
- 顯示綁定 ( 通過 call() 或者 apply() 方法直接指定 this 的綁定對象, 如 foo.call(obj) )
- new 綁定
- 箭頭函數綁定 ( this 的指向由外層作用域決定的,并且指向函數定義時的 this,而不是執行時的 this)
再次強調:
- this 永遠指向最后調用它的那個對象
- 匿名函數的 this 永遠指向 window
- 使用 call() 或者 apply() 的函數會直接執行
- bing() 是創建一個新的函數,需要手動調用才會執行
- 如果 call、appy、bind 接收到的第一個參數是空或者 null,undefine 的話,則會忽略這個參數
- forEach、map、filter 函數的第二個參數也是能顯式綁定 this 的
默認綁定
沒有綁定到任何對象的 變量、函數、屬性 等,都是 綁定到 window 對象。
var a = 10; // a 屬于 window function foo(){console.log(this.a); // this 指向 windowconsole.log(this); // window 對象console.log(window) // window 對象console.log(this === window); // true } foo(); // 10 console.log(window.a); // 10 console.log(window.a === this.a); // true使用 let 、const 聲明的 變量,不會綁定到 window
let a = 10; const b = 20; function foo(){console.log(this.a);console.log(this.b); } foo(); console.log(window.a); var a = 1; function foo(){var a = 2console.log(this);console.log(this.a); // foo 屬于 window ,所以 打印 1 } foo();修改代碼:
var a = 1; function foo(){var a = 2function inner(){console.log(this.a); }inner(); } foo(); // 打印 1foo 函數屬于 window,inner 是 foo 的內嵌函數,所以 this 指向 window?
隱式綁定
示例代碼:
function foo(){console.log(this.a); } var obj = {a:1, foo}; // var obj = {foo} 等價于 var obj = {foo: foo} var a = 2; obj.foo(); // 打印 1隱式丟失:就是被隱式綁定的函數,在特定的情況下會丟失綁定對象。
特定情況是指:
- 1. 使用另一個變量來給函數取別名
- 2. 或者 將函數作為參數傳遞時會被隱式賦值,回調函數丟失 this 綁定
示例:
function foo(){console.log(this.a); } var obj = {a:1, foo}; var a = 2; var foo2 = obj.foo; obj.foo(); // 1 foo2(); // 2 這里 foo2(); 是被 window 調用的,即 window.foo2(); 使的 this 指向 window示例:
function foo(){console.log(this.a); } function doFoo(fn){console.log(this);fn(); } var obj = {a:1, foo}; var a = 2; doFoo(obj.foo); // 打印 2示例:
function foo(){console.log(this.a); } function doFoo(fn){console.log(this);fn(); } var obj = {a:1, foo}; var a = 2;var obj2 = {a:3, doFoo}; obj2.doFoo(obj.foo); // 打印 2結論:如果把一個函數當成參數傳遞到另一個函數的時候,也會發生隱式丟失的問題,且與包裹著它的函數的 this 指向無關。在非嚴格模式下,會把該函數的 this 綁定到 window 上。嚴格模式下綁定到 undefine
示例:
var obj1 = {a:1 }; var obj2 = {a:2,foo1: function(){console.log(this.a); // 打印 2},foo2: function(){setTimeout(function(){console.log(this); // 打印 window 對象console.log(this.a); // 打印 3}, 0);} }; var a =3; obj2.foo1(); obj2.foo2(); // this 永遠指向最后調用它的那個對象。。。call, apply, bind
使用 call() 或者 apply()??的函數是會直接執行的。
bind() 是創建一個新的函數,需要手動調用才會執行
- 1. 都是對函數的操作,使用方式:函數.call
- 2. 都是用來改變函數的 this 對象的指向的。
- 3. 第一個參數都是 this 要指向的對象。
- 4. 都可以利用后續參數傳參。
- 5. call 接受函數傳參方式為:fn.call(this, 1, 2, 3)
- 6. apply 接受函數傳參方式為:fn.apply(this,[1, 2, 3])
- 7. bind 的返回值為一個新的函數,需要再次調用: fn.bind(this)(1, 2, 3)
專利局( JS 混淆,大量運用原型鏈 )( F12 打開控制臺?):http://cpquery.sipo.gov.cn/
示例( 函數內嵌套的函數中的 this 指向 window ):
var obj1 = {a:1 }; var obj2 = {a:2,foo1: function(){console.log(this.a);},foo2: function(){var that = this;console.log(that); // 打印 obj2 對象function inner(){console.log(this); // 打印 window 對象console.log(this.a); // 打印 window 對象 的 a}inner();inner.call(obj2); // 改變 this 指向 obj2, 所以 打印 2} }; var a =3; obj2.foo1(); // 打印 2 obj2.foo2(); // 打印 3示例:
function foo(){console.log(this.a);return function(){console.log(this.a)}; } var obj = {a:1}; var a = 2;foo(); foo.call(obj); foo().call(obj); // foo的返回值是一個函數,再進行call,改變this指向//結果: // 2 ---> foo(); 輸出結果 // 1 ---> foo().call(obj); 改變this指向 obj, 輸出 1 // 2 ---> // 1 ---> foo 的返回值,再進行call,改變this指向new 綁定
function Person(name){this.name = name;this.foo1 = function(){console.log(this.name);};this.foo2 = function(){return function(){console.log(this.name);};}; } var person1 = new Person('person1'); person1.foo1(); person1.foo2()();new 和 call 同時出現
var name = 'window' function Person(name){this.name = name;this.foo = function(){console.log(this.name);return function(){console.log(this.name);};}; } var person1 = new Person('person1'); var person2 = new Person('person2');person1.foo.call(person2)(); person1.foo().call(person2);箭頭函數
箭頭函數綁定中:this 的指向由外層作用域決定的,并且指向函數定義時的 this,而不是執行時的 this。
var obj = {name: 'obj',foo1: () => {console.log(this.name);},foo2: function(){console.log(this.name);return () => {console.log(this.name)};} }; var name = 'window'; obj.foo1(); obj.foo2()();結果: window obj obj示例:
var name = 'window'; function Person(name){this.name = name;this.foo1 = function(){console.log(this.name);};this.foo2 = ()=>{console.log(this.name);}; } var Person2 = {name: 'Person2',foo2: ()=>{ console.log(this.name); } }; var person1 = new Person('person1'); person1.foo1(); person1.foo2(); Person2.foo2();結果: person1 person1 window示例:( 箭頭函數 與 call 結合 )
箭頭函數里面的 this 是由外層作用域來決定的,并且指向函數定義時的 this,而不是執行時的 this
字面量創建的對象,作用域是 window,如果里面有箭頭函數屬性的話, this??指向的是 window
構造函數創建的對象,作用域是可以理解為是這個構造函數,且這個構造函數的 this 是指向新建的對象的,因此 this 指向這個對象
箭頭函數的 this 是無法通過 bind、call、apply 來直接修改,但是可以用過改變作用域中 this 的指向來間接修改
new?的過程中到底發生了什么?
1. 新生成了一個對象
2. 鏈接到原型
3. 綁定 this
4. 返回新對象
更多可以參看 《JavaScript高級程序設計 第4版》
js 逆向技巧
From:js逆向技巧 - 走看看
一、總結
1. 全局關鍵詞搜索
2. 事件監聽斷點
3. 堆棧跟蹤
非常頻繁使用的事件監聽斷點:script、XHR
一般頻繁使用的:Dom斷點、Control、timer
不太常用但是偶爾會用到的:Mouse
一句話總結:
- 1. 搜索:全局搜索、代碼內搜索
- 2. debug:常規 debug、XHR debug、行為 debug
- 3. 查看請求調用的堆棧
- 4. 執行 堆內存 中的函數
- 5. 修改 堆棧 中的參數值
- 6. 寫 js 代碼
- 7. 打印 windows 對象的值
- 8. 勾子:cookie鉤子、請求鉤子、header鉤子
二、js逆向技巧
博客對應課程的視頻位置:
當我們抓取網頁端數據時,經常被加密參數、加密數據所困擾,如何快速定位這些加解密函數,尤為重要。本片文章是我逆向js時一些技巧的總結,如有遺漏,歡迎補充。
所需環境:Chrome瀏覽器
1. 搜索
1.1 全局搜索 ( Ctrl + shift + f )
適用于根據關鍵詞快速定位關鍵文件及代碼
當前頁面 右鍵 ---> 檢查,彈出檢查工具
搜索支持 關鍵詞、正則表達式
1.2 代碼內搜索 ( Ctrl + f )
適用于根據關鍵詞快速定位關鍵代碼
點擊代碼,然后按 ctrl+f 或 command+f 調出搜索框。搜索支持 關鍵詞、css表達式、xpath
2. debug
2.1 常規 debug
適用于分析關鍵函數代碼邏輯
a、埋下斷點
b、調試
如圖所示,標記了 1 到 6,下面分別介紹其含義
- 1. 執行到下一個端點
- 2. 執行下一步,不會進入所調用的函數內部
- 3. 進入所調用的函數內部
- 4. 跳出函數內部
- 5. 一步步執行代碼,遇到有函數調用,則進入函數
- 6.Call Stack 為代碼調用的堆棧信息,代碼執行順序為由下至上,這對于著關鍵函數前后調用關系很有幫助
2.2 XHR debug
匹配url中關鍵詞,匹配到則跳轉到參數生成處,適用于url中的加密參數全局搜索搜不到,可采用這種方式攔截
2.3 行為 debug
適用于點擊按鈕時,分析代碼執行邏輯
如圖所示,可快速定位點擊探索按鈕后,所執行的js。
3 查看請求調用的堆棧
可以在 Network 選項卡下,該請求的 Initiator 列里看到它的調用棧,調用順序由上而下:
4. 執行堆內存中的函數
當 debug 到某一個函數時,我們想主動調用,比如傳遞下自定義的參數,這時可以在檢查工具里的 console 里調用
此處要注意,只有debug打這個函數時,控制臺里才可以調用。如果想保留這個函數,可使用this.xxx=xxx 的方式。之后調用時無需debug到xxx函數,直接使用this.xxx 即可。
5. 修改堆棧中的參數值
6. 寫 js 代碼
7. 打印 windows 對象的值
在 console 中輸入如下代碼,如只打印 _$ 開頭的變量值
for (var p in window) {if (p.substr(0, 2) !== "_$") continue;console.log(p + " >>> " + eval(p)) }8. 勾子
以 chrome 插件的方式,在匹配到關鍵詞處插入斷點
8.1 cookie 鉤子
用于定位 cookie 中關鍵參數生成位置
var code = function(){var org = document.cookie.__lookupSetter__('cookie');document.__defineSetter__("cookie",function(cookie){if(cookie.indexOf('TSdc75a61a')>-1){debugger;}org = cookie;});document.__defineGetter__("cookie",function(){return org;}); } var script = document.createElement('script'); script.textContent = '(' + code + ')()'; (document.head||document.documentElement).appendChild(script); script.parentNode.removeChild(script);當 cookie 中匹配到了?TSdc75a61a, 則插入斷點。
8.2 請求鉤子
用于定位請求中關鍵參數生成位置
var code = function(){ var open = window.XMLHttpRequest.prototype.open; window.XMLHttpRequest.prototype.open = function (method, url, async){if (url.indexOf("MmEwMD")>-1){debugger;}return open.apply(this, arguments); }; } var script = document.createElement('script'); script.textContent = '(' + code + ')()'; (document.head||document.documentElement).appendChild(script); script.parentNode.removeChild(script);當請求的 url 里包含?MmEwMD?時,則插入斷點
8.3 header 鉤子
用于定位 header 中關鍵參數生成位置
var code = function(){ var org = window.XMLHttpRequest.prototype.setRequestHeader; window.XMLHttpRequest.prototype.setRequestHeader = function(key,value){if(key=='Authorization'){debugger;}return org.apply(this,arguments); } } var script = document.createElement('script'); script.textContent = '(' + code + ')()'; (document.head||document.documentElement).appendChild(script); script.parentNode.removeChild(script);當 header 中包含?Authorization?時,則插入斷點
8.4 manifest.json
插件的配置文件
{"name": "Injection","version": "2.0","description": "RequestHeader鉤子","manifest_version": 2,"content_scripts": [{"matches": ["<all_urls>"],"js": ["inject.js"],"all_frames": true,"permissions": ["tabs"],"run_at": "document_start"}] }使用方法
a、如圖所示,創建一個文件夾,文件夾中創建一個鉤子函數文件inject.js 及 插件的配置文件 mainfest.json 即可
b、打開chrome 的擴展程序, 加載已解壓的擴展程序,選擇步驟1創建的文件夾即可
c、切換回原網頁,刷新頁面,若鉤子函數關鍵詞匹配到了,則觸發debug
刷新型 cookie 反爬
瑞數、加固樂
調試干擾
無限 debugger
無限 debugger,還有一個非常靚麗的名字:debugger地獄
最關鍵的一點:無限 debugger 不可能無限,否則瀏覽器會卡死
實現debugger的方案: Function,關鍵字,eval制作虛擬機
【不可混淆】
? ? ? ? 1. debugger;?
【可混淆】
? ? ? ? 2. eval("debugger;")
【可重度混淆】
? ? ? ? 3. ?Function("debugger").call()/apply() ?或賦值 ?bind()
? ? ? ? XXX.constructor("debugger").call("action")
? ? ? ? Function.constructor("debugger").call("action")
? ? ? ? (function() {return !![];}["constructor"]("debugger")["call"]("action"))
無限 debugger 的處理實際上很簡單,有以下幾種情況
1. ? 無限 debugger 貫穿全局:干掉定時器等全局事件(置空或重寫)
2. ? 無限 debugger 在加密邏輯之前。想要調試到函數入口,必須越過這個無限 debugger
- 針對靜態文件/偽動態文件(大部分都是這個情況)
- 用 fiddler ?Autoresponse 刪掉 debugger
- 可以右鍵 never
- 針對真動態文件或 Autoresponse 失效或刪掉 debugger 邏輯很繁瑣的情況下
? ? 1. 如果是 Function 原理的 debugger,可以重寫 函數構造器 Function.prototype.constructor_bak = Function.prototype.constructor; Function.prototype.constructor = function(){//var arg_string = "";//for (var i=0; i<arguments.length; i++){ arg_string += arguments[i] }if("debugger" === arguments){}else{return Function.prototype.constructor_bak.apply(this, arguments);} }; ????2. 如果是 eval 型的構造器,可以重構 eval 函數 eval_bak = eval window.eval = function(a){if('debugger'===a){}else{eval_bak(a);} } ????3. 如果是定時器,并且 2失效了,可以重構定時器
????4. 在以上方式都失效時,向上找堆棧,在進入無限debugger之前打上斷點將觸發無限debugger 的函數置空(最麻煩,但是適用性最廣)
3. ? ?無限 debugger 在加密邏輯之后:不用管,script/ 第一行斷點打上,從頭開始
控制臺 檢測
:https://www.cnblogs.com/wuxianyu/p/14523102.html
?:https://match.yuanrenxue.com/match/16
打個 script 斷點,當跳轉到主頁時,查看 源碼,代碼如下:
<script>var ConsoleManager={onOpen:function(){alert("Console is opened")},onClose:function(){alert("Console is closed")},init:function(){var self = this;var x = document.createElement('div');var isOpening = false,isOpened=false;Object.defineProperty(x, 'id', {get:function(){if(!isOpening){self.onOpen();isOpening=true;}isOpened=true;}});setInterval(function(){isOpened=false;console.info(x);console.clear();if(!isOpened && isOpening){self.onClose();isOpening=false;}},200)}}ConsoleManager.onOpen = function(){try{window.open('http://match.yuanrenxue.com/',target='_self');}catch(err){var a = document.createElement("button");a.onclick=function(){window.open('http://match.yuanrenxue.com/',target='_self');};a.click();}};ConsoleManager.init(); </script>上面過控制臺檢測
e.open = function(){} ConsoleManager.onOpen = function(){}// 或者 window.open = function(){} setInterval = function(){}打上斷點之后,當要運行跳轉時,直接把跳轉 置為 空函數 即可
控制臺檢測原理:原理就是使用 console 的特性,只有在控制臺打開的時候,console 才會對一些信息和內容進行打印。如果設置一個定時器,在定時器中不斷循環獲取一個參數x,并且對這個參數x進行 hook,利用 Object.defineProperty 處理其get 屬性,那么當打開控制臺的一瞬間,console 就會生效,獲取屬性并觸發 hook,執行 Object.defineProperty 內的邏輯。
chrome 瀏覽器有這個特性, firefox 沒有這個特性,可以多換幾個瀏覽器試一下。
<html> <head></head> <script>var x = document.createElement('div');Object.defineProperty(x, 'id',{get:function(){alert(12345)'}});function test(){console.log(x);console.clear();}setInterval(test, 200); </script> </html>內存爆破原理
其實可以叫做 "蜜罐內存爆破",當檢測出來時( 例如:js 代碼進行了格式化,導致特征字符穿不匹配,使用 js 正則檢測出來??),走到蜜罐里面一直循環。
對于 js 格式化被正則檢測出來,可以分段進行 js 格式化,逐漸排出,找到關鍵位置,然后讓正則檢測時返回 true?
內存爆破,指 js 通過死循環/頻繁操作數據庫( 包括cookie ) / 頻繁調取 history 等方式,使瀏覽器崩潰的一種反調試手段。
關鍵詞:頻繁
還有一種特性情況:js文件很大,電腦內存不足(這種情況在調試層面幾乎無解)
特點:
? ? ? ? ?1. ? ?正常運行時,一切正常
?? ? ? ? 2. ? ?調試時利用時間差,調試特點等講控制流推入循環
?? ? ? ? 3.? ? ob混淆內存爆破原理:利用正則/toString() 判斷代碼是否進行格式化
?? ? ? ? 4. ? ?利用瀏覽器指紋判斷是否為瀏覽器環境
總結
以上是生活随笔為你收集整理的JavaScript 逆向 ( 一 ) --- JavaScript 语法基础的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 开源 Python网络爬虫框架 Scra
- 下一篇: SpringBoot 自带工具类~Col