javascript
JavaScript 逆向 ( 一 ) --- JavaScript 语法基础
js 逆向:https://www.cnblogs.com/wuxianyu/category/1940304.html
js逆向2:https://www.cnblogs.com/wuxianyu/category/1941486.html
JS 中的類型轉(zhuǎn)換:https://segmentfault.com/a/1190000013679224
1、JavaScript 基礎(chǔ)
菜鳥教程 JavaScript 教程:https://www.runoob.com/js/js-tutorial.html
- 1. 基礎(chǔ)數(shù)據(jù)類型:number、string、boolean、null、undefined、object
- 2. 順序、條件、循環(huán)、比較
- 3. 函數(shù)
- 4. 運(yùn)算符
js 數(shù)組遍歷方法總結(jié):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
強(qiáng)烈推薦書籍《JavaScript高級(jí)程序設(shè)計(jì) 第4版》:https://book.douban.com/subject/35175321/
示例:
1.1 Javascript 函數(shù)
:https://www.liaoxuefeng.com/wiki/1022910821149312/1023021053637728
JavaScript 中的函數(shù)是`頭等公民`,不僅可以像變量一樣使用它,同時(shí)它還具有十分強(qiáng)大的抽象能力;
定義函數(shù)的 2 種方式
在JavaScript 中,定義函數(shù)的方式如下:
function abs(x) {if (x >= 0) {return x;} else {return -x;} }上述abs()函數(shù)的定義如下:
- function指出這是一個(gè)函數(shù)定義;
- abs是函數(shù)的名稱;
- (x)括號(hào)內(nèi)列出函數(shù)的參數(shù),多個(gè)參數(shù)以,分隔;
- { ... }之間的代碼是函數(shù)體,可以包含若干語(yǔ)句,甚至可以沒有任何語(yǔ)句。
請(qǐng)注意,函數(shù)體內(nèi)部的語(yǔ)句在執(zhí)行時(shí),一旦執(zhí)行到return時(shí),函數(shù)就執(zhí)行完畢,并將結(jié)果返回。因此,函數(shù)內(nèi)部通過條件判斷和循環(huán)可以實(shí)現(xiàn)非常復(fù)雜的邏輯。
如果沒有return語(yǔ)句,函數(shù)執(zhí)行完畢后也會(huì)返回結(jié)果,只是結(jié)果為undefined。
由于JavaScript的函數(shù)也是一個(gè)對(duì)象,上述定義的abs()函數(shù)實(shí)際上是一個(gè)函數(shù)對(duì)象,而函數(shù)名abs可以視為指向該函數(shù)的變量。
因此,第二種定義函數(shù)的方式如下:
var abs = function (x) {if (x >= 0) {return x;} else {return -x;} };在這種方式下,function (x) { ... }是一個(gè)匿名函數(shù),它沒有函數(shù)名。但是,這個(gè)匿名函數(shù)賦值給了變量abs,所以,通過變量abs就可以調(diào)用該函數(shù)。
注意:上述兩種定義完全等價(jià),第二種方式按照完整語(yǔ)法需要在函數(shù)體末尾加一個(gè);,表示賦值語(yǔ)句結(jié)束。( 加不加都一樣,如果按照語(yǔ)法完整性要求,需要加上?)
調(diào)用函數(shù)
調(diào)用函數(shù)時(shí),按順序傳入?yún)?shù)即可:
abs(10); // 返回10 abs(-9); // 返回9由于JavaScript 允許傳入任意個(gè)參數(shù)而不影響調(diào)用,因此傳入的參數(shù)比定義的參數(shù)多也沒有問題,雖然函數(shù)內(nèi)部并不需要這些參數(shù):
abs(10, 'blablabla'); // 返回10 abs(-9, 'haha', 'hehe', null); // 返回9傳入的參數(shù)比定義的少也沒有問題:
abs(); // 返回NaN此時(shí)?abs(x)函數(shù)的參數(shù)x將收到undefined,計(jì)算結(jié)果為NaN。要避免收到?undefined,可以對(duì)參數(shù)進(jìn)行檢查:
function abs(x) {if (typeof x !== 'number') {throw 'Not a number';}if (x >= 0) {return x;} else {return -x;} }arguments
JavaScript 還有一個(gè)免費(fèi)贈(zèng)送的關(guān)鍵字arguments,它只在函數(shù)內(nèi)部起作用,并且永遠(yuǎn)指向當(dāng)前函數(shù)的調(diào)用者傳入的所有參數(shù)。arguments?類似?Array?但它不是一個(gè)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,你可以獲得調(diào)用者傳入的所有參數(shù)。也就是說,即使函數(shù)不定義任何參數(shù),還是可以拿到參數(shù)的值,實(shí)際上arguments最常用于判斷傳入?yún)?shù)的個(gè)數(shù)。
rest 參數(shù)
由于 JavaScrip t函數(shù)允許接收任意個(gè)參數(shù),于是我們就不得不用arguments來獲取所有參數(shù):
'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); }為了獲取除了已定義參數(shù)a、b之外的參數(shù),我們不得不用arguments,并且循環(huán)要從索引2開始以便排除前兩個(gè)參數(shù),這種寫法很別扭,只是為了獲得額外的rest參數(shù),有沒有更好的方法?
ES6 標(biāo)準(zhǔn)引入了rest參數(shù),上面的函數(shù)可以改寫為:
function foo(a, b, ...rest) {console.log('a = ' + a);console.log('b = ' + b);console.log(rest); }foo(1, 2, 3, 4, 5); // 結(jié)果: // a = 1 // b = 2 // Array [ 3, 4, 5 ]foo(1); // 結(jié)果: // a = 1 // b = undefined // Array []rest參數(shù)只能寫在最后,前面用...標(biāo)識(shí),從運(yùn)行結(jié)果可知,傳入的參數(shù)先綁定a、b,多余的參數(shù)以數(shù)組形式交給變量rest,所以,不再需要arguments我們就獲取了全部參數(shù)。
如果傳入的參數(shù)連正常定義的參數(shù)都沒填滿,也不要緊,rest參數(shù)會(huì)接收一個(gè)空數(shù)組(注意不是undefined)。
因?yàn)閞est參數(shù)是ES6新標(biāo)準(zhǔn),所以你需要測(cè)試一下瀏覽器是否支持。請(qǐng)用rest參數(shù)編寫一個(gè)sum()函數(shù),接收任意個(gè)參數(shù)并返回它們的和:
'use strict'function sum(...rest) {console.log(rest) } sum(1,2,3,4,5)測(cè)試:
// 測(cè)試: var i, args = []; for (i=1; i<=100; i++) {args.push(i); } if (sum() !== 0) {console.log('測(cè)試失敗: sum() = ' + sum()); } else if (sum(1) !== 1) {console.log('測(cè)試失敗: sum(1) = ' + sum(1)); } else if (sum(2, 3) !== 5) {console.log('測(cè)試失敗: sum(2, 3) = ' + sum(2, 3)); } else if (sum.apply(null, args) !== 5050) {console.log('測(cè)試失敗: sum(1, 2, 3, ..., 100) = ' + sum.apply(null, args)); } else {console.log('測(cè)試通過!'); }小心你的 return 語(yǔ)句
JavaScript 引擎有一個(gè)在行末自動(dòng)添加分號(hào)的機(jī)制,
這可能讓你栽到return語(yǔ)句的一個(gè)大坑。。。
示例:
function foo() {return { name: 'foo' }; }foo(); // { name: 'foo' }如果把 return 語(yǔ)句拆成兩行:
function foo() {return{ name: 'foo' }; }foo(); // undefined要小心了,由于 JavaScript 引擎在行末自動(dòng)添加分號(hào)的機(jī)制,上面的代碼實(shí)際上變成了:
function foo() {return; // 自動(dòng)添加了分號(hào),相當(dāng)于return undefined;{ name: 'foo' }; // 這行語(yǔ)句已經(jīng)沒法執(zhí)行到了 }所以正確的多行寫法是:
function foo() {return { // 這里不會(huì)自動(dòng)加分號(hào),因?yàn)閧表示語(yǔ)句尚未結(jié)束name: 'foo'}; }變量作用域與解構(gòu)賦值
在 JavaScript 中,用var聲明的變量實(shí)際上是有作用域的。
如果一個(gè)變量在函數(shù)體內(nèi)部聲明,則該變量的作用域?yàn)檎麄€(gè)函數(shù)體,在函數(shù)體外不可引用該變量:
'use strict';function foo() {var x = 1;x = x + 1; }x = x + 2; // ReferenceError! 無(wú)法在函數(shù)體外引用變量x如果兩個(gè)不同的函數(shù)各自聲明了同一個(gè)變量,那么該變量只在各自的函數(shù)體內(nèi)起作用。換句話說,不同函數(shù)內(nèi)部的同名變量互相獨(dú)立,互不影響:
'use strict';function foo() {var x = 1;x = x + 1; }function bar() {var x = 'A';x = x + 'B'; }由于 JavaScript 的函數(shù)可以嵌套,此時(shí),內(nèi)部函數(shù)可以訪問外部函數(shù)定義的變量,反過來則不行:
'use strict';function foo() {var x = 1;function bar() {var y = x + 1; // bar可以訪問foo的變量x!}var z = y + 1; // ReferenceError! foo不可以訪問bar的變量y! }如果內(nèi)部函數(shù)和外部函數(shù)的變量名重名怎么辦?來測(cè)試一下:
'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 的函數(shù)在查找變量時(shí)從自身函數(shù)定義開始,從“內(nèi)”向“外”查找。如果內(nèi)部函數(shù)定義了與外部函數(shù)重名的變量,則內(nèi)部函數(shù)的變量將“屏蔽”外部函數(shù)的變量。
變量提升
JavaScript的函數(shù)定義有個(gè)特點(diǎn),它會(huì)先掃描整個(gè)函數(shù)體的語(yǔ)句,把所有申明的變量“提升”到函數(shù)頂部:
'use strict';function foo() {var x = 'Hello, ' + y;console.log(x);var y = 'Bob'; }foo();雖然是strict模式,但語(yǔ)句var x = 'Hello, ' + y;并不報(bào)錯(cuò),原因是變量y在稍后申明了。但是console.log顯示Hello, undefined,說明變量y的值為undefined。這正是因?yàn)镴avaScript引擎自動(dòng)提升了變量y的聲明,但不會(huì)提升變量y的賦值。
對(duì)于上述foo()函數(shù),JavaScript引擎看到的代碼相當(dāng)于:
function foo() {var y; // 提升變量y的申明,此時(shí)y為undefinedvar x = 'Hello, ' + y;console.log(x);y = 'Bob'; }由于JavaScript的這一怪異的“特性”,我們?cè)诤瘮?shù)內(nèi)部定義變量時(shí),請(qǐng)嚴(yán)格遵守“在函數(shù)內(nèi)部首先申明所有變量”這一規(guī)則。最常見的做法是用一個(gè)var申明函數(shù)內(nèi)部用到的所有變量:
function foo() {varx = 1, // x初始化為1y = x + 1, // y初始化為2z, i; // z和i為undefined// 其他語(yǔ)句:for (i=0; i<100; i++) {...} }全局作用域
不在任何函數(shù)內(nèi)定義的變量就具有全局作用域。實(shí)際上,JavaScript 默認(rèn)有一個(gè)全局對(duì)象?window,全局作用域的變量實(shí)際上被綁定到?window?的一個(gè)屬性。 在 nodejs 中,是綁定到?global 這個(gè)變量中
'use strict';var course = 'Learn JavaScript'; alert(course); // 'Learn JavaScript' alert(window.course); // 'Learn JavaScript'因此,直接訪問全局變量course和訪問window.course是完全一樣的。
你可能猜到了,由于函數(shù)定義有兩種方式,以變量方式var foo = function () {}定義的函數(shù)實(shí)際上也是一個(gè)全局變量,因此,頂層函數(shù)的定義也被視為一個(gè)全局變量,并綁定到window對(duì)象:
'use strict';function foo() {alert('foo'); }foo(); // 直接調(diào)用foo() window.foo(); // 通過window.foo()調(diào)用進(jìn)一步大膽地猜測(cè),我們每次直接調(diào)用的alert()函數(shù)其實(shí)也是window的一個(gè)變量:
'use strict';window.alert('調(diào)用window.alert()'); // 把a(bǔ)lert保存到另一個(gè)變量: var old_alert = window.alert; // 給alert賦一個(gè)新函數(shù): window.alert = function () {} // alert('無(wú)法用alert()顯示了!');恢復(fù) alert?
// 恢復(fù)alert: window.alert = old_alert; alert('又可以用alert()了!');這說明JavaScript實(shí)際上只有一個(gè)全局作用域。任何變量(函數(shù)也視為變量),如果沒有在當(dāng)前函數(shù)作用域中找到,就會(huì)繼續(xù)往上查找,最后如果在全局作用域中也沒有找到,則報(bào)ReferenceError錯(cuò)誤。
名字空間
全局變量會(huì)綁定到window上,不同的JavaScript文件如果使用了相同的全局變量,或者定義了相同名字的頂層函數(shù),都會(huì)造成命名沖突,并且很難被發(fā)現(xiàn)。
減少?zèng)_突的一個(gè)方法是把自己的所有變量和函數(shù)全部綁定到一個(gè)全局變量中。例如:
// 唯一的全局變量MYAPP: var MYAPP = {};// 其他變量: MYAPP.name = 'myapp'; MYAPP.version = 1.0;// 其他函數(shù): MYAPP.foo = function () {return 'foo'; };把自己的代碼全部放入唯一的名字空間MYAPP中,會(huì)大大減少全局變量沖突的可能。
許多著名的JavaScript庫(kù)都是這么干的:jQuery,YUI,underscore等等。
局部作用域
由于JavaScript的變量作用域?qū)嶋H上是函數(shù)內(nèi)部,我們?cè)趂or循環(huán)等語(yǔ)句塊中是無(wú)法定義具有局部作用域的變量的:
'use strict';function foo() {for (var i=0; i<100; i++) {//}i += 100; // 仍然可以引用變量i }為了解決塊級(jí)作用域,ES6引入了新的關(guān)鍵字let,用let替代var可以申明一個(gè)塊級(jí)作用域的變量:
'use strict';function foo() {var sum = 0;for (let i=0; i<100; i++) {sum += i;}// SyntaxError:i += 1; }常量
由于var和let申明的是變量,如果要申明一個(gè)常量,在ES6之前是不行的,我們通常用全部大寫的變量來表示“這是一個(gè)常量,不要修改它的值”:
var PI = 3.14;ES6標(biāo)準(zhǔn)引入了新的關(guān)鍵字const來定義常量,const與let都具有塊級(jí)作用域:
'use strict';const PI = 3.14; PI = 3; // 某些瀏覽器不報(bào)錯(cuò),但是無(wú)效果! PI; // 3.14解構(gòu)賦值
從ES6開始,JavaScript引入了解構(gòu)賦值,可以同時(shí)對(duì)一組變量進(jìn)行賦值。
什么是解構(gòu)賦值?我們先看看傳統(tǒng)的做法,如何把一個(gè)數(shù)組的元素分別賦值給幾個(gè)變量:
var array = ['hello', 'JavaScript', 'ES6']; var x = array[0]; var y = array[1]; var z = array[2];現(xiàn)在,在ES6中,可以使用解構(gòu)賦值,直接對(duì)多個(gè)變量同時(shí)賦值:
'use strict'// 如果瀏覽器支持解構(gòu)賦值就不會(huì)報(bào)錯(cuò) // x, y, z分別被賦值為數(shù)組對(duì)應(yīng)元素: var [x, y, z] = ['hello', 'JavaScript', 'ES6']; console.log('x = ' + x + ', y = ' + y + ', z = ' + z);注意,對(duì)數(shù)組元素進(jìn)行解構(gòu)賦值時(shí),多個(gè)變量要用[...]括起來。
如果數(shù)組本身還有嵌套,也可以通過下面的形式進(jìn)行解構(gòu)賦值,注意嵌套層次和位置要保持一致:
let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']]; x; // 'hello' y; // 'JavaScript' z; // 'ES6'解構(gòu)賦值還可以忽略某些元素:
let [, , z] = ['hello', 'JavaScript', 'ES6']; // 忽略前兩個(gè)元素,只對(duì)z賦值第三個(gè)元素 z; // 'ES6'如果需要從一個(gè)對(duì)象中取出若干屬性,也可以使用解構(gòu)賦值,便于快速獲取對(duì)象的指定屬性:
'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分別被賦值為對(duì)應(yīng)屬性: console.log('name = ' + name + ', age = ' + age + ', passport = ' + passport);對(duì)一個(gè)對(duì)象進(jìn)行解構(gòu)賦值時(shí),同樣可以直接對(duì)嵌套的對(duì)象屬性進(jìn)行賦值,只要保證對(duì)應(yīng)的層次是一致的:
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, 因?yàn)閷傩悦莦ipcode而不是zip // 注意: address不是變量,而是為了讓city和zip獲得嵌套的address對(duì)象的屬性: address; // Uncaught ReferenceError: address is not defined使用解構(gòu)賦值對(duì)對(duì)象屬性進(jìn)行賦值時(shí),如果對(duì)應(yīng)的屬性不存在,變量將被賦值為undefined,這和引用一個(gè)不存在的屬性獲得undefined是一致的。如果要使用的變量名和屬性名不一致,可以用下面的語(yǔ)法獲取:
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解構(gòu)賦值還可以使用默認(rèn)值,這樣就避免了不存在的屬性返回undefined的問題:
var person = {name: '小明',age: 20,gender: 'male',passport: 'G-12345678' };// 如果person對(duì)象沒有single屬性,默認(rèn)賦值為true: var {name, single=true} = person; name; // '小明' single; // true有些時(shí)候,如果變量已經(jīng)被聲明了,再次賦值的時(shí)候,正確的寫法也會(huì)報(bào)語(yǔ)法錯(cuò)誤:
// 聲明變量: var x, y; // 解構(gòu)賦值: {x, y} = { name: '小明', x: 100, y: 200}; // 語(yǔ)法錯(cuò)誤: Uncaught SyntaxError: Unexpected token =這是因?yàn)镴avaScript引擎把{開頭的語(yǔ)句當(dāng)作了塊處理,于是=不再合法。解決方法是用小括號(hào)括起來:
({x, y} = { name: '小明', x: 100, y: 200});使用場(chǎng)景
解構(gòu)賦值在很多時(shí)候可以大大簡(jiǎn)化代碼。例如,交換兩個(gè)變量x和y的值,可以這么寫,不再需要臨時(shí)變量:
var x=1, y=2; [x, y] = [y, x]快速獲取當(dāng)前頁(yè)面的域名和路徑:
var {hostname:domain, pathname:path} = location;如果一個(gè)函數(shù)接收一個(gè)對(duì)象作為參數(shù),那么,可以使用解構(gòu)直接把對(duì)象的屬性綁定到變量中。例如,下面的函數(shù)可以快速創(chuàng)建一個(gè)Date對(duì)象:
function buildDate({year, month, day, hour=0, minute=0, second=0}) {return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second); }它的方便之處在于傳入的對(duì)象只需要year、month和day這三個(gè)屬性:
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)使用解構(gòu)賦值可以減少代碼量,但是,需要在支持ES6解構(gòu)賦值特性的現(xiàn)代瀏覽器中才能正常運(yùn)行。目前支持解構(gòu)賦值的瀏覽器包括Chrome,Firefox,Edge等。
補(bǔ)充幾個(gè)兩數(shù)交換的方法
// 方法 1 b=[a,a=b][0];// 方法 2 a=a+b-(b=a);// 方法 3 a=a^b; b=b^a; a=a^b;方法( 對(duì)象中的函數(shù)叫做方法 )
在一個(gè)對(duì)象中綁定函數(shù),稱為這個(gè)對(duì)象的方法。
在JavaScript中,對(duì)象的定義是這樣的:
var xiaoming = {name: '小明',birth: 1990 };但是,如果我們給xiaoming綁定一個(gè)函數(shù),就可以做更多的事情。比如,寫個(gè)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(); // 今年調(diào)用是25,明年調(diào)用就變成26了綁定到對(duì)象上的函數(shù)稱為方法,和普通函數(shù)也沒啥區(qū)別,但是它在內(nèi)部使用了一個(gè)this關(guān)鍵字,這個(gè)東東是什么?
在一個(gè)方法內(nèi)部,this是一個(gè)特殊變量,它始終指向當(dāng)前對(duì)象,也就是xiaoming這個(gè)變量。所以,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, 正常結(jié)果 getAge(); // NaN單獨(dú)調(diào)用函數(shù)getAge()怎么返回了NaN?請(qǐng)注意,我們已經(jīng)進(jìn)入到了JavaScript的一個(gè)大坑里。
JavaScript的函數(shù)內(nèi)部如果調(diào)用了this,那么這個(gè)this到底指向誰(shuí)?
答案是,視情況而定!
如果以對(duì)象的方法形式調(diào)用,比如xiaoming.age(),該函數(shù)的this指向被調(diào)用的對(duì)象,也就是xiaoming,這是符合我們預(yù)期的。
如果單獨(dú)調(diào)用函數(shù),比如getAge(),此時(shí),該函數(shù)的this指向全局對(duì)象,也就是window。
坑爹啊!
更坑爹的是,如果這么寫:
var fn = xiaoming.age; // 先拿到xiaoming的age函數(shù) fn(); // NaN也是不行的!要保證this指向正確,必須用obj.xxx()的形式調(diào)用!
由于這是一個(gè)巨大的設(shè)計(jì)錯(cuò)誤,要想糾正可沒那么簡(jiǎn)單。ECMA決定,在strict模式下讓函數(shù)的this指向undefined,因此,在strict模式下,你會(huì)得到一個(gè)錯(cuò)誤:
'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這個(gè)決定只是讓錯(cuò)誤及時(shí)暴露出來,并沒有解決this應(yīng)該指向的正確位置。
有些時(shí)候,喜歡重構(gòu)的你把方法重構(gòu)了一下:
'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結(jié)果又報(bào)錯(cuò)了!原因是this指針只在age方法的函數(shù)內(nèi)指向xiaoming,在函數(shù)內(nèi)部定義的函數(shù),this又指向undefined了!(在非strict模式下,它重新指向全局對(duì)象window!)
修復(fù)的辦法也不是沒有,我們用一個(gè)that變量首先捕獲this:
'use strict';var xiaoming = {name: '小明',birth: 1990,age: function () {var that = this; // 在方法內(nèi)部一開始就捕獲thisfunction getAgeFromBirth() {var y = new Date().getFullYear();return y - that.birth; // 用that而不是this}return getAgeFromBirth();} };xiaoming.age(); // 25用var that = this;,你就可以放心地在方法內(nèi)部定義其他函數(shù),而不是把所有語(yǔ)句都堆到一個(gè)方法中。
apply
雖然在一個(gè)獨(dú)立的函數(shù)調(diào)用中,根據(jù)是否是strict模式,this指向undefined或window,不過,我們還是可以控制this的指向的!
要指定函數(shù)的this指向哪個(gè)對(duì)象,可以用函數(shù)本身的apply方法,它接收兩個(gè)參數(shù),第一個(gè)參數(shù)就是需要綁定的this變量,第二個(gè)參數(shù)是Array,表示函數(shù)本身的參數(shù)。
用apply修復(fù)getAge()調(diào)用:
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, 參數(shù)為空另一個(gè)與apply()類似的方法是call(),唯一區(qū)別是:
-
apply()把參數(shù)打包成Array再傳入;
-
call()把參數(shù)按順序傳入。
比如調(diào)用Math.max(3, 5, 4),分別用apply()和call()實(shí)現(xiàn)如下:
Math.max.apply(null, [3, 5, 4]); // 5 Math.max.call(null, 3, 5, 4); // 5對(duì)普通函數(shù)調(diào)用,我們通常把this綁定為null。
裝飾器
利用apply(),我們還可以動(dòng)態(tài)改變函數(shù)的行為。
JavaScript的所有對(duì)象都是動(dòng)態(tài)的,即使內(nèi)置的函數(shù),我們也可以重新指向新的函數(shù)。
現(xiàn)在假定我們想統(tǒng)計(jì)一下代碼一共調(diào)用了多少次parseInt(),可以把所有的調(diào)用都找出來,然后手動(dòng)加上count += 1,不過這樣做太傻了。最佳方案是用我們自己的函數(shù)替換掉默認(rèn)的parseInt():
'use strict'var count = 0; var oldParseInt = parseInt; // 保存原函數(shù)var window = global window.parseInt = function () {count += 1;return oldParseInt.apply(null, arguments); // 調(diào)用原函數(shù) };// 測(cè)試: parseInt('10'); parseInt('20'); parseInt('30'); console.log('count = ' + count); // 3高階函數(shù)
:https://www.liaoxuefeng.com/wiki/1022910821149312/1023021271742944
高階函數(shù)英文叫Higher-order function。那么什么是高階函數(shù)?
JavaScript的函數(shù)其實(shí)都指向某個(gè)變量。既然變量可以指向函數(shù),函數(shù)的參數(shù)能接收變量,那么一個(gè)函數(shù)就可以接收另一個(gè)函數(shù)作為參數(shù),這種函數(shù)就稱之為高階函數(shù)。
一個(gè)最簡(jiǎn)單的高階函數(shù):
function add(x, y, f) {return f(x) + f(y); }當(dāng)我們調(diào)用add(-5, 6, Math.abs)時(shí),參數(shù)x,y和f分別接收-5,6和函數(shù)Math.abs,根據(jù)函數(shù)定義,我們可以推導(dǎo)計(jì)算過程為:
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中,我們調(diào)用Array的map()方法,傳入我們自己的函數(shù),就得到了一個(gè)新的Array作為結(jié)果:
'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()也接收一個(gè)函數(shù)。和map()不同的是,filter()把傳入的函數(shù)依次作用于每個(gè)元素,然后根據(jù)返回值是true還是false決定保留還是丟棄該元素。
例如,在一個(gè)Array中,刪掉偶數(shù),只保留奇數(shù),可以這么寫:
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]把一個(gè)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()這個(gè)高階函數(shù),關(guān)鍵在于正確實(shí)現(xiàn)一個(gè)“篩選”函數(shù)。
回調(diào)函數(shù)
filter()接收的回調(diào)函數(shù),其實(shí)可以有多個(gè)參數(shù)。通常我們僅使用第一個(gè)參數(shù),表示Array的某個(gè)元素。回調(diào)函數(shù)還可以接收另外兩個(gè)參數(shù),表示元素的位置和數(shù)組本身:
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的重復(fù)元素:
'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());去除重復(fù)元素依靠的是indexOf總是返回第一個(gè)元素的位置,后續(xù)的重復(fù)元素位置與indexOf返回的位置不相等,因此被filter濾掉了。
Array?對(duì)象的其他高階函數(shù)
對(duì)于數(shù)組,除了map()、reduce、filter()、sort()這些方法可以傳入一個(gè)函數(shù)外,Array對(duì)象還提供了很多非常實(shí)用的高階函數(shù)。
every
every()方法可以判斷數(shù)組的所有元素是否滿足測(cè)試條件。
例如,給定一個(gè)包含若干字符串的數(shù)組,判斷所有字符串是否滿足指定的測(cè)試條件:
'use strict'var arr = ['Apple', 'pear', 'orange']; console.log(arr.every(function (s) {return s.length > 0; })); // true, 因?yàn)槊總€(gè)元素都滿足s.length>0console.log(arr.every(function (s) {return s.toLowerCase() === s; })); // false, 因?yàn)椴皇敲總€(gè)元素都全部是小寫find
find()方法用于查找符合條件的第一個(gè)元素,如果找到了,返回這個(gè)元素,否則,返回undefined:
var arr = ['Apple', 'pear', 'orange']; console.log(arr.find(function (s) {return s.toLowerCase() === s; })); // 'pear', 因?yàn)閜ear全部是小寫console.log(arr.find(function (s) {return s.toUpperCase() === s; })); // undefined, 因?yàn)闆]有全部是大寫的元素findIndex
findIndex()和find()類似,也是查找符合條件的第一個(gè)元素,不同之處在于findIndex()會(huì)返回這個(gè)元素的索引,如果沒有找到,返回-1:
var arr = ['Apple', 'pear', 'orange']; console.log(arr.findIndex(function (s) {return s.toLowerCase() === s; })); // 1, 因?yàn)?#39;pear'的索引是1console.log(arr.findIndex(function (s) {return s.toUpperCase() === s; })); // -1forEach
forEach()和map()類似,它也把每個(gè)元素依次作用于傳入的函數(shù),但不會(huì)返回新的數(shù)組。forEach()常用于遍歷數(shù)組,因此,傳入的函數(shù)不需要返回值:
var arr = ['Apple', 'pear', 'orange']; arr.forEach(console.log); // 依次打印每個(gè)元素閉包
:https://www.liaoxuefeng.com/wiki/1022910821149312/1023021250770016
函數(shù)作為返回值:高階函數(shù)除了可以接受函數(shù)作為參數(shù)外,還可以把函數(shù)作為結(jié)果值返回。
返回閉包時(shí)牢記的一點(diǎn)就是:返回函數(shù)不要引用任何循環(huán)變量,或者后續(xù)會(huì)發(fā)生變化的變量。
箭頭函數(shù)
ES6 標(biāo)準(zhǔn)新增了一種新的函數(shù):Arrow Function(箭頭函數(shù))。
為什么叫Arrow Function?因?yàn)樗亩x用的就是一個(gè)箭頭:
x => x * x上面的箭頭函數(shù)相當(dāng)于:
function (x) {return x * x; }在繼續(xù)學(xué)習(xí)箭頭函數(shù)之前,請(qǐng)測(cè)試你的瀏覽器是否支持 ES 6的 Arrow Function:
var fn = x => x * x;箭頭函數(shù)相當(dāng)于匿名函數(shù),并且簡(jiǎn)化了函數(shù)定義。箭頭函數(shù)有兩種格式,
- 一種像上面的,只包含一個(gè)表達(dá)式,連{ ... }和return都省略掉了。
- 還有一種可以包含多條語(yǔ)句,這時(shí)候就不能省略{ ... }和return:
如果參數(shù)不是一個(gè),就需要用括號(hào)()括起來:
// 兩個(gè)參數(shù): (x, y) => x * x + y * y// 無(wú)參數(shù): () => 3.14// 可變參數(shù): (x, y, ...rest) => {var i, sum = x + y;for (i=0; i<rest.length; i++) {sum += rest[i];}return sum; }如果要返回一個(gè)對(duì)象,就要注意,如果是單表達(dá)式,這么寫的話會(huì)報(bào)錯(cuò):
// SyntaxError: x => { foo: x }因?yàn)楹秃瘮?shù)體的{ ... }有語(yǔ)法沖突,所以要改為:
// ok: x => ({ foo: x })this
箭頭函數(shù)看上去是匿名函數(shù)的一種簡(jiǎn)寫,但實(shí)際上,箭頭函數(shù)和匿名函數(shù)有個(gè)明顯的區(qū)別:箭頭函數(shù)內(nèi)部的this是詞法作用域,由上下文確定。
回顧前面的例子,由于JavaScript函數(shù)對(duì)this綁定的錯(cuò)誤處理,下面的例子無(wú)法得到預(yù)期結(jié)果:
var obj = {birth: 1990,getAge: function () {var b = this.birth; // 1990var fn = function () {return new Date().getFullYear() - this.birth; // this指向window或undefined};return fn();} };現(xiàn)在,箭頭函數(shù)完全修復(fù)了this的指向,this總是指向詞法作用域,也就是外層調(diào)用者obj:
var obj = {birth: 1990,getAge: function () {var b = this.birth; // 1990var fn = () => new Date().getFullYear() - this.birth; // this指向obj對(duì)象return fn();} }; obj.getAge(); // 25如果使用箭頭函數(shù),以前的那種hack寫法:
var that = this;就不再需要了。
由于this在箭頭函數(shù)中已經(jīng)按照詞法作用域綁定了,所以,用call()或者apply()調(diào)用箭頭函數(shù)時(shí),無(wú)法對(duì)this進(jìn)行綁定,即傳入的第一個(gè)參數(shù)被忽略:
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標(biāo)準(zhǔn)引入的新的數(shù)據(jù)類型。一個(gè)generator看上去像一個(gè)函數(shù),但可以返回多次。
ES6定義generator標(biāo)準(zhǔn)的哥們借鑒了Python的generator的概念和語(yǔ)法,如果你對(duì)Python的generator很熟悉,那么ES6的generator就是小菜一碟了。如果你對(duì)Python還不熟,趕快惡補(bǔ)Python教程!。
1.2?Javascript 定義 類(class)的幾種方法
在面向?qū)ο缶幊讨?#xff0c;類(class)是對(duì)象(object)的模板,定義了同一組對(duì)象(又稱"實(shí)例")共有的屬性和方法。類是對(duì)象的抽象,而對(duì)象是類的具體實(shí)例。類是抽象的,不占用內(nèi)存,而對(duì)象是具體的,占用存儲(chǔ)空間。
早期的javascript需求都很簡(jiǎn)單, 不支持面向?qū)ο?#xff0c;基本都是寫成函數(shù)的,然后是面向過程的寫法,后來慢慢的引入面向?qū)ο箝_發(fā)思想,再后來就慢慢寫成?類。
在 class 概念引入之前,js通過原型對(duì)象來實(shí)現(xiàn)類和類的繼承,具體可以參考前文(?面向?qū)ο蟮?JavaScript:封裝、繼承與多態(tài):https://zhuanlan.zhihu.com/p/112779427?)
在 ECMAScript 6 出現(xiàn) class 的概念之后,才算是告別了直接通過原型對(duì)象來模擬類和類繼承,但class 也只是基于 JavaScript 原型繼承的語(yǔ)法糖,并沒有引入新的對(duì)象繼承模式,所以理解原型以及原型繼承是非常重要的。通過 class 來創(chuàng)建對(duì)象,可以讓代碼更為簡(jiǎn)潔,復(fù)用性更高。
在js中,寫成類的本質(zhì)基本都是?構(gòu)造函數(shù)+原型。下面,就討論一下js類的幾種寫法:
構(gòu)造函數(shù) 法
這是經(jīng)典方法,也是教科書必教的方法。它用構(gòu)造函數(shù)模擬 "類",在其內(nèi)部用 this 關(guān)鍵字指代實(shí)例對(duì)象。
function Cat() {this.name = "大毛"; }生成實(shí)例的時(shí)候,使用 new 關(guān)鍵字。
var cat1 = new Cat(); alert(cat1.name); // 大毛類的屬性和方法,還可以定義在構(gòu)造函數(shù)的 prototype 對(duì)象之上。
Cat.prototype.makeSound = function(){alert("喵喵喵"); }關(guān)于這種方法的詳細(xì)介紹,查看系列文章《Javascript 面向?qū)ο缶幊獭?#xff0c;這里就不多說了。它的主要缺點(diǎn)是,比較復(fù)雜,用到了 this 和 prototype,編寫和閱讀都很費(fèi)力。
From:https://www.cnblogs.com/lidabo/archive/2011/12/17/2291238.html
一:定義類并創(chuàng)建類的實(shí)例對(duì)象
在Javascript中,我們用function來定義類,如下:
function Shape(){var x = 1 ;var y = 2 ; }你或許會(huì)說,疑?這個(gè)不是定義函數(shù)嗎?沒錯(cuò),這個(gè)是定義函數(shù),我們定義了一個(gè) Shape 函數(shù),并對(duì)x和y進(jìn)行了初始化。不過,如果你換個(gè)角度來看,這個(gè)就是定義一個(gè)Shape類,里面有兩個(gè)屬性x和y,初始值分別是1和2,只不過,我們定義類的關(guān)鍵字是 function 而不是 class。然后,我們可以創(chuàng)建Shape類的對(duì)象aShape,如下:
var?aShape?=?new?Shape();二:定義 公有屬性、私有屬性
我們已經(jīng)創(chuàng)建了aShape對(duì)象,但是,當(dāng)我們?cè)囍L問它的屬性時(shí),會(huì)出錯(cuò),如下:
aShape.x = 1 ;這說明,用 var 定義的屬性是私有的。我們需要使用 this 關(guān)鍵字來定義公有的屬性
function Shape(){this .x = 1 ;this .y = 2 ; }這樣,我們就可以訪問Shape的屬性了,如:aShape.x?=?2?;
總結(jié)得到:用 var 可以定義類的private屬性,而用 this 能定義類的 public 屬性。
三:定義 公有方法、私有方法
在Javascript中,函數(shù)是 Function 類的實(shí)例,Function 間接繼承自 Object,所以,函數(shù)也是一個(gè)對(duì)象,因此,我們可以用賦值的方法創(chuàng)建函數(shù),當(dāng)然,我們也可以將一個(gè)函數(shù)賦給類的一個(gè)屬性變量,那么,這個(gè)屬性變量就可以稱為方法,因?yàn)樗且粋€(gè)可以執(zhí)行的函數(shù)。代碼如下:
function Shape() {var x = 0 ;var y = 1 ;this.draw = function (){// print;}; }我們?cè)谏厦娴拇a中定義了一個(gè) draw,并把一個(gè) function 賦給它,下面,我們就可以通過 aShape 調(diào)用這個(gè)函數(shù),OOP 中稱為 公有方法,如:aShape.draw();
如果用 var 定義,那么這個(gè) draw 就變成私有的了,OOP 中稱為私有方法,如
function Shape() {var x = 0 ;var y = 1 ;var draw = function (){// print;}; }這樣就不能使用aShape.draw調(diào)用這個(gè)函數(shù)了。
三:構(gòu)造函數(shù)
Javascript 并不支持 OOP,當(dāng)然也就沒有構(gòu)造函數(shù)了,不過,我們可以自己模擬一個(gè)構(gòu)造函數(shù),讓對(duì)象被創(chuàng)建時(shí)自動(dòng)調(diào)用,代碼如下:
function Shape() {var init = function (){// 構(gòu)造函數(shù)代碼};init(); }在Shape的最后,我們?nèi)藶榈恼{(diào)用了init函數(shù),那么,在創(chuàng)建了一個(gè)Shape對(duì)象是,init總會(huì)被自動(dòng)調(diào)用,可以模擬我們的構(gòu)造函數(shù)了。
四:帶參數(shù)的構(gòu)造函數(shù)
如何讓構(gòu)造函數(shù)帶參數(shù)呢?其實(shí)很簡(jiǎn)單,將要傳入的參數(shù)寫入函數(shù)的參數(shù)列表中即可,如
function Shape(ax,ay) {var x = 0 ;var y = 0 ;var init = function (){// 構(gòu)造函數(shù)x = ax;y = ay;};init(); }這樣,我們就可以這樣創(chuàng)建對(duì)象:var?aShape?=?new?Shape(?0?,?1?);
五:靜態(tài)屬性、靜態(tài)方法
在 Javascript 中如何定義靜態(tài)的屬性和方法呢?如下所示
function Shape(ax,ay){var x = 0 ;var y = 0 ;var init = function (){// 構(gòu)造函數(shù)x = ax;y = ay;};init(); } Shape.count = 0 ; // 定義一個(gè)靜態(tài)屬性count,這個(gè)屬性是屬于類的,不是屬于對(duì)象的。 Shape.staticMethod = function (){}; // 定義一個(gè)靜態(tài)的方法有了靜態(tài)屬性和方法,我們就可以用類名來訪問它了,如下
alert(aShape.count); aShape.staticMethod();注意:靜態(tài)屬性和方法都是公有的,目前為止,我不知道如何讓靜態(tài)屬性和方法變成私有的~
六:在方法中訪問本類的公有屬性和私有屬性
在類的方法中訪問自己的屬性,Javascript對(duì)于公有屬性和私有屬性的訪問方法有所不同,請(qǐng)大家看下面的代碼
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 的注意事項(xiàng)
在 JavaScript 中,類中的 this 并不是一直指向我們的這個(gè)對(duì)象本身的,主要原因還是因?yàn)镴avascript 并不是 OOP 語(yǔ)言,而且,函數(shù) 和 類 均用 function 定義,當(dāng)然會(huì)引起一些小問題。
this 指針指錯(cuò)的場(chǎng)合一般在事件處理上面,我們想讓某個(gè)對(duì)象的成員函數(shù)來響應(yīng)某個(gè)事件,當(dāng)事件被觸發(fā)以后,系統(tǒng)會(huì)調(diào)用我們這個(gè)成員函數(shù),但是,傳入的 this 指針已經(jīng)不是我們本身的對(duì)象了,當(dāng)然,這時(shí)再在成員函數(shù)中調(diào)用this當(dāng)然會(huì)出錯(cuò)了。
解決方法是我們?cè)诙x類的一開始就將this保存到一個(gè)私有的屬性中,以后,我們可以用這個(gè)屬性代替this。我用這個(gè)方法使用this指針相當(dāng)安全,而且很是省心~
我們修改一下代碼,解決this問題。對(duì)照第六部分的代碼看,你一定就明白了
示例:
/***定義類***/ var Class = function(){var _self = this;//把本身引用負(fù)值到一變量上var _Field = "Test Field"; //私有字段var privateMethod = function(){ //私有方法alert(_self.Property); //調(diào)用屬性}this.Property = "Test Property"; //公有屬性this.Method = function(){ //公有方法alert(_Field); //調(diào)用私用字段privateMethod(); //調(diào)用私用方法}/***構(gòu)造函數(shù)***/var init = function(){privateMethod();}init(); }// 使用這個(gè)類 var c = new Class(); c.Method(); // 使用方法關(guān)于 Javascript 中的 OOP 實(shí)現(xiàn)就聊到這里,以上是最實(shí)用的內(nèi)容,一般用 Javascript 定義類,創(chuàng)建對(duì)象用以上的代碼已經(jīng)足夠了。當(dāng)然,你還可以用 mootools 或 prototype 來定義類,創(chuàng)建對(duì)象。我用過mootools框架,感覺很不錯(cuò),它對(duì) Javascript 的類模擬就更完善了,還支持類的繼承,有興趣的讀者可以去嘗試一下。當(dāng)然,如果使用了框架,那么在你的網(wǎng)頁(yè)中就需要包含相關(guān)的js頭文件,因此我還是希望讀者能夠在沒有框架的情況下創(chuàng)建類,這樣,代碼效率較高,而且你也可以看到,要?jiǎng)?chuàng)建一個(gè)簡(jiǎn)單的類并不麻煩~
示例 1:
/*** 封裝類方法* @methodOf Clazz.prototype*/ var Clazz = function() {}; /*** [給基類的原型賦值一個(gè)方法 當(dāng)作類的構(gòu)造器]* @return {[Object]} [description]*/ Clazz.prototype.construct = function() {}; /*** 創(chuàng)建類* @example* var MyClass = Clazz.extend({* //構(gòu)造器,new時(shí)執(zhí)行* construct: function(myParam){* // 編寫你的代碼邏輯* }* });** 繼承類* var MySubClass = MyClass.extend({* construct: function(myParam){* // 使用這個(gè)來調(diào)用父類的構(gòu)造函數(shù)* 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;//給這個(gè)新類相同的靜態(tài)擴(kuò)展方法classDef.extend = this.extend;return classDef; };//========使用實(shí)例========= 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(){//未調(diào)用父類的構(gòu)造函數(shù)this.name = 'SubClass ';} }); //繼承MyClass 類 var SubClass2 = MyClass .extend({construct: function(){//調(diào)用父類構(gòu)造函數(shù)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類:定義一個(gè)人,有name屬性和getName方法 */ <script>function Person(name){this.name = name;this.getName = function(){return this.name;}}//我們?cè)谶@里實(shí)例化幾個(gè)對(duì)象var p1 = new Person("trigkit4");var p2 = new Person("mike");console.log(p1 instanceof Person);//trueconsole.log(p2 instanceof Person);//true </script>由上面控制臺(tái)輸出結(jié)果可知,p1和p2的確是類Person的實(shí)例對(duì)象。instanceof操作符左邊是待檢測(cè)類的對(duì)象,右邊是定義類的構(gòu)造函數(shù)。這里,instanceof用來檢測(cè)對(duì)象p1是否屬于Person類。
這種方式的優(yōu)點(diǎn)是:我們可以根據(jù)參數(shù)來構(gòu)造不同的對(duì)象實(shí)例 ,缺點(diǎn)是每次構(gòu)造實(shí)例對(duì)象時(shí)都會(huì)生成getName方法,造成了內(nèi)存的浪費(fèi) 。
我們可以用一個(gè)外部函數(shù)來代替類方法,達(dá)到了每個(gè)對(duì)象共享同一個(gè)方法。改寫后的類如下:
//外部函數(shù) <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>原型方式:
- 缺點(diǎn) 就是不能通過參數(shù)來構(gòu)造對(duì)象實(shí)例 (一般每個(gè)對(duì)象的屬性是不相同的) ,
- 優(yōu)點(diǎn) 是所有對(duì)象實(shí)例都共享getName方法(相對(duì)于構(gòu)造函數(shù)方式),沒有造成內(nèi)存浪費(fèi) 。
構(gòu)造函數(shù) + 原型方式
取前面兩種的優(yōu)點(diǎn):
- a、用構(gòu)造函數(shù)來定義類屬性(字段)。
- b、用原型方式來定義類的方法。
這樣,我們就既可以構(gòu)造不同屬性的對(duì)象,也可以讓對(duì)象實(shí)例共享方法,不會(huì)造成內(nèi)存的浪費(fèi)。為了讓js代碼風(fēng)格更緊湊,我們讓prototype方法代碼移到?function Person?的大括號(hào)內(nèi)。
<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 */ // 定義一個(gè)構(gòu)造函數(shù) function Range(from, to){this.from = from; this.to = to; } // 所有Range類的實(shí)例化對(duì)象都會(huì)繼承構(gòu)造函數(shù)Range的prototype屬性 Range.prototype = {toString: function(){return this.from + '....' + this.to; }, includes: function(x){return x >= this.from && x <= this.to;} };// 實(shí)例化一個(gè)對(duì)象 var r = new Range(1, 3); // 因?yàn)?r 繼承了Range.prototype, 所以可以直接調(diào)用里面的方法 r.toString()?由 例1 和 例2 可以總結(jié)出javascript中定義類的步驟:
- 第一步:先定義一個(gè)構(gòu)造函數(shù),并設(shè)置初始化新對(duì)象的實(shí)例屬性
- 第二步:給構(gòu)造函數(shù)的prototype對(duì)象定義實(shí)例方法
- 第三步:給構(gòu)造函數(shù)定義類字段和類屬性?
繼承
?新語(yǔ)法定義類,以及繼承類
Object.create() 法
為了解決 "構(gòu)造函數(shù)法" 的缺點(diǎn),更方便地生成對(duì)象,Javascript的國(guó)際標(biāo)準(zhǔn)?ECMAScript?第五版(目前通行的是第三版),提出了一個(gè)新的方法?Object.create()。
用這個(gè)方法,"類" 就是一個(gè) 對(duì)象,不是 函數(shù)。
var Cat = {name: "大毛",makeSound: function(){ alert("喵喵喵"); } };然后,直接用 Object.create() 生成實(shí)例,不需要用到 new。
var cat1 = Object.create(Cat); alert(cat1.name); // 大毛 cat1.makeSound(); // 喵喵喵目前,各大瀏覽器的最新版本(包括IE9)都部署了這個(gè)方法。如果遇到老式瀏覽器,可以用下面的代碼自行部署。
if (!Object.create) {Object.create = function (o) {function F() {}F.prototype = o;return new F();}; }這種方法比 "構(gòu)造函數(shù)法" 簡(jiǎn)單,但是不能實(shí)現(xiàn)私有屬性和私有方法,實(shí)例對(duì)象之間也不能共享數(shù)據(jù),對(duì)"類"的模擬不夠全面。
極簡(jiǎn)主義法 ( 推薦的方法?)
荷蘭程序員 Gabor de Mooij提出了一種比 Object.create() 更好的新方法,他稱這種方法為"極簡(jiǎn)主義法"(minimalist approach)。也是推薦的方法。
這種方法不使用 this 和 prototype,代碼部署起來非常簡(jiǎn)單,這大概也是它被叫做 "極簡(jiǎn)主義法" 的原因。首先,它也是用一個(gè)對(duì)象模擬 "類"。在這個(gè)類里面,定義一個(gè)構(gòu)造函數(shù) createNew(),用來生成實(shí)例。
var Cat = {createNew: function(){// some code here} };然后,在 createNew() 里面,定義一個(gè)實(shí)例對(duì)象,把這個(gè)實(shí)例對(duì)象作為返回值。
var Cat = {createNew: function(){var cat = {};cat.name = "大毛";cat.makeSound = function(){ alert("喵喵喵"); };return cat;} };使用的時(shí)候,調(diào)用 createNew() 方法,就可以得到實(shí)例對(duì)象。
var cat1 = Cat.createNew(); cat1.makeSound(); // 喵喵喵這種方法的好處是,容易理解,結(jié)構(gòu)清晰優(yōu)雅,符合傳統(tǒng)的"面向?qū)ο缶幊?#34;的構(gòu)造,因此可以方便地部署下面的特性。
繼承
讓一個(gè)類繼承另一個(gè)類,實(shí)現(xiàn)起來很方便。只要在前者的 createNew() 方法中,調(diào)用后者的createNew() 方法即可。
先定義一個(gè) Animal 類。
var Animal = {createNew: function(){var animal = {};animal.sleep = function(){ alert("睡懶覺"); };return animal;} };然后,在 Cat 的 createNew() 方法中,調(diào)用 Animal 的 createNew() 方法。
var Cat = {createNew: function(){var cat = Animal.createNew();cat.name = "大毛";cat.makeSound = function(){ alert("喵喵喵"); };return cat;} };這樣得到的 Cat 實(shí)例,就會(huì)同時(shí)繼承 Cat類 和 Animal類。
var cat1 = Cat.createNew(); cat1.sleep(); // 睡懶覺私有屬性 和 私有方法
在 createNew() 方法中,只要不是定義在 cat 對(duì)象上的方法和屬性,都是私有的。
var Cat = {createNew: function(){var cat = {};var sound = "喵喵喵";cat.makeSound = function(){ alert(sound); };return cat;} };上例的內(nèi)部變量 sound,外部無(wú)法讀取,只有通過 cat 的公有方法 makeSound() 來讀取。
var cat1 = Cat.createNew(); alert(cat1.sound); // undefined數(shù)據(jù)共享
有時(shí)候,我們需要所有實(shí)例對(duì)象,能夠讀寫同一項(xiàng)內(nèi)部數(shù)據(jù)。這個(gè)時(shí)候,只要把這個(gè)內(nèi)部數(shù)據(jù),封裝在類對(duì)象的里面、createNew()方法的外面即可。
var Cat = {sound : "喵喵喵",createNew: function(){var cat = {};cat.makeSound = function(){ alert(Cat.sound); };cat.changeSound = function(x){ Cat.sound = x; };return cat;} };然后,生成兩個(gè)實(shí)例對(duì)象:
var cat1 = Cat.createNew(); var cat2 = Cat.createNew(); cat1.makeSound(); // 喵喵喵這時(shí),如果有一個(gè)實(shí)例對(duì)象,修改了共享的數(shù)據(jù),另一個(gè)實(shí)例對(duì)象也會(huì)受到影響。
cat2.changeSound("啦啦啦"); cat1.makeSound(); // 啦啦啦使用關(guān)鍵字 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
以前都是通過構(gòu)造函數(shù) function 和 原型prototype 來實(shí)現(xiàn)類的效果,在ES6中新增了 class 關(guān)鍵字用來定義類,使用 class 關(guān)鍵字定義類的寫法更加清晰,更像面向?qū)ο蟮恼Z(yǔ)法。但是可以看作是語(yǔ)法糖,因?yàn)樗€是構(gòu)造函數(shù)和原型的概念。
類聲明
定義類有2中方式,類聲明 和 類表達(dá)式:
// 類聲明 class Student {} // 類表達(dá)式 const Student = class {}ECMAScript 6 中定義一個(gè)類示例代碼:
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 方法用來創(chuàng)建和初始化對(duì)象,而且一個(gè)類中有且只能有一個(gè)consctuctor方法,默認(rèn)為constructor(){}。dog 就是Animal實(shí)例化的對(duì)象。
ES5 中創(chuàng)建類
在前面說到,class 只是基于現(xiàn)有的 JavaSript 實(shí)現(xiàn)類的語(yǔ)法糖,那先讓我們簡(jiǎn)單使用 ES5 中的方法來模擬類,并實(shí)例化。
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() 方法就相當(dāng)于 Animal() 構(gòu)造函數(shù),而在 class 中定義屬性就相當(dāng)于直接在原型對(duì)象上定義屬性。我們不妨這樣試一試:
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 還是依靠原型對(duì)象來實(shí)現(xiàn)類的。
為什么說它是語(yǔ)法糖
因?yàn)轭悓?shí)際上它是一個(gè) function,區(qū)別在于構(gòu)造函數(shù)是函數(shù)作用域,類是塊級(jí)作用域,類中的方法,都是定義在類的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類包含的屬性和方法
類可以包含?構(gòu)造函數(shù)方法、實(shí)例方法、獲取函數(shù)、設(shè)置函數(shù)和靜態(tài)類方法,但這些都不是必需的。空的類定義照樣有效。
class Student {// 實(shí)例屬性 也可以放在這// b = 1// 靜態(tài)屬性static a = 1// 構(gòu)造函數(shù)constructor() {// 實(shí)例屬性 - 也可以放在類的最頂層this.b = 1}// 獲取函數(shù)get myName() {}// 設(shè)置函數(shù)set myName() {}// 靜態(tài)方法 不會(huì)被實(shí)例繼承static show() {}// 方法take() {}// 私有方法_take() {} }實(shí)例屬性必須定義在類方法中:
class Animal {constructor(name) {this.name = name; // 實(shí)例屬性} }靜態(tài)屬性和原型屬性避必須在類的外面定義:
Animal.age = 18; // “靜態(tài)屬性” Animal.prototype.sex = 'male'; // 原型屬性實(shí)例屬性顧名思義,就是對(duì)象獨(dú)有的方法/屬性,靜態(tài)屬性就是位于Class本身的屬性,但是ES6中明確說明Class只有靜態(tài)方法,沒有靜態(tài)屬性,原型屬性也很容易理解也很容易看出,就是位于原型鏈上的屬性/方法。
類的構(gòu)造函數(shù)
類的構(gòu)造函數(shù)關(guān)鍵字是 constructor,它同等于原型中的 prototype.constructor。
如果沒有寫 constructor 函數(shù),那么會(huì)默認(rèn)有一個(gè)空的 constructor 函數(shù)。
當(dāng)使用 new 操作符創(chuàng)建實(shí)例時(shí),會(huì)調(diào)用 constructor 構(gòu)造函數(shù)。
類的方法
class Student {// 方法take() {} }類的靜態(tài)方法
跟類的方法一樣,只不過前面加上static關(guān)鍵字。
靜態(tài)方法不會(huì)被實(shí)例繼承。
父類的靜態(tài)方法可以被子類繼承。
所有在類中定義的方法會(huì)被實(shí)例繼承,但是有時(shí)候我們并不想所有實(shí)例都能繼承某個(gè)方法,這時(shí)候,static關(guān)鍵字就能達(dá)到你的目的,在聲明方法前加上static關(guān)鍵字,這個(gè)方法就不會(huì)被實(shí)例繼承,而是直接通過類來調(diào)用,它被叫做靜態(tài)方法,如下:
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(); // 喵喵喵靜態(tài)方法雖然不能被實(shí)例繼承,但是可以被子類繼承,但是子類的實(shí)例依舊沒有繼承它:
class Animal {static bark() {console.log('喵喵喵');} } class Dog extends Animal{} Dog.bark(); // 喵喵喵 let dog = new Dog(); dog.bark(); // TypeError類的私有方法
es6中沒有提供這個(gè)方法,但是通常都是在方法前面加上下劃線來表示。
class A {// 私有方法_show() {console.log('hi')} }取值函數(shù)(getter)和存值函數(shù)(setter)
在類中有 set 和 get 關(guān)鍵詞,可以對(duì)某個(gè)屬性設(shè)置存值和取值函數(shù),攔截它的存取行為。
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 關(guān)鍵字來創(chuàng)建子類
class Animal {constructor(name) {this.name = name;}sayHi() {console.log(`hello, ${this.name}`);} }class Dog extends Animal {bark() {console.log(`喵喵喵`);} }let wangcai = new Dog('旺財(cái)'); wangcai.bark(); // 喵喵喵 wangcai.sayHi(); // hello, 旺財(cái)但如果在子類中定義了 constructor 方法,必須先調(diào)用 super() 才能使用 this,因?yàn)樽宇惒]有 this對(duì)象,而是繼承父類的 this 對(duì)象,所以 super 必須在使用 this 關(guān)鍵字之前使用:
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('旺財(cái)', '喵喵喵'); wangcai.bark(); // referenceErrorclass Dog extends Animal {constructor(name, sound) {super(name);this.sound = sound;};bark() {console.log(this.sound);} } let wangcai = new Dog('旺財(cái)', '喵喵喵'); wangcai.bark(); // 喵喵喵super 方法
注意如果子類如果沒寫constructor構(gòu)造函數(shù),則會(huì)默認(rèn)有constructor構(gòu)造函數(shù)和super方法,但是如果顯性的寫了constructor構(gòu)造函數(shù),那么必須在子類的構(gòu)造函數(shù)中添加super方法,添加之后會(huì)調(diào)用父類的構(gòu)造函數(shù)并得到父類的屬性和方法,如果沒有添加super方法則會(huì)報(bào)ReferenceError錯(cuò)誤。
super 不僅可以調(diào)用父類的 constructor 函數(shù),還可以調(diào)用父類上的方法:
class Animal {constructor(name) {this.name = name;}sayHi() {console.log(`hello, ${this.name}`);} }class Dog extends Animal {bark() {super.sayHi();} }let wangcai = new Dog('旺財(cái)'); wangcai.bark(); // hello, 旺財(cái)示例:
class A {constructor () {this.name = '小米'}show() {console.log('hi')} } class B extends A {constructor () {super() // 如果不寫super,則會(huì)報(bào)ReferenceError錯(cuò)誤} } 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,那么它指向的是類的實(shí)例。但是如果將它單獨(dú)拿出來使用那么會(huì)報(bào)錯(cuò)。
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種:
- 在構(gòu)造函數(shù)中綁定 this
- 使用箭頭函數(shù)
區(qū)分是否繼承了這個(gè)類
區(qū)分是否繼承了這個(gè)類使用Object.getPrototypeOf函數(shù)。
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 對(duì)象
From:https://www.jb51.net/article/80177.htm
JS 中的 所有對(duì)象 都是 繼承自 Object對(duì)象
創(chuàng)建 對(duì)象
"對(duì)象" 是一組相似數(shù)據(jù)和功能的集合,用它可以來模擬現(xiàn)實(shí)世界中的任何東西。
在 Javascript 中,創(chuàng)建對(duì)象的方式通常有兩種方式:
- 構(gòu)造函數(shù)。這種方式使用 new 關(guān)鍵字,接著跟上 Object 構(gòu)造函數(shù),再來給對(duì)象實(shí)例動(dòng)態(tài)添加上不同的屬性。這種方式相對(duì)來說比較繁瑣,一般推薦使用對(duì)象字面量來創(chuàng)建對(duì)象。 var person = new Object(); person.name = "狼狼的藍(lán)胖子"; person.age = 25;
- 對(duì)象字面量。對(duì)象字面量很好理解,使用 key/value 的形式直接創(chuàng)建對(duì)象,通過花括號(hào)將對(duì)象的屬性包起來,對(duì)象的每個(gè)屬性之間用逗號(hào)隔開。注意:如果是最后一個(gè)屬性,后面就不要加逗號(hào),因?yàn)樵谝恍┡f的瀏覽器下會(huì)報(bào)錯(cuò)。 var person = {name: "狼狼的藍(lán)胖子",age: 25 };
注意:
obj_1 = {name: 'obj_1' }const {name} = obj_1; // 相當(dāng)于 name = obj_1.name console.log(name) // obj console.log(name === obj_1.name) // true構(gòu)造函數(shù) 和?對(duì)象字面量?這兩種方法有一個(gè)缺點(diǎn)就是:如果要?jiǎng)?chuàng)建多個(gè)對(duì)象,寫起來很繁瑣,所以后來就有了一種創(chuàng)建自定義構(gòu)造函數(shù)的方法來創(chuàng)建對(duì)象,如下所示:
function Person(name, age) {this.name = name;this.age = age; } var person = new Person("Jack", 15);這種方式可以很方便的創(chuàng)建多個(gè)同樣的對(duì)象,也是目前比較常用的方法。
javascript 中 function(){}(), new function(), new Function(), Function
From:https://www.cnblogs.com/pizitai/p/6427433.html
javascript 中的類的構(gòu)造:
javascript 中有對(duì)象的概念,卻沒有類的概念。
- 類?是一種抽象的概念,例如:動(dòng)物、植物;
- 對(duì)象?則是指這種概念中的實(shí)體,比如 "中國(guó)人、美國(guó)人、楊樹、柳樹";
- 實(shí)例化 就是指以類為基礎(chǔ)構(gòu)建一個(gè)實(shí)體。
類所擁有的特征,其實(shí)例化對(duì)象,也一定擁有這些特征,而且實(shí)例化后可能擁有更多特征。
javascript 在用到對(duì)象時(shí),完全沒有類的概念,但是編程的世界里,無(wú)奇不有,可以通過 function 構(gòu)造出一種假想的類,從而實(shí)現(xiàn) javascript 中類的構(gòu)造。比如,我們通過下面的方法來構(gòu)造一個(gè)類:
//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(){}() 讓變量快速初始化結(jié)果
在《javascript立即執(zhí)行某個(gè)函數(shù):插件中function(){}()再思考》一文中,我詳細(xì)闡述了 function(){}() 的作用及理解思路。這里不再贅述,現(xiàn)在,我們面臨的新問題是,知道了它的作用,我們?nèi)绾问褂盟?#xff1f;讓我們來看一段代碼:
var timestamp = function(){var timestamp = Date.parse(new Date());return timestamp/1000; }();當(dāng)我們要使用一個(gè)變量時(shí),我們希望這個(gè)變量在一個(gè)環(huán)節(jié)完成我們的賦值,使用上面的這種方法,可以減少代碼上下文執(zhí)行邏輯,如果按照我們以前的方法,代碼可能會(huì)寫成:
var timestamp = Date.parse(new Data()); timestamp = timestamp/1000;看上去好像比上面的操作簡(jiǎn)潔多了,只需要兩行代碼。但是我們仔細(xì)去觀察,就會(huì)發(fā)現(xiàn)第一段代碼其實(shí)本身僅是一個(gè)賦值操作,在 function 中完成的所有動(dòng)作將會(huì)在function執(zhí)行完后全部釋放,整個(gè)代碼看上去好像只執(zhí)行了一條語(yǔ)句一樣。
而實(shí)際上更重要的意義在于它可以讓一個(gè)變量在初始化時(shí),就具備了運(yùn)算結(jié)果的效果。
使用 new function 初始化一個(gè)可操作對(duì)象
上面講了 javascript 中的類,而使用 new function 可以實(shí)例化這個(gè)類。但是我們實(shí)際上有的時(shí)候在為一個(gè)變量賦值的時(shí)候,希望直接將它初始化為一個(gè)可操作的對(duì)象,比如像這樣:
// 這里的數(shù)據(jù)庫(kù)操作是我虛擬出來的一種數(shù)據(jù)庫(kù)操作形式 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);} };當(dāng)我們要對(duì)數(shù)據(jù)庫(kù) database 進(jìn)行查詢時(shí),只需要通過?var list = $db.select('table','1=1');
進(jìn)行操作即可,數(shù)據(jù)庫(kù)的初始化結(jié)果已經(jīng)在$db這個(gè)變量中了。
Function 是由 function 關(guān)鍵字定義的 函數(shù)對(duì)象的原型
在 javascript 中,多出了一個(gè)原型的概念。所謂原型,其實(shí)就是一個(gè)對(duì)象的本質(zhì)( 可以理解成 基類?),但復(fù)雜就復(fù)雜在,原型本身也是對(duì)象,因此,任何一個(gè)對(duì)象又可以作為其他對(duì)象的原型。Function 就相當(dāng)于一個(gè)系統(tǒng)原型,可以把它理解為一種 "基本對(duì)象類型",是 "對(duì)象"?這個(gè)概念范疇類的基本數(shù)據(jù)類型。
除了 Function 之外,其實(shí)還有很多類似的首字母大寫的對(duì)象原型,例如 Object, Array, Image 等等。有一種說法是:javascript 中所有的一切都是對(duì)象(除了基本數(shù)據(jù)類型,其他的一切全是對(duì)象),所有的對(duì)象都是 Object 衍生出來的。(按照這種說法,我們應(yīng)該返回去再思考,上面說的類的假設(shè)是否成立。)
極其重要的 prototype 概念
prototype 的概念在 javascript 中極其重要,它是 javascript 中完成上面說的 "一切皆對(duì)象"?的關(guān)鍵。有了prototype,才有了原型,有了原型,才有了 javascript 五彩繽紛的世界(當(dāng)然,也有人說是雜亂的)。我們可以這樣去理解 prototype:世界上本沒有 javascript,上帝說要有Object,于是有了 Object,可是要有 Function 怎么辦?只需要對(duì) Object 進(jìn)行擴(kuò)展,可是如何擴(kuò)展?只需要用prototype……當(dāng)然,這是亂扯的,不過在 javascript 中,只要是 function,就一定會(huì)有一個(gè)prototype 屬性。實(shí)際上確實(shí)是這樣
Function.prototype.show = function() {...}在原型的基礎(chǔ)上通過prototype新增屬性或方法,則以該對(duì)象為原型的實(shí)例化對(duì)象中,必然存在新增的屬性或方法,而且它的內(nèi)容是靜態(tài)不可重載的。原型之所以被稱為原型,可能正是因?yàn)檫@種不可重載的特質(zhì)。
比如上面的這段代碼,會(huì)導(dǎo)致每一個(gè)實(shí)例化的function,都會(huì)具備一個(gè)show方法。而如果我們自己創(chuàng)建了一個(gè)類,則可以通過 prototype 將之轉(zhuǎn)化為原型:
function Cat() {...} Cat.prototype.run = function() {}; var cat1 = new Cat();這時(shí),對(duì)于 cat1 而言,Cat 就是原型,而該原型擁有一個(gè) run 的原始方法,所以無(wú)論實(shí)例化多少個(gè) Cat,每一個(gè)實(shí)例化對(duì)象都有 run 方法,而且該方法是不能被重載的,通過 cat1.run = function(){} 是無(wú)效的。
為了和其他語(yǔ)言的類的定義方法統(tǒng)一,我們可以將這種原型屬性在定義類的時(shí)候,寫在類的構(gòu)造里面:
function Cat() {....Cat.prototype.run = function() {}; }new Function() 是函數(shù)原型的一個(gè)實(shí)例化
在理解了 Function 原型的概念之后,再來看 new Function()就顯得很容易了。首先來看下我們是怎么使用這種奇特的寫法的:
var message = new Function('msg','alert(msg)'); // 等價(jià)于: function message(msg) {alert(msg); }new Function(參數(shù)1,參數(shù)2,…,參數(shù)n,函數(shù)體),它的本意其實(shí)是通過實(shí)例化一個(gè)Function原型,得到一個(gè)數(shù)據(jù)類型為function的對(duì)象,也就是一個(gè)函數(shù),而該變量就是函數(shù)名。
this 在這類 function 中的指向
this 在 javascript 中真的是無(wú)法讓我們捉摸透徹。但是有一個(gè)小竅門,就是:一般情況下,this 指向的是當(dāng)前實(shí)例化對(duì)象,如果沒有找到該對(duì)象,則是指向 window。從使用上來講,我們應(yīng)該排除new Function 的討論,因?yàn)樗臀覀兂S玫暮瘮?shù)聲明是一致的。
- 普通的函數(shù)中this的指向:函數(shù)聲明的時(shí)候,如果使用了this,那么就要看是把該函數(shù)當(dāng)做一個(gè)對(duì)象加以返回,還是以僅執(zhí)行函數(shù)體。普通函數(shù)執(zhí)行時(shí),我們完全沒有引入對(duì)象、類這些概念,因此,this 指向 window。通過代碼來看下: var msg;
function message(msg) {this.msg = msg;
}
message('ok');
alert(msg);
首先是聲明一個(gè)函數(shù)message,在函數(shù)中this.msg實(shí)際上就是window.msg,也實(shí)際上就是代碼開頭的msg。因此,當(dāng)執(zhí)行完message(‘ok’)的時(shí)候,開頭的全局變量msg也被賦值為ok。
-
通過 function 構(gòu)造類時(shí) this 的指向:如果function被構(gòu)造為一個(gè)類,那么必然存在該類被實(shí)例化的一個(gè)過程,如果沒有實(shí)例化,那么該類實(shí)際上并沒有在程序中被使用。而一旦實(shí)例化,那么this將指向?qū)嵗膶?duì)象。
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'));上面代碼中標(biāo)記了4處紅色的this的使用。根據(jù)我們的原則,this指向?qū)嵗瘜?duì)象,我們來對(duì)每一個(gè) this 進(jìn)行分解。
首先是 cat1.weight,我使用了 function(){}(),直接利用貓咪的年齡進(jìn)行計(jì)算得出體重返回給weight屬性。
第一個(gè) this.age 出現(xiàn)在function(){}(this.age),這個(gè)this.age實(shí)際上是一個(gè)傳值過程,如果你對(duì)我之前分析function(){}()比較了解的話,應(yīng)該知道,this.age實(shí)際上是和前面this.age = 2指同一個(gè),這里的this.age的this,首先要去找它所在的function,然后看這個(gè)function是否被實(shí)例化,最后確認(rèn),確實(shí)被實(shí)例化為cat1,因此this=cat1。
第二個(gè) this.age 出現(xiàn)在function(){this.age}()。同樣,你先需要對(duì)function(){}()再次深入了解,實(shí)際上,function(){}()就是執(zhí)行一個(gè)函數(shù)而已,我們前面提到了,普通函數(shù)執(zhí)行中this=window,所以,這里的this.age實(shí)際上是var age = 3。
第三個(gè) this.color 出現(xiàn)在new function(){this.color},這里就比較好玩,由于有一個(gè)new,實(shí)際上也被實(shí)例化了,只不過是對(duì)匿名類的實(shí)例化,沒有類名,而且實(shí)例化僅可能出現(xiàn)這一次。因此,this.color的this要去找new function的主人,也就是this.eye,而this.eye的this=cat1,所以cat1.eye.color=’red’。
第四個(gè) this.name 出現(xiàn)在function(){this.name},它出現(xiàn)在cacthing方法中,它既不是普通的函數(shù)執(zhí)行,也不是實(shí)例化為對(duì)象,而是正常的類中的方法的聲明,因此this指向要去找它所在的function被實(shí)例化的對(duì)象,也就是cat1。
對(duì)象實(shí)例 的 屬性和方法
不管通過哪種方式創(chuàng)建了對(duì)象實(shí)例后,該 實(shí)例 都會(huì)擁有 下面的屬性和方法,下面將會(huì)逐個(gè)說明。
constructor 屬性
"constructor 屬性" 是用來保存當(dāng)前對(duì)象的構(gòu)造函數(shù)的,前面的例子中,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 方法接收一個(gè)字符串參數(shù),該參數(shù)表示屬性名稱,用來判斷該屬性是否在當(dāng)前對(duì)象實(shí)例中,而不是在對(duì)象的原型鏈中。我們來看看下面這個(gè)例子:
var arr = []; console.log(arr.hasOwnProperty("length")); //true console.log(arr.hasOwnProperty("hasOwnProperty")); //false在這個(gè)例子中,首先定義了一個(gè)數(shù)組對(duì)象的 實(shí)例arr,我們知道,數(shù)組對(duì)象實(shí)際是通過 原型鏈 ( 下面會(huì)介紹?) 繼承了Object 對(duì)象,然后擁有自己的一些屬性,可以通過?hasOwnProperty?方法 判斷 length 是 arr 自己的屬性,然而?hasOwnProperty方法?是在 原型鏈 上的屬性。
hasOwnProperty 方法 可以和 for..in 結(jié)合起來獲取 對(duì)象自己的 key?
isPrototypeOf(Object) 方法
isPrototype 方法接收一個(gè)對(duì)象,用來判斷當(dāng)前對(duì)象是否在傳入的參數(shù)對(duì)象的原型鏈上,說起來有點(diǎn)抽象,我們來看看代碼。
function MyObject() {} var obj = new MyObject(); console.log(Object.prototype.isPrototypeOf(obj));上面代碼中 MyObject 是繼承自 Object 對(duì)象的,而在JS中,繼承是通過 prototype 來實(shí)現(xiàn)的,所以 Object 的 prototype 必定在 MyObject 對(duì)象實(shí)例的原型鏈上。
propertyIsEnumerable(prototypeName) 方法
prototypeIsEnumerable 用來判斷給定的屬性是否可以被 for..in 語(yǔ)句給枚舉出來??聪旅娲a:
var obj = {name: "objName" } for (var i in obj) {console.log(i); }執(zhí)行這段代碼輸出字符串 “name”,這就說明通過 for…in 語(yǔ)句可以得到 obj 的 name 這個(gè)屬性,但是我們知道,obj 的屬性還有很多,比如 constructor,比如hasOwnPrototype 等等,但是它們沒有被輸出,說明這些屬性不能被 for…in 給枚舉出來,可以通過 propertyIsEnumerable 方法來得到。
console.log(obj.propertyIsEnumerable("constructor")); // false判斷 “constructor” 是否可以被枚舉,輸出 false 說明無(wú)法被枚舉出來。
toLocaleString() 方法
toLocalString 方法返回對(duì)象的字符串表示,和代碼的執(zhí)行環(huán)境有關(guān)。
var obj = {}; console.log(obj.toLocaleString()); //[object Object] var date = new Date(); console.log(date.toLocaleString()); //2021/4/15 下午1:30:15toString() 方法
toString 用來返回對(duì)象的字符串表示。
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 (中國(guó)標(biāo)準(zhǔn)時(shí)間)valueOf() 方法
valueOf 方法返回對(duì)象的原始值,可能是字符串、數(shù)值 或 bool值 等,看具體的對(duì)象。
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如代碼所示,三個(gè)不同的對(duì)象實(shí)例調(diào)用 valueOf 返回不同的數(shù)據(jù)。
屬性 的 類型
在 Javascript 中,屬性有兩種類型,分別是
- 數(shù)據(jù)?屬性
- 訪問器?屬性
我們來看看這兩種屬性具體是什么東西。
數(shù)據(jù)?屬性
數(shù)據(jù)屬性:可以理解為我們平時(shí)定義對(duì)象時(shí)賦予的屬性,它可以進(jìn)行讀和寫。但是,ES5中定義了一些 特性,這些特性是用來描述屬性的各種特征。即 特性的作用 是描述 屬性的各種特征。特性是內(nèi)部值,不能直接訪問到。特性通過用兩對(duì)方括號(hào)表示,比如[[Enumerable]]。
屬性的特性會(huì)有一些默認(rèn)值,要修改特性的默認(rèn)值,必須使用 ES5 定義的新方法 Object.defineProperty 方法來修改
數(shù)據(jù)屬性有4個(gè)描述其特征的特性,下面將依次說明每一個(gè)特性:
(1)[[Configurable]]:該特性表示是否可以通過 delete 操作符來刪除屬性,默認(rèn)值是 true。
var obj = {}; obj.name = "myname";delete obj.name; console.log(obj.name);//undefined這段代碼很明顯,通過 delete 刪除了 obj 的 name 屬性后,我們?cè)僭L問 name 屬性就訪問不到了。
我們通過 Object.defineProperty 方法來修改 [[Configurable]] 特性。
var obj = {}; obj.name = "myname"; Object.defineProperty(obj, "name", {configurable: false }) delete obj.name; console.log(obj.name); //myname通過將 configurable 特性設(shè)置成 false 之后,delete 就無(wú)法刪除 name 屬性了,如果在嚴(yán)格模式下,使用 delete 去刪除就會(huì)報(bào)錯(cuò)。
(2)[[Enumerable]]:表示是否能夠通過 for…in 語(yǔ)句來枚舉出屬性,默認(rèn)是 true
我們來看看前面的例子:
var obj = {name: "objName" } for (var i in obj) {console.log(i);//name }這段代碼只輸出了 name 屬性,我們來將 constructor 屬性的 [[Enumerable]] 設(shè)置為 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 循環(huán)得到了 name 和 constructor 兩個(gè)屬性,而通過 propertyIsEnumerable 方法來判斷 constructor 也返回了true。
(3)[[Writable]]:表示屬性值是否可以修改,默認(rèn)為true。如果 [[Writable]] 被設(shè)置成 false,嘗試修改時(shí)將沒有效果,在嚴(yán)格模式下會(huì)報(bào)錯(cuò)
(4)[[Value]]:表示屬性的值,默認(rèn)為 undefined
我們通過一個(gè)簡(jiǎn)單的例子來看看這兩個(gè)特性:
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 對(duì)象的 name 屬性值為 “name”,然后通過 defineProperty 方法來修改值,并且將其設(shè)置為不可修改的。接著我們?cè)傩薷?name 屬性的值,可以發(fā)現(xiàn)修改無(wú)效。
如果我們通過 defineProperty 來修改 name 屬性的值,是否可以修改呢?答案是可以的:
Object.defineProperty(obj, "name", {value: "oldValue" }) console.log(obj.name); //oldValue訪問器?屬性
訪問器屬性有點(diǎn)類似于 C# 中的屬性,和數(shù)據(jù)屬性的區(qū)別在于,它沒有數(shù)據(jù)屬性的 [[Writable]] 和 [[Value]] 兩個(gè)特性,而是擁有一對(duì) getter 和 setter 函數(shù)。
- [[Get]]:讀取屬性時(shí)調(diào)用的函數(shù),默認(rèn)是 undefined
- [[Set]]:設(shè)置屬性時(shí)調(diào)用的函數(shù),默認(rèn)是 undefined
getter 和 setter 是一個(gè)很有用的東西,假設(shè)有兩個(gè)屬性,其中第二個(gè)屬性值會(huì)隨著第一個(gè)屬性值的變化而變化。這種場(chǎng)景在我們平時(shí)的編碼中起始是非常常見的。在之前的做法中,我們往往要去手動(dòng)修改第二個(gè)屬性的值,那現(xiàn)在我們就可以通過 get 和 set 函數(shù)來解決這個(gè)問題??聪旅孢@個(gè)例子:
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 的值也會(huì)相應(yīng)的修改,這樣我們就不用再手動(dòng)的去修改 type 的值了。
下面這種方式也是可以實(shí)現(xiàn)同樣的效果:
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);訪問器?屬性 的 注意點(diǎn)
關(guān)于訪問器屬性,有幾點(diǎn)要注意:
- 1、嚴(yán)格模式下,必須同時(shí)設(shè)置 get 和 set
- 2、非嚴(yán)格模式下,可以只設(shè)置其中一個(gè),如果只設(shè)置 get,則屬性是只讀的,如果只設(shè)置 set,屬性則無(wú)法讀取
- 3、Object.defineProperty 是 ES5 中的新方法,IE9(IE8部分實(shí)現(xiàn),只有dom對(duì)象才支持)以下瀏覽器不支持,一些舊的瀏覽器可以通過非標(biāo)準(zhǔn)方法defineGetter()和defineSetter()來設(shè)置,這里就不說明了,有興趣的同學(xué)可以查找相關(guān)資料。
特性?操作的相關(guān)方法
ES5 提供了一些讀取或操作屬性特性的方法,前面用到的 Object.defineProperty 就是其中之一。我總結(jié)了一些比較常用的方法如下:
(1)Object.defineProperty
定義一個(gè)對(duì)象的屬性,這個(gè)方法前面我們已經(jīng)用到多次,簡(jiǎn)單說說其用法。
Object.defineProperty(obj, propName, descriptor);defineProperty 有點(diǎn)類似于定于在 Object 上的靜態(tài)方法,通過 Object 直接調(diào)用,它接收3個(gè)參數(shù):
- obj:需要定義屬性的對(duì)象
- propNane:需要被定義的屬性名稱
- defineProperty:屬性描述符,包含一些屬性的特性定義
例子如下:
var obj = {}; Object.defineProperty(obj, "name", {value: "name",configurable: true,writable: true,enumerable: true });(2)Object.defineProperties
和 defineProperty 類似,是用來定義對(duì)象屬性的,不同的是它可以用來同時(shí)定義多個(gè)屬性,我們通過命名也可以看出來,用法如下:
var obj = {}; Object.defineProperty(obj, {"name": {value: "name",configurable: true,writable: true,enumerable: true},"age": {value: 20} });(3)Object.getOwnPropertyDescriptor
ES5 中還提供了一個(gè)讀取特性值的方法,該方法接收對(duì)象及其屬性名作為兩個(gè)參數(shù),返回一個(gè)對(duì)象,根據(jù)屬性類型的不同,返回對(duì)象會(huì)包含不同的值。
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 對(duì)象上新增了一批方法,這些方法可以直接通過 Object 進(jìn)行訪問,前面用到的 defineProperty 就是新增的方法之一。除此之外還有很多方法,我將其總結(jié)歸納如下:
對(duì)象創(chuàng)建型方法?Object.create(proto, [propertiesObject])
在前面我們提到,創(chuàng)建一個(gè)對(duì)象有兩種方法:構(gòu)造函數(shù) 和 對(duì)象字面量。
這兩種方法有一個(gè)缺點(diǎn)就是:如果要?jiǎng)?chuàng)建多個(gè)對(duì)象,寫起來很繁瑣,所以后來就有了一種創(chuàng)建自定義構(gòu)造函數(shù)的方法來創(chuàng)建對(duì)象,如下所示:
function Person(name, age) {this.name = name;this.age = age; } var person = new Person("Jack", 15);這種方式可以很方便的創(chuàng)建多個(gè)同樣的對(duì)象,也是目前比較常用的方法。
ES5 提供的 Object.create 方法也是一個(gè)創(chuàng)建對(duì)象的方法,這個(gè)方法允許為創(chuàng)建的對(duì)象選擇原型對(duì)象,不需要定義一個(gè)構(gòu)造函數(shù)。用法如下:
var obj = Object.create(Object.prototype, {name: {value: "Jack"} }) console.log(obj.name); //Jack這個(gè)方法接收的第一個(gè)參數(shù)作為被創(chuàng)建對(duì)象的原型,第二個(gè)參數(shù)是對(duì)象的屬性。
注意:在這個(gè)例子中,name屬性是無(wú)法被修改的,因?yàn)樗鼪]有設(shè)置writable特性,默認(rèn)則為false。
個(gè)人看法:Object.create這種創(chuàng)建對(duì)象的方式略顯繁瑣,除非是需要修改屬性的特性,否則不建議使用這種方式創(chuàng)建對(duì)象。
獲取 屬性?
Object.keys? 獲取自身屬性,但不包括原型中的屬性
Object.keys 是 es5 中新增的方法,用來獲取對(duì)象自身所有的可枚舉的屬性名,但不包括原型中的屬性,然后返回一個(gè)由屬性名組成的數(shù)組。
注意它同 for..in 一樣不能保證屬性按對(duì)象原來的順序輸出。
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 和 不可枚舉的相關(guān)屬性。
// 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 來達(dá)到類似的效果。
function Parent() {this.lastName = "Black" }function Child(firstName) {this.firstName = firstName; }Child.prototype = new Parent();var son = new Child("Jack");// 注意:這里如果不支持 Object.keys,則把 Object.keys 定義為一個(gè)函數(shù) 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 中新增的方法,返回對(duì)象自身的所有屬性的屬性名(包括可枚舉和不可枚舉的所有屬性)組成的數(shù)組,但不會(huì)獲取原型鏈上的屬性。
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 對(duì)象定義了一個(gè)不可枚舉的屬性 age,然后通過 keys 和 getOwnPropertyNames 兩個(gè)方法來獲取屬性列表,能明顯看出了兩者區(qū)別。
屬性?特性型?方法
這個(gè)主要是前面提到的三個(gè)方法:
- defineProperty,
- defineProperties
- getOwnPropertyDescriptor?
對(duì)象 限制型 方法
ES5 中提供了一系列限制對(duì)象被修改的方法,用來防止被某些對(duì)象被無(wú)意間修改導(dǎo)致的錯(cuò)誤。每種限制類型包含一個(gè)判斷方法和一個(gè)設(shè)置方法。
阻止對(duì)象擴(kuò)展
Object.preventExtensions()? 用來限制對(duì)象的擴(kuò)展,設(shè)置之后,對(duì)象將無(wú)法添加新屬性,用法如下:
Object.preventExtensions(obj);該方法接收一個(gè)要被設(shè)置成無(wú)法擴(kuò)展的對(duì)象作為參數(shù),需要注意兩點(diǎn):
- 1、對(duì)象的屬性不可用擴(kuò)展,但是已存在的屬性可以被刪除
- 2、無(wú)法添加新屬性指的是無(wú)法在自身上添加屬性,如果是在對(duì)象的原型上,還是可以添加屬性的。
Object.isExtensible 方法用來判斷一個(gè)對(duì)象是否可擴(kuò)展,默認(rèn)情況是 true
將對(duì)象密封
Object.seal 可以密封一個(gè)對(duì)象并返回被密封的對(duì)象。
密封對(duì)象無(wú)法添加或刪除已有屬性,也無(wú)法修改屬性的 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將對(duì)象密封后,使用 delete 刪除對(duì)象屬性,還是可以訪問得到屬性。
通過 Object.isSealed 可以用來判斷一個(gè)對(duì)象是否被密封了。
凍結(jié)對(duì)象
Object.freeze 方法用來凍結(jié)一個(gè)對(duì)象,被凍結(jié)的對(duì)象將無(wú)法添加,修改,刪除屬性值,也無(wú)法修改屬性的特性值,即這個(gè)對(duì)象無(wú)法被修改。
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分析上面的代碼我們可以發(fā)現(xiàn),被凍結(jié)的對(duì)象無(wú)法刪除自身的屬性,但是通過其原型對(duì)象還是可以新增屬性的。
通過Object.isFrozen可以用來判斷一個(gè)對(duì)象是否被凍結(jié)了。
可以發(fā)現(xiàn):這三個(gè)限制對(duì)象的方法的限制程度是依次上升的。
Javascript 的 Object 常用方法總結(jié)
參考:Javascript Object常用方法總結(jié) - fozero - 博客園
Object.keys(ojb)?方法
Object.keys(obj) 方法是 JavaScript 中用于遍歷對(duì)象屬性的一個(gè)方法 。它傳入的參數(shù)是一個(gè)對(duì)象,返回的是一個(gè)數(shù)組,數(shù)組中包含的是該對(duì)象所有的屬性名。如:
var cat = {name: 'mini',age: 2,color: 'yellow',desc:"cute" } console.log(Object.keys(cat)); // ["name", "age", "color", "desc"]這里有一道關(guān)于 Object.keys 的題目:輸出對(duì)象中值大于 2的 key 的數(shù)組
var data = {a: 1, b: 2, c: 3, d: 4}; Object.keys(data).filter(function (x) {return 1; })/* 期待輸出:["c","d"] 請(qǐng)問1處填什么? 正確答案:1 :data[x]>2 */Object.keys 是 es5 中新增的方法,用來獲取對(duì)象自身所有的可枚舉的屬性名,但不包括原型中的屬性,然后返回一個(gè)由屬性名組成的數(shù)組。注意它同 for..in 一樣不能保證屬性按對(duì)象原來的順序輸出。
Object.getOwnPropertyNames 也是 es5 中新增的方法,返回對(duì)象的所有自身屬性的屬性名(包括不可枚舉的屬性)組成的數(shù)組,但不會(huì)獲取原型鏈上的屬性。
Array.filter(function)
對(duì)數(shù)組進(jìn)行過濾返回符合條件的數(shù)組。
Object.values() 方法
Object.values 方法返回一個(gè)數(shù)組,成員是參數(shù)對(duì)象自身的(不含繼承的)所有可遍歷( enumerable )屬性的鍵值。
var obj = {foo: "bar", baz: 42}; Object.values(obj) // ["bar", 42]返回?cái)?shù)組的成員順序,屬性名為數(shù)值的屬性,是按照數(shù)值大小,從小到大遍歷的,因此返回的順序是b、c、a。Object.values 只返回對(duì)象自身的可遍歷屬性。
var obj = {100: 'a', 2: 'b', 7: 'c'}; Object.values(obj) // ["b", "c", "a"]如果 Object.values 方法的參數(shù)是一個(gè)字符串,會(huì)返回各個(gè)字符組成的一個(gè)數(shù)組。
Object.values('foo') // ['f', 'o', 'o']上面代碼中,字符串會(huì)先轉(zhuǎn)成一個(gè)類似數(shù)組的對(duì)象。字符串的每個(gè)字符,就是該對(duì)象的一個(gè)屬性。因此,Object.values返回每個(gè)屬性的鍵值,就是各個(gè)字符組成的一個(gè)數(shù)組。
如果參數(shù)不是對(duì)象,Object.values 會(huì)先將其轉(zhuǎn)為對(duì)象。由于數(shù)值和布爾值的包裝對(duì)象,都不會(huì)為實(shí)例添加非繼承的屬性。所以,Object.values 會(huì)返回空數(shù)組。
Object.create()
Object.create() 方法創(chuàng)建一個(gè)新對(duì)象,使用現(xiàn)有的對(duì)象來提供新創(chuàng)建的對(duì)象的 __proto__ 。
語(yǔ)法:Object.create(proto, [propertiesObject])
參數(shù)
? ? ? ? :proto? 新創(chuàng)建對(duì)象的原型對(duì)象。
? ? ? ? :propertiesObject? 可選。如果沒有指定為 undefined,則是要添加到新創(chuàng)建對(duì)象的可枚舉屬性(即其自身定義的屬性,而不是其原型鏈上的枚舉屬性)對(duì)象的屬性描述符以及相應(yīng)的屬性名稱。這些屬性對(duì)應(yīng)Object.defineProperties()的第二個(gè)參數(shù)。
返回值:一個(gè)新對(duì)象,帶著指定的原型對(duì)象和屬性。如:
var parent = {x: 1,y: 1 } var child = Object.create(parent, {z: { // z會(huì)成為創(chuàng)建對(duì)象的屬性writable: true,configurable: true,value: "newAdd"} }); console.log(child)//{z: "newAdd"}z: "newAdd"__proto__: x: 1y: 1__proto__: ObjectObject.create()??創(chuàng)建繼承
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 一個(gè)實(shí)例 //方式2 function C() {A.call(this); }C.prototype = Object.create(A.prototype) //這里使用的是父類的原型以上兩種方式有什么區(qū)別?
1 的缺點(diǎn):
? ? ? ? 執(zhí)行了 new,相當(dāng)于運(yùn)行了一遍 A ,如果在 A 里做了一些其它事情(如改變?nèi)肿兞?#xff09;就會(huì)有副作用。
? ? ? ? 用 A 創(chuàng)建的對(duì)象做原型,里面可能會(huì)有一些冗余的屬性。
2 模擬了 new 的執(zhí)行過程
Object.hasOwnProperty() 方法
判斷對(duì)象自身屬性中是否具有指定的屬性。這個(gè)方法是不包括對(duì)象原型鏈上的方法的。
判斷某個(gè)對(duì)象是否擁有某個(gè)屬性,判斷的方法有很多,常用的方法就是 object.hasOwnProperty('×××')
var obj = {name: 'fei' } console.log(obj.hasOwnProperty('name')) // true console.log(obj.hasOwnProperty('toString')) // false以上,obj 對(duì)象存在的 name 屬性的時(shí)候,調(diào)用這個(gè)方法才是返回 true,我們知道其實(shí)每個(gè)對(duì)象實(shí)例的原型鏈上存在 toString 方法,在這里打印 false,說明這個(gè)方法只是表明實(shí)例對(duì)象的屬性,不包括原型鏈上的屬性。
Object.getOwnPropertyNames() 方法
Object.getOwnPropertyNames() 方法返回對(duì)象的所有自身屬性的屬性名(包括不可枚舉的屬性)組成的數(shù)組,但不會(huì)獲取原型鏈上的屬性。
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?區(qū)別
Object.getOwnPropertyNames 和 Object.keys 的區(qū)別,
- Object.keys 只適用于可枚舉的屬性,
- Object.getOwnPropertyNames 返回對(duì)象自動(dòng)的全部屬性名稱。
es6 中?JavaScript 對(duì)象方法 Object.assign()
Object.assign 方法用于對(duì)象的合并,將源對(duì)象( source )的所有可枚舉屬性,復(fù)制到目標(biāo)對(duì)象( 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、如果目標(biāo)對(duì)象與源對(duì)象有同名屬性,或多個(gè)源對(duì)象有同名屬性,則后面的屬性會(huì)覆蓋前面的屬性。
2、如果只有一個(gè)參數(shù),Object.assign 會(huì)直接返回該參數(shù)。
3、如果該參數(shù)不是對(duì)象,則會(huì)先轉(zhuǎn)成對(duì)象,然后返回。
4、由于undefined和null無(wú)法轉(zhuǎn)成對(duì)象,所以如果它們作為參數(shù),就會(huì)報(bào)錯(cuò)。
5、Object.assign方法實(shí)行的是淺拷貝,而不是深拷貝。也就是說,如果源對(duì)象某個(gè)屬性的值是對(duì)象,那么目標(biāo)對(duì)象拷貝得到的是這個(gè)對(duì)象的引用。
上面代碼中,源對(duì)象obj1的a屬性的值是一個(gè)對(duì)象,Object.assign拷貝得到的是這個(gè)對(duì)象的引用。這個(gè)對(duì)象的任何變化,都會(huì)反映到目標(biāo)對(duì)象上面。
常見用途
( 1 )為對(duì)象添加屬性
class Point {constructor(x, y) {Object.assign(this, {x, y});} }上面方法通過Object.assign方法,將x屬性和y屬性添加到Point類的對(duì)象實(shí)例。
( 2 )為對(duì)象添加方法
Object.assign(SomeClass.prototype, {someMethod(arg1, arg2) {//···},anotherMethod() {//···} }); // 等同于下面的寫法 SomeClass.prototype.someMethod = function (arg1, arg2) {//··· }; SomeClass.prototype.anotherMethod = function () {//··· };上面代碼使用了對(duì)象屬性的簡(jiǎn)潔表示法,直接將兩個(gè)函數(shù)放在大括號(hào)中,再使用 assign 方法添加到 SomeClass.prototype 之中。
( 3 )克隆對(duì)象
function clone(origin) { return Object.assign({}, origin); }上面代碼將原始對(duì)象拷貝到一個(gè)空對(duì)象,就得到了原始對(duì)象的克隆。不過,采用這種方法克隆,只能克隆原始對(duì)象自身的值,不能克隆它繼承的值。
( 4 )合并多個(gè)對(duì)象
將多個(gè)對(duì)象合并到某個(gè)對(duì)象。
const merge =(target, ...sources) => Object.assign(target, ...sources);如果希望合并后返回一個(gè)新對(duì)象,可以改寫上面函數(shù),對(duì)一個(gè)空對(duì)象合并。
const merge =(...sources) => Object.assign({}, ...sources);( 5 )為屬性?指定?默認(rèn)值
const DEFAULTS = {logLevel: 0,outputFormat: 'html' };function processContent(options) {let options = Object.assign({}, DEFAULTS, options); }DEFAULTS 對(duì)象是默認(rèn)值,options 對(duì)象是用戶提供的參數(shù)。Object.assign 方法將 DEFAULTS 和 options 合并成一個(gè)新對(duì)象,如果兩者有同名屬性,則 option 的屬性值會(huì)覆蓋 DEFAULTS 的屬性值。注意,由于存在深拷貝的問題,DEFAULTS 對(duì)象 和 options對(duì)象的所有屬性的值,都只能是簡(jiǎn)單類型,而不能指向另一個(gè)對(duì)象。否則,將導(dǎo)致DEFAULTS 對(duì)象的該屬性不起作用。
參考 es6 javascript 對(duì)象方法Object.assign():es6 javascript對(duì)象方法Object.assign()_現(xiàn)在學(xué)習(xí)也不晚-CSDN博客_object.assign
Object.defineProperty() 方法理解
Object.defineProperty 可以用來定義新屬性或修改原有的屬性
使用構(gòu)造函數(shù)定義對(duì)象和屬性
var obj = new Object; //obj = {} obj.name = "張三"; //添加描述 obj.say = function(){}; //添加行為語(yǔ)法:Object.defineProperty(obj, prop, descriptor)
參數(shù)說明
- obj:必需。目標(biāo)對(duì)象
- prop:必需。需定義或修改的屬性的名字
- descriptor:必需。目標(biāo)屬性所擁有的特性
給對(duì)象的屬性添加特性描述,目前提供兩種形式:
- 數(shù)據(jù)描述
- 存取器描述
數(shù)據(jù)?描述
修改或定義對(duì)象的某個(gè)屬性的時(shí)候,給這個(gè)屬性添加一些特性, 數(shù)據(jù)描述中的屬性都是可選的
var obj = {test:"hello" }//對(duì)象已有的屬性添加特性描述 /* Object.defineProperty(obj,"test",{configurable:true | false,enumerable:true | false,value:任意類型的值,writable:true | false }); *///對(duì)象新添加的屬性的特性描述 /* Object.defineProperty(obj,"newKey",{configurable:true | false,enumerable:true | false,value:任意類型的值,writable:true | false }); */- value: 設(shè)置屬性的值
- writable: 值是否可以重寫。true | false
- enumerable: 目標(biāo)屬性是否可以被枚舉。true | false
- configurable: 目標(biāo)屬性是否可以被刪除或是否可以再次修改特性 true | false
存取器?描述
使用存取器描述屬性的特性的時(shí)候,允許設(shè)置以下特性屬性, 當(dāng)使用了getter 或 setter 方法,不允許使用 writable 和 value 這兩個(gè)屬性
var obj = {}; Object.defineProperty(obj, "newKey", {get: function () {} | undefined,set: function (value) {} | undefined,configurable: true | false,enumerable: true | false });getter / setter
getter 是一種獲得屬性值的方法。setter是一種設(shè)置屬性值的方法。使用 get/set 屬性來定義對(duì)應(yīng)的方法
var obj = {}; var initValue = 'hello'; Object.defineProperty(obj, "newKey", {get: function () {//當(dāng)獲取值的時(shí)候觸發(fā)的函數(shù)return initValue;},set: function (value) {//當(dāng)設(shè)置值的時(shí)候觸發(fā)的函數(shù),設(shè)置的新值通過參數(shù)value拿到initValue = value;} });//獲取值console.log(obj.newKey); //hello //設(shè)置值 obj.newKey = 'change value'; console.log(obj.newKey); //change value原型鏈
- 1.?每個(gè)函數(shù)(?函數(shù)也是對(duì)象 )都有 prototype 和 __proto__
- 2.?每一個(gè)對(duì)象?/?構(gòu)造函數(shù)的實(shí)例(這個(gè)也是對(duì)象)都有 __proto__
- 3.?實(shí)例 的? __proto__? 指向 構(gòu)造函數(shù) 的 prototype。這個(gè)稱為 構(gòu)造函數(shù)的原型對(duì)象
- 4. JavaScript?引擎會(huì)沿著 __proto__? --->??ptototype 的順序一直往上方查找,找到?window.Object.prototype 為止,Object 為原生底層對(duì)象,到這里就停止了查找。
? ? ? 如果沒有找到,就會(huì)報(bào)錯(cuò)或者返回 undefined - 5.?而構(gòu)造函數(shù)的 __proto__? 指向 Function.prototype? ? () { [native code] } 【構(gòu)造器函數(shù),但這個(gè)叫法? 并不準(zhǔn)確,它目前沒有一個(gè)合適的中文名】
- 6.?__proto__ 是瀏覽器廠商實(shí)現(xiàn)的,W3C規(guī)范中并沒有這個(gè)東西
- 1. JS 代碼還沒運(yùn)行的時(shí)候,JS 環(huán)境里已經(jīng)有一個(gè) window 對(duì)象了。
- 2. window 對(duì)象有一個(gè) Object 屬性,window.Object 是一個(gè) 函數(shù)對(duì)象
- 3. window.Object 這個(gè)函數(shù)對(duì)象有一個(gè)重要屬性是 prototype
- 4. window.Object.prototype 里面有一堆屬性
- 5. 所有的實(shí)例函數(shù)的 __proto__ 都會(huì)指向 構(gòu)造函數(shù)的 prototype
- 6. constructor 是 反向的prototype
上面定義了一個(gè) 空對(duì)象obj,當(dāng)調(diào)用 obj 的 toString() 理論上會(huì)報(bào) undefined 錯(cuò)誤,但是實(shí)際上不會(huì)報(bào)錯(cuò)
運(yùn)算符 -- JavaScript 標(biāo)準(zhǔn)參考教程(alpha):運(yùn)算符 -- JavaScript 標(biāo)準(zhǔn)參考教程(alpha)
JavaScript 中 === 和 == 的區(qū)別 ( 參考:Javascript 中 == 和 === 區(qū)別是什么? - 知乎 ):
- "==="? 叫做?恒等?運(yùn)算符。( 即 類型和值 都相等?)(?其實(shí)叫?全等運(yùn)算符 更合適。即內(nèi)存中每個(gè)bit位都一樣?)
? ? 嚴(yán)格相等運(yùn)算符 === 的運(yùn)算規(guī)則如下:
? ? (1) 不同類型值。如果兩個(gè)值的類型不同,直接返回false。
? ? (2) 同一類的原始類型值。同一類型的原始類型的值(數(shù)值、字符串、布爾值)比較時(shí),值相同就返回true,值不同就返回false。
? ? (3) 同一類的復(fù)合類型值。兩個(gè)復(fù)合類型(對(duì)象、數(shù)組、函數(shù))的數(shù)據(jù)比較時(shí),不是比較它們的值是否相等,而是比較它們是否指向同一個(gè)對(duì)象。
? ? (4) undefined 和 null。undefined 和 null 與自身嚴(yán)格相等。
? ? ????null === null ?//true
? ? ????undefined === undefined ?//true - "=="? 叫做 相等 運(yùn)算符。( 只 判斷數(shù)據(jù)的值 )
? ? 相等運(yùn)算符 == 在比較相同類型的數(shù)據(jù)時(shí),與嚴(yán)格相等運(yùn)算符完全一樣。
? ? 在比較不同類型的數(shù)據(jù)時(shí),相等運(yùn)算符會(huì)先將數(shù)據(jù)進(jìn)行類型轉(zhuǎn)換,然后再用嚴(yán)格相等運(yùn)算符比較。
? ? 類型轉(zhuǎn)換規(guī)則如下:
? ? (1) 原始類型的值。原始類型的數(shù)據(jù)會(huì)轉(zhuǎn)換成數(shù)值類型再進(jìn)行比較。字符串和布爾值都會(huì)轉(zhuǎn)換成數(shù)值。
? ? (2) 對(duì)象與原始類型值比較。對(duì)象(這里指廣義的對(duì)象,包括數(shù)值和函數(shù))與原始類型的值比較時(shí),對(duì)象轉(zhuǎn)化成原始類型的值,再進(jìn)行比較。
? ? (3) undefined和null。undefined和null與其他類型的值比較時(shí),結(jié)果都為false,它們互相比較時(shí)結(jié)果為true。
? ? (4) 相等運(yùn)算符的缺點(diǎn)。相等運(yùn)算符會(huì)隱藏類型的轉(zhuǎn)換,然后帶來一些違反直覺的結(jié)果。 - 因?yàn)?#34;=="不嚴(yán)謹(jǐn),可能會(huì)帶來一些違反直覺的后果,建議盡量不要使用 相等運(yùn)算符 == ,而是使用 嚴(yán)格相等運(yùn)算符 ===?
示例:
'' == '0' ?// false
0 == '' ???// true。'' 會(huì)先轉(zhuǎn)換成數(shù)值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'); // 無(wú)輸出 }js中 !== 和 != 的區(qū)別:
- !=? ? 會(huì)轉(zhuǎn)換成相同類型 進(jìn)行比較。即 在表達(dá)式兩邊的數(shù)據(jù)類型不一致時(shí),會(huì)隱式轉(zhuǎn)換為相同數(shù)據(jù)類型,然后對(duì)值進(jìn)行比較;
- !==? ?不會(huì)進(jìn)行類型轉(zhuǎn)換,在比較時(shí)除了對(duì)值進(jìn)行比較以外,還比較兩邊的數(shù)據(jù)類型, 它是 恒等運(yùn)算符 === 的非形式。
解釋:上面是定義?obj 變量指向一個(gè)空對(duì)象,當(dāng)對(duì)象定義的一瞬間,就會(huì)瞬間產(chǎn)生一個(gè)?__proto__ 的屬性?,這個(gè)屬性指向 window.Object.prototype。
上面這個(gè)搜索的過程是由 __proto__ 組成的鏈子一直走下去的,這個(gè)過程就叫做 原型鏈
上面是一個(gè) "鏈",下面繼續(xù)深入,看下數(shù)組
var arr = [] arr.push(1) // [1]再?gòu)?fù)雜一下,arr.valueOf() 做了什么?
- arr 自身沒有 valueOf,于是去 arr.__proto__ 上找
- arr.__proto__ 只有 pop、push 也沒有 valueOf,于是去 arr.__proto__.__proto__ 上找
- arr.__proto__.__proto__ 就是 window.Object.prototype
- 所以 arr.valueOf 其實(shí)就是 window.Object.prototype.valueOf
- arr.valueOf() 等價(jià)于 arr.valueOf.call(arr)
- arr.valueOf.call(arr) 等價(jià)于 window.Object.prototype.valueOf.call(arr)
函數(shù)進(jìn)階
JS中一切皆對(duì)象。對(duì)象是擁有屬性和方法的數(shù)據(jù)。JS函數(shù)也是對(duì)象
當(dāng)創(chuàng)建一個(gè)函數(shù)的時(shí)候,發(fā)生了什么?
實(shí)際上,函數(shù)?是?Function類型 的 實(shí)例,此時(shí)可以把每一個(gè)創(chuàng)建出來的函數(shù),當(dāng)成是Function類型的實(shí)例對(duì)象,
所以函數(shù)本身?yè)碛械膶?duì)象屬性是來源于?Function,Fn.Constructor 即為 Function
但是與此同時(shí)要注意:Function.prototype.__proto__ === Object.prototype
可以理解為:構(gòu)造器函數(shù)的構(gòu)造函數(shù)是Object
也可以簡(jiǎn)單的理解:函數(shù)即對(duì)象
如果上面看不懂,可以繼續(xù)看下面就會(huì)明白。。。
構(gòu)造函數(shù)
每個(gè) 函數(shù)?都有一個(gè) 原型對(duì)象(prototype)
原型對(duì)象 都包含一個(gè)指向 構(gòu)造函數(shù) 的 指針,?
而 實(shí)例(instance) 都包含一個(gè)指向 原型對(duì)象 的 內(nèi)部指針。
1. 在 JavaScript 中,用 new 關(guān)鍵字來調(diào)用的函數(shù),稱為構(gòu)造函數(shù)。構(gòu)造函數(shù)首字母一般大寫。
示例:使用構(gòu)造函數(shù)創(chuàng)建一個(gè)對(duì)象,在這個(gè)例子中,Person 就是一個(gè)構(gòu)造函數(shù),然后使用 new 創(chuàng)建了一個(gè) 實(shí)例對(duì)象 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 的實(shí)例。這兩個(gè)實(shí)例都有一個(gè) constructor (構(gòu)造函數(shù))屬性,該屬性(是一個(gè)指針)指向 Person。 即:
console.log(person1.constructor == Person) //true console.log(person2.constructor == Person) //true2.構(gòu)造函數(shù)的執(zhí)行過程
function Person(name, sex, age){this.name = name;this.sex = sex;this.age = age; } p1 = new Person('king', '男', 100);- (1) ?當(dāng)以 new 關(guān)鍵字調(diào)用時(shí),會(huì)創(chuàng)建一個(gè)新的內(nèi)存空間,標(biāo)記為 Person 的實(shí)例
- (2) ?函數(shù)體內(nèi)部的 this 指向該內(nèi)存,每當(dāng)創(chuàng)建一個(gè)實(shí)例的時(shí)候,就會(huì)創(chuàng)建一個(gè)新的內(nèi)存空間
- (3) ?給 this 添加屬性,就相當(dāng)于給實(shí)例添加屬性
- (4) ?由于函數(shù)體內(nèi)部的 this 指向新創(chuàng)建的內(nèi)存空間,默認(rèn)返回 this ,就相當(dāng)于默認(rèn)返回了該內(nèi)存空間
prototype
每個(gè) 函數(shù) 都有一個(gè) prototype 屬性,就是我們經(jīng)常在各種例子中看到的那個(gè) prototype ,指向調(diào)用該構(gòu)造函數(shù)而創(chuàng)建的 實(shí)例的原型,
比如:上面例子中的 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是函數(shù)才會(huì)有的屬性 Person.prototype.name = 'Kevin'; var person1 = new Person(); var person2 = new Person(); console.log(person1.name) // Kevin console.log(person2.name) // Kevin那這個(gè)函數(shù)的 prototype 屬性到底指向的是什么呢?是這個(gè)函數(shù)的原型嗎?
?其實(shí),函數(shù)的 prototype 屬性指向了一個(gè)對(duì)象,這個(gè)對(duì)象正是調(diào)用該構(gòu)造函數(shù)?而創(chuàng)建 的 實(shí)例的原型,也就是這個(gè)例子中的 person1 和 person2 的原型。
那什么是原型呢 ?
可以這樣理解:每一個(gè)JavaScript對(duì)象(null除外)在創(chuàng)建的時(shí)候就會(huì)與之關(guān)聯(lián)另一個(gè)對(duì)象,這個(gè)對(duì)象就是我們所說的原型,每一個(gè)對(duì)象都會(huì)從原型"繼承"屬性。
讓我們用一張圖表示構(gòu)造函數(shù)和實(shí)例原型之間的關(guān)系:
在這張圖中我們用 Object.prototype 表示實(shí)例原型。
那么我們?cè)撛趺幢硎緦?shí)例與實(shí)例原型,也就是 person 和 Person.prototype 之間的關(guān)系呢,這時(shí)候我們就要講到第二個(gè)屬性:__proto__
JS 的繼承是用過原型鏈實(shí)現(xiàn)的
給構(gòu)造函數(shù)添加屬性
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__ 區(qū)別 :
- 函數(shù)(Function)才有 prototype 屬性,
- 對(duì)象(除Object)擁有__proto__。
js 原型鏈 prototype? __proto__:https://www.cnblogs.com/mengfangui/p/9566114.html
var a = {}; // 定義一個(gè)對(duì)象 console.log(a.prototype); //undefined console.log(a.__proto__); //Object {}var b = function() {} // 定義一個(gè)函數(shù) 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、構(gòu)造器方式*/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__
這是每一個(gè) JavaScript 對(duì)象 (除了 null ) 都具有的一個(gè)屬性,叫 __proto__,這個(gè)屬性會(huì)指向該對(duì)象的原型。
為了證明這一點(diǎn),我們可以在火狐或者谷歌中輸入:
function Person() { } var person = new Person(); console.log(person.__proto__ === Person.prototype); // true于是我們更新下關(guān)系圖:
既然 實(shí)例對(duì)象 和 構(gòu)造函數(shù) 都可以指向原型,那么 原型 是否有屬性指向 構(gòu)造函數(shù)或者實(shí)例 呢?
constructor
- 原型 指向 實(shí)例 倒是沒有,因?yàn)橐粋€(gè)構(gòu)造函數(shù)可以生成多個(gè)實(shí)例。
-
但是 原型 指向 構(gòu)造函數(shù) 倒是有的,這就要講到第三個(gè)屬性:constructor,每個(gè)原型都有一個(gè) constructor 屬性指向關(guān)聯(lián)的構(gòu)造函數(shù)。
為了驗(yàn)證這一點(diǎn),我們可以嘗試:
function Person() {} var person1 = new Person() console.log(Person === Person.prototype.constructor) // true console.log(person1.__proto__ === Person.prototype) // true所以再更新下關(guān)系圖:?
function Person(){}; ? ?// 定義一個(gè) 構(gòu)造函數(shù)
var pp = new Person(); // 定義一個(gè) 構(gòu)造函數(shù)的實(shí)例
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
構(gòu)造函數(shù) 的 constructor 指向 構(gòu)造函數(shù)自身
繼續(xù)深入,來看:
? ? ? ? var arr = []
? ? ? ? arr.push(1) // [1]
?復(fù)雜一下,arr.valueOf() 做了什么?
arr 自身沒有 valueOf,于是去 arr.__proto__ 上找 arr.__proto__ 只有 pop、push 也沒有 valueOf,于是去 arr.__proto__.__proto__ 上找 arr.__proto__.__proto__ 就是 window.Object.prototype 所以 arr.valueOf 其實(shí)就是 window.Object.prototype.valueOf arr.valueOf() 等價(jià)于 arr.valueOf.call(arr) arr.valueOf.call(arr) 等價(jià)于 window.Object.prototype.valueOf.call(arr)
綜上我們已經(jīng)得出:
function Person() { } var person = new Person(); console.log(person.__proto__ == Person.prototype) // true console.log(Person.prototype.constructor == Person) // true // 順便學(xué)習(xí)一個(gè)ES5的方法,可以獲得對(duì)象的原型 console.log(Object.getPrototypeOf(person) === Person.prototype) // true首先要清楚:JS中所有事物都是對(duì)象,對(duì)象是擁有屬性和方法的數(shù)據(jù)。所以函數(shù)也是對(duì)象
當(dāng)創(chuàng)建一個(gè)函數(shù)的時(shí)候,發(fā)生了什么?
實(shí)際上,
- 函數(shù)是 Function類型的實(shí)例,此時(shí)可以把每一個(gè)創(chuàng)建出來的函數(shù),當(dāng)成是Function類型的實(shí)例對(duì)象,
- 所以函數(shù)本身?yè)碛械膶?duì)象屬性,來源于Function,即?Fn.Constructor?就是?Function
- 但是與此同時(shí)要注意:Function.prototype.__proto__ === Object.prototype 可以理解為:構(gòu)造器函數(shù)的構(gòu)造函數(shù)是Object 也可以簡(jiǎn)單的理解:函數(shù)即對(duì)象
?
?了解了構(gòu)造函數(shù)、實(shí)例原型、和實(shí)例之間的關(guān)系,接下來我們講講 實(shí)例 和 原型 的關(guān)系:
定義對(duì)象的兩種方式:
class Cat {constructor(){}toString(){}toValue(){} } 等價(jià)于 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實(shí)例 與 原型
當(dāng)讀取 實(shí)例的屬性?時(shí),如果找不到,就會(huì)查找與對(duì)象關(guān)聯(lián)的原型中的屬性,如果還查不到,就去找原型的原型,一直找到最頂層為止。
舉個(gè)例子:
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在這個(gè)例子中,我們給實(shí)例對(duì)象 person 添加了 name 屬性,當(dāng)我們打印 person.name 的時(shí)候,結(jié)果自然為 Daisy。但是當(dāng)我們刪除了 person 的 name 屬性時(shí),讀取 person.name,從 person 對(duì)象中找不到 name 屬性,就會(huì)從 person 的原型也就是 person.__proto__ ,也就是 Person.prototype 中查找,幸運(yùn)的是我們找到了 name 屬性,結(jié)果為 Kevin。
但是萬(wàn)一還沒有找到呢?原型的原型又是什么呢?
原型 的 原型
在前面,我們已經(jīng)講了原型也是一個(gè)對(duì)象,既然是對(duì)象,我們就可以用最原始的方式創(chuàng)建它,那就是:
var obj = new Object();
obj.name = 'Kevin'
console.log(obj.name) // Kevin
所以原型對(duì)象是通過 Object 構(gòu)造函數(shù)生成的,結(jié)合之前所講,實(shí)例的 __proto__? 指向 構(gòu)造函數(shù)的 prototype ,所以我們?cè)俑孪玛P(guān)系圖:
原型鏈
那 Object.prototype 的原型呢?
null,不信我們可以打印:console.log(Object.prototype.__proto__ === null) // true
所以查到屬性的時(shí)候查到 Object.prototype 就可以停止查找了。所以最后一張關(guān)系圖就是
順便還要說一下,圖中由 相互關(guān)聯(lián)的原型組成的鏈狀結(jié)構(gòu) 就是 原型鏈,也就是藍(lán)色的這條線。
補(bǔ)充
最后,補(bǔ)充三點(diǎn)大家可能不會(huì)注意的地方:
constructor
首先是 constructor 屬性,我們看個(gè)例子:
function Person() { } var person = new Person(); console.log(person.constructor === Person); // true當(dāng)獲取 person.constructor 時(shí),其實(shí) person 中并沒有 constructor 屬性,當(dāng)不能讀取到 constructor 屬性時(shí),會(huì)從 person 的原型也就是 Person.prototype 中讀取,正好原型中有該屬性,所以:
person.constructor === Person.prototype.constructor__proto__
其次是 __proto__ ,絕大部分瀏覽器都支持這個(gè)非標(biāo)準(zhǔn)的方法訪問原型,然而它并不存在于 Person.prototype 中,實(shí)際上,它是來自于 Object.prototype ,與其說是一個(gè)屬性,不如說是一個(gè) getter/setter,當(dāng)使用 obj.__proto__ 時(shí),可以理解成返回了 Object.getPrototypeOf(obj)。
真的是繼承嗎?
最后是關(guān)于繼承,前面我們講到“每一個(gè)對(duì)象都會(huì)從原型‘繼承'屬性”,實(shí)際上,繼承是一個(gè)十分具有迷惑性的說法,引用《你不知道的JavaScript》中的話,就是:
繼承意味著復(fù)制操作,然而 JavaScript 默認(rèn)并不會(huì)復(fù)制對(duì)象的屬性,相反,JavaScript 只是在兩個(gè)對(duì)象之間創(chuàng)建一個(gè)關(guān)聯(lián),這樣,一個(gè)對(duì)象就可以通過委托訪問另一個(gè)對(duì)象的屬性和函數(shù),所以與其叫繼承,委托的說法反而更準(zhǔn)確些。
自執(zhí)行 匿名 函數(shù)
- 聲明式函數(shù)。會(huì)導(dǎo)致函數(shù)提升,function會(huì)被解釋器優(yōu)先編譯。即我們用聲明式寫函數(shù),可以在任何區(qū)域聲明,不會(huì)影響我們調(diào)用。function XXX(){}
? ? ? ? fn1();
? ? ? ? function fn1(){}? ? // 可以正常調(diào)用 - 函數(shù)表達(dá)式。函數(shù)表達(dá)式經(jīng)常使用,而函數(shù)表達(dá)式中的?function?則不會(huì)出現(xiàn)函數(shù)提升。而是JS解釋器逐行解釋,到了這一句才會(huì)解釋。因此如果調(diào)用在函數(shù)表達(dá)式之前,則會(huì)調(diào)用失敗。var k = function(){}
? ? ? ? fn2();
? ? ? ? var fn2 = function(){}? ? // 無(wú)法調(diào)用
小括號(hào) 的 作用:
- 小括號(hào) 能把表達(dá)式組合分塊,并且每一塊,也就是每一對(duì)小括號(hào),都有一個(gè)返回值。返回值實(shí)際上也就是 小括號(hào)中 表達(dá)式的返回值。
- 所以,當(dāng)我們用一對(duì)小括號(hào)把匿名函數(shù)括起來的時(shí)候,實(shí)際上 小括號(hào)對(duì) 返回的就是一個(gè)匿名函數(shù)的 Function 對(duì)象。
- 因此,小括號(hào)對(duì) 加上 匿名函數(shù) 就如同 有名字的函數(shù) 被我們?nèi)〉盟囊梦恢昧恕K匀绻谶@個(gè)引用變量后面再加上參數(shù)列表,就會(huì)實(shí)現(xiàn)普通函數(shù)的調(diào)用形式。
- 簡(jiǎn)單來說就是:小括號(hào)有返回值,也就是小括號(hào)內(nèi)的函數(shù)或者表達(dá)式的返回值,所以說小括號(hào)內(nèi)的 function 返回值等于小括號(hào)的返回值
自執(zhí)行函數(shù)
1. 先來看個(gè)最簡(jiǎn)單的自執(zhí)行函數(shù)
(function(){}());相當(dāng)于聲明并調(diào)用
var b = function () {} b()2.?自執(zhí)行函數(shù)也可以有名字
function b(){... }()3.?執(zhí)行函數(shù)也可以傳參
function b(i){console.log(i) }(5)總結(jié):自執(zhí)行函數(shù) 在調(diào)用上與普通函數(shù)一樣,可以匿名,可以傳參。只不過是在聲明的時(shí)候自調(diào)用了一次
常見的自執(zhí)行匿名函數(shù):
- 第一種:(function(x,y){return x+y;})(3,4);? ? ? ? ? ? ? // 兩個(gè)()() ,function寫在第一個(gè)()里面,第二個(gè)()用來傳參,這種比較常見。默認(rèn)返回值 是? undefine
- 第二種:(function(param) { alert(param);})('張三');? ? ? ?// 自執(zhí)行函數(shù)的傳參。默認(rèn)返回值 是? undefine
- 第三種:(function(param) { console.log(param); return arguments.callee;})('html5')('php');? ?
不常見的自執(zhí)行匿名函數(shù):
- 第四種:~(function(){ alert('hellow world!'); })();? ? ? ? // 默認(rèn)返回值 是 -1 ~function(x, y) {return x+y; }(3, 4);
- 第五種:void function(){ alert('hellow world!'); }();? ? ?// 默認(rèn)返回值 是? undefine。??void function(x) {x = x-1;}(9);
- 第六種:+function(){ alert('hellow world!'); }();? ? ? ? ? // 默認(rèn)返回值 是 NaN +function(x,y){return x+y; }(3,4);++function(x,y){return x+y; }(3,4);
- 第七種:-function(){ alert('hellow world!'); }();? ? ? ? ???// 默認(rèn)返回值 是 NaN -function(x,y){return x+y; }(3,4);--function(x,y){return x+y; }(3,4);
- 第八種:!function(){ alert('hellow world!'); }();? ? ? ? ? ?// 默認(rèn)返回?true,是一個(gè) bool 值
- 第九種:(? function(x,y){return x+y;}(3,4)? );? ? ? ? ??// 一個(gè)() ,里面寫 function(){}()? 默認(rèn)返回值 是? undefine
- 匿名函數(shù)的執(zhí)行放在 [ ] 中: [function(){console.log(this) // 瀏覽器的控制臺(tái)輸出window }(this)]
-
匿名函數(shù)前加 typeof
typeof function(){console.log(this) // 瀏覽器得控制臺(tái)輸出window }(this)
示例:匿名函數(shù)自執(zhí)行 實(shí)現(xiàn) 異步函數(shù)遞歸
(function async(i) { if (i >= 5){ return }else{setTimeout(() => {console.log(i)i++async(i)}, 1000)} })(0)自執(zhí)行匿名函數(shù) 執(zhí)行耗時(shí)結(jié)果:
- new 方法永遠(yuǎn)最慢。
- 括號(hào) 在測(cè)試?yán)锉憩F(xiàn)始終很快,在大多數(shù)情況下比感嘆號(hào)更快,甚至可以說是最優(yōu)的。
- 加減號(hào) 在chrome表現(xiàn)驚人,而且在其他瀏覽器下也普遍很快,相比加號(hào)效果更好。當(dāng)然這只是個(gè)簡(jiǎn)單測(cè)試,不能說明問題。
但有些結(jié)論是有意義的:括號(hào)和加減號(hào)最優(yōu)
面向?qū)ο?--- 封裝
封裝:把客觀事物封裝成抽象的類,隱藏屬性和方法,僅對(duì)外公開接口。
在ES6之前,是不存在?class?這個(gè)語(yǔ)法糖類的。所以實(shí)現(xiàn)大多采用原型對(duì)象和構(gòu)造函數(shù)
- 私有 屬性和方法:只能在構(gòu)造函數(shù)內(nèi)訪問不能被外部所訪問(在構(gòu)造函數(shù)內(nèi)使用var聲明的屬性)
- 公有 屬性和方法(或?qū)嵗椒?:對(duì)象外可以訪問到對(duì)象內(nèi)的屬性和方法(在構(gòu)造函數(shù)內(nèi)使用this設(shè)置,或者設(shè)置在構(gòu)造函數(shù)原型對(duì)象上比如Cat.prototype.xxx)
- 靜態(tài) 屬性和方法:定義在構(gòu)造函數(shù)上的方法(比如Cat.xxx),不需要實(shí)例就可以調(diào)用(例如Object.assign())
在ES6之后,存在class這個(gè)語(yǔ)法糖類。當(dāng)你使用class的時(shí)候,它會(huì)默認(rèn)調(diào)用constructor這個(gè)函數(shù),來接收一些參數(shù),并構(gòu)造出一個(gè)新的實(shí)例對(duì)象(this)并將它返回,因此它被稱為constructor構(gòu)造方法(函數(shù))。
私有變量、函數(shù)
在函數(shù)內(nèi)部定義的變量和函數(shù)如果不對(duì)外提供接口,那么外部將無(wú)法訪問到,也就是變?yōu)樗接凶兞亢退接泻瘮?shù)。
function Obj() {var a = 0 //私有變量var fn = function() {//私有函數(shù)} }var o = new Obj() console.log(o.a) //undefined console.log(o.fn) //undefined靜態(tài)變量、函數(shù)
當(dāng)定義一個(gè)函數(shù)后通過 “.”為其添加的屬性和函數(shù),通過對(duì)象本身仍然可以訪問得到,但是其實(shí)例卻訪問不到,這樣的變量和函數(shù)分別被稱為靜態(tài)變量和靜態(tài)函數(shù)。
function Obj() {}Obj.a = 0 //靜態(tài)變量Obj.fn = function() {//靜態(tài)函數(shù) }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實(shí)例變量、函數(shù)
在面向?qū)ο缶幊讨谐艘恍?kù)函數(shù)我們還是希望在對(duì)象定義的時(shí)候同時(shí)定義一些屬性和方法,實(shí)例化后可以訪問,JavaScript也能做到這樣。
function Obj(){this.a=[]; //實(shí)例變量this.fn=function(){ //實(shí)例方法 } }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測(cè)試
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) }請(qǐng)寫出以下輸出結(jié)果:
Foo.getName() getName() Foo().getName() getName() new Foo.getName() new Foo().getName() new new Foo().getName()解讀:首先定義了一個(gè)叫 Foo 的函數(shù),之后為 Foo 創(chuàng)建了一個(gè)叫 getName 的靜態(tài)屬性存儲(chǔ)了一個(gè)匿名函數(shù),之后為 Foo 的原型對(duì)象新創(chuàng)建了一個(gè)叫 getName 的匿名函數(shù)。之后又通過函數(shù)變量表達(dá)式創(chuàng)建了一個(gè) getName 的函數(shù),最后再聲明一個(gè)叫 getName 函數(shù)。
先來劇透一下答案,再來看看具體分析
//答案: 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 函數(shù)上存儲(chǔ)的靜態(tài)屬性,自然是 2
- 第二問:直接調(diào)用 getName 函數(shù)。既然是直接調(diào)用那么就是訪問當(dāng)前上文作用域內(nèi)的叫 getName 的函數(shù),所以跟 1 2 3 都沒什么關(guān)系。但是此處有兩個(gè)坑,一是變量聲明提升,二是函數(shù)表達(dá)式。關(guān)于函數(shù)變量提示,此處省略一萬(wàn)字。。。。題中代碼最終執(zhí)行時(shí)的是
- 第三問:?Foo().getName(); 先執(zhí)行了 Foo 函數(shù),然后調(diào)用 Foo 函數(shù)的返回值對(duì)象的 getName 屬性函數(shù)。這里 Foo 函數(shù)的返回值是 this,this 指向 window 對(duì)象。所以第三問相當(dāng)于執(zhí)行 window.getName()。 然而這里 Foo 函數(shù)將此變量的值賦值為function(){alert(1)}。
- 第四問:直接調(diào)用 getName 函數(shù),相當(dāng)于 window.getName(),答案和前面一樣。
后面三問都是考察 js 的運(yùn)算符優(yōu)先級(jí)問
面向?qū)ο?--- 繼承
繼承:繼承就是子類可以使用父類的所有功能,并且對(duì)這些功能進(jìn)行擴(kuò)展。
比如我有個(gè)構(gòu)造函數(shù)A,然后又有個(gè)構(gòu)造函數(shù)B,但是B想要使用A里的一些屬性和方法,一種辦法就是讓我們自身化身為CV俠,復(fù)制粘貼一波。還有一種就是利用繼承,我讓B直接繼承了A里的功能,這樣我就能用它了。
- 1. 原型鏈繼承
- 2. 構(gòu)造繼承
- 3. 組合繼承
- 4. 寄生組合繼承
- 5. 原型式繼承
- 6. 寄生繼承
- 7. 混入式繼承
- 8. class中的extends繼承
原型鏈繼承
instanceof 關(guān)鍵字
instanceof 運(yùn)算符用于檢測(cè)構(gòu)造函數(shù)的 prototype 屬性是否出現(xiàn)在某個(gè)實(shí)例對(duì)象的原型鏈上。?
A instanceof B?
實(shí)例對(duì)象A instanceof 構(gòu)造函數(shù)B
檢測(cè)A的原型鏈(__proto__)上是否有B.prototype,有則返回true,否則返回false
class中的繼承:extends、super
面向?qū)ο?---?多態(tài)
多態(tài)的實(shí)際含義是:同一操作作用于不同的對(duì)象上,可以產(chǎn)生不同的解釋和不同的執(zhí)行結(jié)果。
class中的多態(tài):extends、super
對(duì)于js多態(tài)的詳細(xì)解釋:🍃【何不三連】JS面向?qū)ο笞詈笠粡?多態(tài)篇(羽化升仙) - 掘金
this 與 new
this 永遠(yuǎn)指向最后調(diào)用它的那個(gè)對(duì)象
this,是指當(dāng)前的本身,在非嚴(yán)格模式下this指向的是全局對(duì)象window,而在嚴(yán)格模式下會(huì)綁定到undefined。
this?的?5種綁定方式:
- 默認(rèn)綁定 ( 非嚴(yán)格模式下this指向全局對(duì)象,嚴(yán)格模式下 this 會(huì)綁定到 undefined )
- 隱式綁定 ( 當(dāng)函數(shù)引用有上下文對(duì)象時(shí), 如 obj.foo() 的調(diào)用方式,foo內(nèi)的 this指向obj )
- 顯示綁定 ( 通過 call() 或者 apply() 方法直接指定 this 的綁定對(duì)象, 如 foo.call(obj) )
- new 綁定
- 箭頭函數(shù)綁定 ( this 的指向由外層作用域決定的,并且指向函數(shù)定義時(shí)的 this,而不是執(zhí)行時(shí)的 this)
再次強(qiáng)調(diào):
- this 永遠(yuǎn)指向最后調(diào)用它的那個(gè)對(duì)象
- 匿名函數(shù)的 this 永遠(yuǎn)指向 window
- 使用 call() 或者 apply() 的函數(shù)會(huì)直接執(zhí)行
- bing() 是創(chuàng)建一個(gè)新的函數(shù),需要手動(dòng)調(diào)用才會(huì)執(zhí)行
- 如果 call、appy、bind 接收到的第一個(gè)參數(shù)是空或者 null,undefine 的話,則會(huì)忽略這個(gè)參數(shù)
- forEach、map、filter 函數(shù)的第二個(gè)參數(shù)也是能顯式綁定 this 的
默認(rèn)綁定
沒有綁定到任何對(duì)象的 變量、函數(shù)、屬性 等,都是 綁定到 window 對(duì)象。
var a = 10; // a 屬于 window function foo(){console.log(this.a); // this 指向 windowconsole.log(this); // window 對(duì)象console.log(window) // window 對(duì)象console.log(this === window); // true } foo(); // 10 console.log(window.a); // 10 console.log(window.a === this.a); // true使用 let 、const 聲明的 變量,不會(huì)綁定到 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 函數(shù)屬于 window,inner 是 foo 的內(nèi)嵌函數(shù),所以 this 指向 window?
隱式綁定
示例代碼:
function foo(){console.log(this.a); } var obj = {a:1, foo}; // var obj = {foo} 等價(jià)于 var obj = {foo: foo} var a = 2; obj.foo(); // 打印 1隱式丟失:就是被隱式綁定的函數(shù),在特定的情況下會(huì)丟失綁定對(duì)象。
特定情況是指:
- 1. 使用另一個(gè)變量來給函數(shù)取別名
- 2. 或者 將函數(shù)作為參數(shù)傳遞時(shí)會(huì)被隱式賦值,回調(diào)函數(shù)丟失 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 調(diào)用的,即 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結(jié)論:如果把一個(gè)函數(shù)當(dāng)成參數(shù)傳遞到另一個(gè)函數(shù)的時(shí)候,也會(huì)發(fā)生隱式丟失的問題,且與包裹著它的函數(shù)的 this 指向無(wú)關(guān)。在非嚴(yán)格模式下,會(huì)把該函數(shù)的 this 綁定到 window 上。嚴(yán)格模式下綁定到 undefine
示例:
var obj1 = {a:1 }; var obj2 = {a:2,foo1: function(){console.log(this.a); // 打印 2},foo2: function(){setTimeout(function(){console.log(this); // 打印 window 對(duì)象console.log(this.a); // 打印 3}, 0);} }; var a =3; obj2.foo1(); obj2.foo2(); // this 永遠(yuǎn)指向最后調(diào)用它的那個(gè)對(duì)象。。。call, apply, bind
使用 call() 或者 apply()??的函數(shù)是會(huì)直接執(zhí)行的。
bind() 是創(chuàng)建一個(gè)新的函數(shù),需要手動(dòng)調(diào)用才會(huì)執(zhí)行
- 1. 都是對(duì)函數(shù)的操作,使用方式:函數(shù).call
- 2. 都是用來改變函數(shù)的 this 對(duì)象的指向的。
- 3. 第一個(gè)參數(shù)都是 this 要指向的對(duì)象。
- 4. 都可以利用后續(xù)參數(shù)傳參。
- 5. call 接受函數(shù)傳參方式為:fn.call(this, 1, 2, 3)
- 6. apply 接受函數(shù)傳參方式為:fn.apply(this,[1, 2, 3])
- 7. bind 的返回值為一個(gè)新的函數(shù),需要再次調(diào)用: fn.bind(this)(1, 2, 3)
專利局( JS 混淆,大量運(yùn)用原型鏈 )( F12 打開控制臺(tái)?):http://cpquery.sipo.gov.cn/
示例( 函數(shù)內(nèi)嵌套的函數(shù)中的 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 對(duì)象function inner(){console.log(this); // 打印 window 對(duì)象console.log(this.a); // 打印 window 對(duì)象 的 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的返回值是一個(gè)函數(shù),再進(jìn)行call,改變this指向//結(jié)果: // 2 ---> foo(); 輸出結(jié)果 // 1 ---> foo().call(obj); 改變this指向 obj, 輸出 1 // 2 ---> // 1 ---> foo 的返回值,再進(jìn)行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 同時(shí)出現(xiàn)
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);箭頭函數(shù)
箭頭函數(shù)綁定中:this 的指向由外層作用域決定的,并且指向函數(shù)定義時(shí)的 this,而不是執(zhí)行時(shí)的 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()();結(jié)果: 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();結(jié)果: person1 person1 window示例:( 箭頭函數(shù) 與 call 結(jié)合 )
箭頭函數(shù)里面的 this 是由外層作用域來決定的,并且指向函數(shù)定義時(shí)的 this,而不是執(zhí)行時(shí)的 this
字面量創(chuàng)建的對(duì)象,作用域是 window,如果里面有箭頭函數(shù)屬性的話, this??指向的是 window
構(gòu)造函數(shù)創(chuàng)建的對(duì)象,作用域是可以理解為是這個(gè)構(gòu)造函數(shù),且這個(gè)構(gòu)造函數(shù)的 this 是指向新建的對(duì)象的,因此 this 指向這個(gè)對(duì)象
箭頭函數(shù)的 this 是無(wú)法通過 bind、call、apply 來直接修改,但是可以用過改變作用域中 this 的指向來間接修改
new?的過程中到底發(fā)生了什么?
1. 新生成了一個(gè)對(duì)象
2. 鏈接到原型
3. 綁定 this
4. 返回新對(duì)象
更多可以參看 《JavaScript高級(jí)程序設(shè)計(jì) 第4版》
js 逆向技巧
From:js逆向技巧 - 走看看
一、總結(jié)
1. 全局關(guān)鍵詞搜索
2. 事件監(jiān)聽斷點(diǎn)
3. 堆棧跟蹤
非常頻繁使用的事件監(jiān)聽斷點(diǎn):script、XHR
一般頻繁使用的:Dom斷點(diǎn)、Control、timer
不太常用但是偶爾會(huì)用到的:Mouse
一句話總結(jié):
- 1. 搜索:全局搜索、代碼內(nèi)搜索
- 2. debug:常規(guī) debug、XHR debug、行為 debug
- 3. 查看請(qǐng)求調(diào)用的堆棧
- 4. 執(zhí)行 堆內(nèi)存 中的函數(shù)
- 5. 修改 堆棧 中的參數(shù)值
- 6. 寫 js 代碼
- 7. 打印 windows 對(duì)象的值
- 8. 勾子:cookie鉤子、請(qǐng)求鉤子、header鉤子
二、js逆向技巧
博客對(duì)應(yīng)課程的視頻位置:
當(dāng)我們抓取網(wǎng)頁(yè)端數(shù)據(jù)時(shí),經(jīng)常被加密參數(shù)、加密數(shù)據(jù)所困擾,如何快速定位這些加解密函數(shù),尤為重要。本片文章是我逆向js時(shí)一些技巧的總結(jié),如有遺漏,歡迎補(bǔ)充。
所需環(huán)境:Chrome瀏覽器
1. 搜索
1.1 全局搜索 ( Ctrl + shift + f )
適用于根據(jù)關(guān)鍵詞快速定位關(guān)鍵文件及代碼
當(dāng)前頁(yè)面 右鍵 ---> 檢查,彈出檢查工具
搜索支持 關(guān)鍵詞、正則表達(dá)式
1.2 代碼內(nèi)搜索 ( Ctrl + f )
適用于根據(jù)關(guān)鍵詞快速定位關(guān)鍵代碼
點(diǎn)擊代碼,然后按 ctrl+f 或 command+f 調(diào)出搜索框。搜索支持 關(guān)鍵詞、css表達(dá)式、xpath
2. debug
2.1 常規(guī) debug
適用于分析關(guān)鍵函數(shù)代碼邏輯
a、埋下斷點(diǎn)
b、調(diào)試
如圖所示,標(biāo)記了 1 到 6,下面分別介紹其含義
- 1. 執(zhí)行到下一個(gè)端點(diǎn)
- 2. 執(zhí)行下一步,不會(huì)進(jìn)入所調(diào)用的函數(shù)內(nèi)部
- 3. 進(jìn)入所調(diào)用的函數(shù)內(nèi)部
- 4. 跳出函數(shù)內(nèi)部
- 5. 一步步執(zhí)行代碼,遇到有函數(shù)調(diào)用,則進(jìn)入函數(shù)
- 6.Call Stack 為代碼調(diào)用的堆棧信息,代碼執(zhí)行順序?yàn)橛上轮辽?#xff0c;這對(duì)于著關(guān)鍵函數(shù)前后調(diào)用關(guān)系很有幫助
2.2 XHR debug
匹配url中關(guān)鍵詞,匹配到則跳轉(zhuǎn)到參數(shù)生成處,適用于url中的加密參數(shù)全局搜索搜不到,可采用這種方式攔截
2.3 行為 debug
適用于點(diǎn)擊按鈕時(shí),分析代碼執(zhí)行邏輯
如圖所示,可快速定位點(diǎn)擊探索按鈕后,所執(zhí)行的js。
3 查看請(qǐng)求調(diào)用的堆棧
可以在 Network 選項(xiàng)卡下,該請(qǐng)求的 Initiator 列里看到它的調(diào)用棧,調(diào)用順序由上而下:
4. 執(zhí)行堆內(nèi)存中的函數(shù)
當(dāng) debug 到某一個(gè)函數(shù)時(shí),我們想主動(dòng)調(diào)用,比如傳遞下自定義的參數(shù),這時(shí)可以在檢查工具里的 console 里調(diào)用
此處要注意,只有debug打這個(gè)函數(shù)時(shí),控制臺(tái)里才可以調(diào)用。如果想保留這個(gè)函數(shù),可使用this.xxx=xxx 的方式。之后調(diào)用時(shí)無(wú)需debug到xxx函數(shù),直接使用this.xxx 即可。
5. 修改堆棧中的參數(shù)值
6. 寫 js 代碼
7. 打印 windows 對(duì)象的值
在 console 中輸入如下代碼,如只打印 _$ 開頭的變量值
for (var p in window) {if (p.substr(0, 2) !== "_$") continue;console.log(p + " >>> " + eval(p)) }8. 勾子
以 chrome 插件的方式,在匹配到關(guān)鍵詞處插入斷點(diǎn)
8.1 cookie 鉤子
用于定位 cookie 中關(guān)鍵參數(shù)生成位置
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);當(dāng) cookie 中匹配到了?TSdc75a61a, 則插入斷點(diǎn)。
8.2 請(qǐng)求鉤子
用于定位請(qǐng)求中關(guān)鍵參數(shù)生成位置
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);當(dāng)請(qǐng)求的 url 里包含?MmEwMD?時(shí),則插入斷點(diǎn)
8.3 header 鉤子
用于定位 header 中關(guān)鍵參數(shù)生成位置
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);當(dāng) header 中包含?Authorization?時(shí),則插入斷點(diǎn)
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、如圖所示,創(chuàng)建一個(gè)文件夾,文件夾中創(chuàng)建一個(gè)鉤子函數(shù)文件inject.js 及 插件的配置文件 mainfest.json 即可
b、打開chrome 的擴(kuò)展程序, 加載已解壓的擴(kuò)展程序,選擇步驟1創(chuàng)建的文件夾即可
c、切換回原網(wǎng)頁(yè),刷新頁(yè)面,若鉤子函數(shù)關(guān)鍵詞匹配到了,則觸發(fā)debug
刷新型 cookie 反爬
瑞數(shù)、加固樂
調(diào)試干擾
無(wú)限 debugger
無(wú)限 debugger,還有一個(gè)非常靚麗的名字:debugger地獄
最關(guān)鍵的一點(diǎn):無(wú)限 debugger 不可能無(wú)限,否則瀏覽器會(huì)卡死
實(shí)現(xiàn)debugger的方案: Function,關(guān)鍵字,eval制作虛擬機(jī)
【不可混淆】
? ? ? ? 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"))
無(wú)限 debugger 的處理實(shí)際上很簡(jiǎn)單,有以下幾種情況
1. ? 無(wú)限 debugger 貫穿全局:干掉定時(shí)器等全局事件(置空或重寫)
2. ? 無(wú)限 debugger 在加密邏輯之前。想要調(diào)試到函數(shù)入口,必須越過這個(gè)無(wú)限 debugger
- 針對(duì)靜態(tài)文件/偽動(dòng)態(tài)文件(大部分都是這個(gè)情況)
- 用 fiddler ?Autoresponse 刪掉 debugger
- 可以右鍵 never
- 針對(duì)真動(dòng)態(tài)文件或 Autoresponse 失效或刪掉 debugger 邏輯很繁瑣的情況下
? ? 1. 如果是 Function 原理的 debugger,可以重寫 函數(shù)構(gòu)造器 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 型的構(gòu)造器,可以重構(gòu) eval 函數(shù) eval_bak = eval window.eval = function(a){if('debugger'===a){}else{eval_bak(a);} } ????3. 如果是定時(shí)器,并且 2失效了,可以重構(gòu)定時(shí)器
????4. 在以上方式都失效時(shí),向上找堆棧,在進(jìn)入無(wú)限debugger之前打上斷點(diǎn)將觸發(fā)無(wú)限debugger 的函數(shù)置空(最麻煩,但是適用性最廣)
3. ? ?無(wú)限 debugger 在加密邏輯之后:不用管,script/ 第一行斷點(diǎn)打上,從頭開始
控制臺(tái) 檢測(cè)
:https://www.cnblogs.com/wuxianyu/p/14523102.html
?:https://match.yuanrenxue.com/match/16
打個(gè) script 斷點(diǎn),當(dāng)跳轉(zhuǎn)到主頁(yè)時(shí),查看 源碼,代碼如下:
<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>上面過控制臺(tái)檢測(cè)
e.open = function(){} ConsoleManager.onOpen = function(){}// 或者 window.open = function(){} setInterval = function(){}打上斷點(diǎn)之后,當(dāng)要運(yùn)行跳轉(zhuǎn)時(shí),直接把跳轉(zhuǎn) 置為 空函數(shù) 即可
控制臺(tái)檢測(cè)原理:原理就是使用 console 的特性,只有在控制臺(tái)打開的時(shí)候,console 才會(huì)對(duì)一些信息和內(nèi)容進(jìn)行打印。如果設(shè)置一個(gè)定時(shí)器,在定時(shí)器中不斷循環(huán)獲取一個(gè)參數(shù)x,并且對(duì)這個(gè)參數(shù)x進(jìn)行 hook,利用 Object.defineProperty 處理其get 屬性,那么當(dāng)打開控制臺(tái)的一瞬間,console 就會(huì)生效,獲取屬性并觸發(fā) hook,執(zhí)行 Object.defineProperty 內(nèi)的邏輯。
chrome 瀏覽器有這個(gè)特性, firefox 沒有這個(gè)特性,可以多換幾個(gè)瀏覽器試一下。
<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>內(nèi)存爆破原理
其實(shí)可以叫做 "蜜罐內(nèi)存爆破",當(dāng)檢測(cè)出來時(shí)( 例如:js 代碼進(jìn)行了格式化,導(dǎo)致特征字符穿不匹配,使用 js 正則檢測(cè)出來??),走到蜜罐里面一直循環(huán)。
對(duì)于 js 格式化被正則檢測(cè)出來,可以分段進(jìn)行 js 格式化,逐漸排出,找到關(guān)鍵位置,然后讓正則檢測(cè)時(shí)返回 true?
內(nèi)存爆破,指 js 通過死循環(huán)/頻繁操作數(shù)據(jù)庫(kù)( 包括cookie ) / 頻繁調(diào)取 history 等方式,使瀏覽器崩潰的一種反調(diào)試手段。
關(guān)鍵詞:頻繁
還有一種特性情況:js文件很大,電腦內(nèi)存不足(這種情況在調(diào)試層面幾乎無(wú)解)
特點(diǎn):
? ? ? ? ?1. ? ?正常運(yùn)行時(shí),一切正常
?? ? ? ? 2. ? ?調(diào)試時(shí)利用時(shí)間差,調(diào)試特點(diǎn)等講控制流推入循環(huán)
?? ? ? ? 3.? ? ob混淆內(nèi)存爆破原理:利用正則/toString() 判斷代碼是否進(jìn)行格式化
?? ? ? ? 4. ? ?利用瀏覽器指紋判斷是否為瀏覽器環(huán)境
總結(jié)
以上是生活随笔為你收集整理的JavaScript 逆向 ( 一 ) --- JavaScript 语法基础的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 开源 Python网络爬虫框架 Scra
- 下一篇: SpringBoot 自带工具类~Col