日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 前端技术 > javascript >内容正文

javascript

JavaScript 逆向 ( 一 ) --- JavaScript 语法基础

發(fā)布時(shí)間:2024/7/23 javascript 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JavaScript 逆向 ( 一 ) --- JavaScript 语法基础 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

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; })); // -1

forEach

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:
x => {if (x > 0) {return x * x;}else {return - x * x;} }

如果參數(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); // 25

generator

: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ì)照第六部分的代碼看,你一定就明白了

function Shape(ax,ay) {var _this = this ; // 把this保存下來,以后用_this代替this,這樣就不會(huì)被this弄暈了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(); }

示例:

/***定義類***/ 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、用原型方式來定義類的方法。
<script>function Person(name){this.name = name;}//原型的特性可以讓對(duì)象實(shí)例共享getName方法Person.prototype.getName = function(){return " I'm " + this.name;} </script>

這樣,我們就既可以構(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, dog

constructor 方法用來創(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ù)。

class A {constructor() {this.name = '小明'} } const b = new A() b.constructor === A.prototype.constructor // true

當(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)方法可以被子類繼承。

class A {// 靜態(tài)方法static show() {console.log('hi')} } class B extends A {} const c = new A() c.show() // c.show is not a function B.show() // hi

所有在類中定義的方法會(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 // set123

Class 的 繼承

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
class A {constructor () {this.name = '小米'this.show = this.show.bind(this)}show () {console.log(this.name)} }
  • 使用箭頭函數(shù)
class A {constructor () {this.name = '小米'this.show = () => this}show () {console.log(this.name)} }

區(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) // true

hasOwnProperty(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:15

toString() 方法

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,然后通過 keysgetOwnPropertyNames 兩個(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ì)象的原型上,還是可以添加屬性的。
function Person(name) {this.name = name; }var person = new Person("Jack"); Object.preventExtensions(person);delete person.name; console.log(person.name);//undefinedPerson.prototype.age = 15; console.log(person.age);//15

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__: Object

Object.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.getOwnPropertyNamesObject.keys?區(qū)別

Object.getOwnPropertyNamesObject.keys 的區(qū)別,

  • Object.keys 只適用于可枚舉的屬性,
  • Object.getOwnPropertyNames 返回對(duì)象自動(dòng)的全部屬性名稱。
'use strict'; (function () {if (!Object.getOwnPropertyNames) {console.log('瀏覽器不支持getOwnPropertyNames');return;}//人類的構(gòu)造函數(shù)var person = function (name, age, sex) {this.name = name;this.age = age;this.sex = sex;this.sing = function () {console.log('sing');}}//new 一個(gè)ladygagavar gaga = new person('ladygaga', 26, 'girl');//給嘎嘎發(fā)放一個(gè)不可枚舉的身份證Object.defineProperty(gaga, 'id', {value: '1234567890',enumerable: false});//查看gaga的個(gè)人信息var arr = Object.getOwnPropertyNames(gaga);document.write(arr); //output: name,age,sex,sing,iddocument.write('</br>');//注意和getOwnPropertyNames的區(qū)別,不可枚舉的id沒有輸出var arr1 = Object.keys(gaga);document.write(arr1); //output: name,age,sex,sing })();

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ù)。

var obj = {a: 1}; Object.assign(obj) === obj // true

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ì)象的引用。

var obj1 = {a: {b: 1}}; var obj2 = Object.assign({}, obj1); obj1.a.b = 2; obj2.a.b // 2

上面代碼中,源對(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

var obj = {}; obj.toString();

上面定義了一個(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) //true

2.構(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)存空間
function Cat(){this.cat = 'cat'; } guaiguai = new Cat(); // guaiguai 是 Cat 的實(shí)例化對(duì)象 guaiguai.__proto__ // 因?yàn)間uaiguai是Cat的實(shí)例化對(duì)象,// 所以 __proto__ 指向的是 構(gòu)造函數(shù)的 prototype// 這個(gè)就叫 構(gòu)造函數(shù)的原型對(duì)象 guaiguai.__proto__ === Cat.prototype // true

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í)的是
function Foo() {getName = function() {alert(1)}return this } var getName //只提升變量聲明 function getName() {alert(5) } //提升函數(shù)聲明,覆蓋var的聲明Foo.getName = function() {alert(2) } Foo.prototype.getName = function() {alert(3) } getName = function() {alert(4) } //最終的賦值再次覆蓋function getName聲明
  • 第三問:?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(); // 打印 1

foo 函數(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 的指向來間接修改

var name = 'window'; var obj1 = {name: 'obj1',foo1: function(){console.log(this.name);return ()=>{ console.log(this.name); }},foo2: ()=>{console.log(this.name);return function(){console.log(this.name);}} } var obj2 = { name: 'obj2' }obj1.foo1.call(obj2)() obj1.foo1().call(obj2) obj1.foo2.call(obj2)() obj1.foo2().call(obj2)結(jié)果: obj2 obj2 obj1 obj1 window window window obj2

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)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。