JavaScript基礎入門05


JavaScript 基礎入門05

嚴格模式

運行js代碼的過程中,除了正常的代碼運行模式以外,還存在嚴格模式(strict mode)。意義在於讓代碼采用更加嚴格的JavaScript語法。

需要注意的是,相同的代碼,在不同的模式下可能會有不同的結果。很多時候在正常模式下能夠運行的代碼在嚴格模式下未必能夠運行。

嚴格模式的設計目的

早期的 JavaScript 語言有很多設計不合理的地方,但是為了兼容以前的代碼,又不能改變老的語法,只能不斷添加新的語法,引導程序員使用新語法。

嚴格模式是從 ES5 進入標准的,主要目的有以下幾個。

  • 明確禁止一些不合理、不嚴謹的語法,減少 JavaScript 語言的一些怪異行為。
  • 增加更多報錯的場合,消除代碼運行的一些不安全之處,保證代碼運行的安全。
  • 提高編譯器效率,增加運行速度。
  • 為未來新版本的 JavaScript 語法做好鋪墊。

總之,嚴格模式體現了 JavaScript 更合理、更安全、更嚴謹的發展方向。

如何開啟使用嚴格模式

在js代碼中想要開啟嚴格模式,需要通過一行字符串來開啟:

'use strict';

需要注意的是,老版本的引擎會把它當作一行普普通通的字符串,並且忽略。而新版本的瀏覽器引擎可以很好的支持嚴格模式。

嚴格模式即可以用於當前的整個腳本,也可以用於單個函數。

1、嚴格模式應用於整個腳本文件
use strict放在腳本文件的第一行,整個腳本都將以嚴格模式運行。如果這行語句不在第一行就無效,整個腳本會以正常模式運行。(嚴格地說,只要前面不是產生實際運行結果的語句,use strict可以不在第一行,比如直接跟在一個空的分號后面,或者跟在注釋后面。)

<script>
  'use strict';
  console.log('這是嚴格模式');
</script>

<script>
  console.log('這是正常模式');
</script>

在上面的代碼中,一個網頁文件中有兩段JavaScript代碼。前面的script標簽里面開啟了嚴格模式,后面的script標簽里面則沒有開啟嚴格模式。

如果use strict寫成下面這樣,則不起作用,嚴格模式必須從代碼一開始就生效。

<script>
  console.log('這是正常模式');
  'use strict';
</script>

2、嚴格模式應用在單個函數
use strict放在函數當中的第一行,則整個函數以嚴格模式來運行。

function strict() {
  'use strict';
  return '這是嚴格模式';
}

function strict2() {
  'use strict';
  function f() {
    return '這也是嚴格模式';
  }
  return f();
}

function notStrict() {
  return '這是正常模式';
}

有時,需要把不同的腳本合並在一個文件里面。如果一個腳本是嚴格模式,另一個腳本不是,它們的合並就可能出錯。嚴格模式的腳本在前,則合並后的腳本都是嚴格模式;如果正常模式的腳本在前,則合並后的腳本都是正常模式。這兩種情況下,合並后的結果都是不正確的。這時可以考慮把整個腳本文件放在一個立即執行的匿名函數之中。

(function () {
  'use strict';
  // some code here
})();

顯式報錯

嚴格模式使得js的語法變得更加的嚴格。更多的操作都會顯式的報錯。在這其中,有一些操作,在正常模式下只會默默的失敗,但是不會報錯。

1、只讀屬性不可寫
嚴格模式下,設置字符串的length屬性,會報錯。

'use strict';
'abc'.length = 5;
// TypeError: Cannot assign to read only property 'length' of string 'abc'

上面代碼報錯,因為length是只讀屬性,嚴格模式下不可寫。正常模式下,改變length屬性是無效的,但不會報錯。

嚴格模式下,對只讀屬性賦值,或者刪除不可配置(non-configurable)屬性都會報錯。

// 對只讀屬性賦值會報錯
'use strict';
Object.defineProperty({}, 'a', {
  value: 37,
  writable: false
});
obj.a = 123;
// TypeError: Cannot assign to read only property 'a' of object #<Object>

// 刪除不可配置的屬性會報錯
'use strict';
var obj = Object.defineProperty({}, 'p', {
  value: 1,
  configurable: false
});
delete obj.p
// TypeError: Cannot delete property 'p' of #<Object>

2、禁止擴展的對象不可擴展。

嚴格模式下,對禁止擴展的對象添加新屬性,會報錯。

'use strict';
var obj = {};
Object.preventExtensions(obj);
obj.v = 1;
// Uncaught TypeError: Cannot add property v, object is not extensible

上面代碼中,obj對象禁止擴展,添加屬性就會報錯。

3、eval 、 arguments不可作為標識名
嚴格模式下,使用eval或者arguments作為標識名,將會報錯。下面的語句都會報錯。

'use strict';
var eval = 17;
var arguments = 17;
var obj = { set p(arguments) { } };
try { } catch (arguments) { }
function x(eval) { }
function arguments() { }
var y = function eval() { };
var f = new Function('arguments', "'use strict'; return 17;");
// SyntaxError: Unexpected eval or arguments in strict mode

4、函數不能存在重名的參數
正常模式下,如果函數有多個重名的參數,可以用arguments[i]讀取。嚴格模式下,這屬於語法錯誤。

function f(a, a, b) {
  'use strict';
  return a + b;
}
// Uncaught SyntaxError: Duplicate parameter name not allowed in this context

5、進制八進制的前綴0表示法
正常模式下,整數的第一位如果是0,表示是八進制數,比如0100等於十進制的64。

在嚴格模式下則進制使用這種表示法,整數第一位是0,將會報錯。

'use strict';
var n = 0100;
// Uncaught SyntaxError: Octal literals are not allowed in strict mode.

6、 全局變量顯式的聲明
正常模式當中,如果一個變量沒有聲明就賦值,默認是全局變量。嚴格模式下禁止使用這種語法,全局變量必須顯式的聲明。

'use strict';

v = 1; // 報錯,v未聲明

for (i = 0; i < 2; i++) { // 報錯,i 未聲明
  // ...
}

function f() {
  x = 123;
}
f() // 報錯,未聲明就創建一個全局變量

因此,嚴格模式下,變量都必須先聲明,然后再使用。

7、嚴格模式下禁止this關鍵字指向全局對象
正常模式下,函數內部的this可能會指向全局對象,嚴格模式禁止這種用法,避免無意間創造全局變量。

// 正常模式
function f() {
  console.log(this === window);
}
f() // true

// 嚴格模式
function f() {
  'use strict';
  console.log(this === undefined);
}
f() // true

上面代碼中,嚴格模式的函數體內部thisundefined

這種限制對於構造函數尤其有用。使用構造函數時,有時忘了加new,這時this不再指向全局對象,而是報錯。

function f() {
  'use strict';
  this.a = 1;
};

f();// 報錯,this 未定義

嚴格模式下,函數直接調用時(不使用new調用),函數內部的this表示undefined(未定義),因此可以用call、apply和bind方法,將任意值綁定在this上面。正常模式下,this指向全局對象,如果綁定的值是非對象,將被自動轉為對象再綁定上去,而null和undefined這兩個無法轉成對象的值,將被忽略。

// 正常模式
function fun() {
  return this;
}

fun() // window
fun.call(2) // Number {2}
fun.call(true) // Boolean {true}
fun.call(null) // window
fun.call(undefined) // window

// 嚴格模式
'use strict';
function fun() {
  return this;
}

fun() //undefined
fun.call(2) // 2
fun.call(true) // true
fun.call(null) // null
fun.call(undefined) // undefined

上面代碼中,可以把任意類型的值,綁定在this上面。

8、嚴格模式下禁止使用fn.callee、fn.caller
在嚴格模式下,函數體內禁止使用fn.callerfn.arguments,否則會報錯。

這意味着在函數體內不能調用棧了。

function f1() {
  'use strict';
  f1.caller;    // 報錯
  f1.arguments; // 報錯
}

f1();

9、禁止刪除變量
嚴格模式下無法刪除變量,如果使用delete命令刪除一個變量,會報錯。只有對象的屬性,且屬性的描述對象的configurable屬性設置為true,才能被delete命令刪除。

'use strict';
var x;
delete x; // 語法錯誤

var obj = Object.create(null, {
  x: {
    value: 1,
    configurable: true
  }
});
delete obj.x; // 刪除成功

10、靜態綁定
JavaScript 語言的一個特點,就是允許“動態綁定”,即某些屬性和方法到底屬於哪一個對象,不是在編譯時確定的,而是在運行時(runtime)確定的。

嚴格模式對動態綁定做了一些限制。某些情況下,只允許靜態綁定。也就是說,屬性和方法到底歸屬哪個對象,必須在編譯階段就確定。這樣做有利於編譯效率的提高,也使得代碼更容易閱讀,更少出現意外。

在嚴格模式下是禁止使用with語句的。

在正常的模式下,JavaScript語言有兩種變量作用域(scope):全局作用域和函數作用域。
在嚴格模式下多出了第三種作用域:eval作用域。

在正常模式下,eval語句的作用域,取決於它處於全局作用域,還是函數作用域。嚴格模式下,eval語句本身就是一個作用域,不能夠在其所運行的作用域創建新的變量了,簡單的說,eval所生成的變量只能用於eval內部。

(function () {
  'use strict';
  var x = 2;
  console.log(eval('var x = 5; x')) // 5
  console.log(x) // 2
})()

上面代碼中,由於eval語句內部是一個獨立作用域,所以內部的變量x不會泄露到外部。

注意,如果希望eval語句也使用嚴格模式,有兩種方式。

// 方式一
function f1(str){
  'use strict';
  return eval(str);
}
f1('undeclared_variable = 1'); // 報錯

// 方式二
function f2(str){
  return eval(str);
}
f2('"use strict";undeclared_variable = 1')  // 報錯

上面兩種寫法,eval內部使用的都是嚴格模式。

11、arguments不再追蹤參數的變化
變量arguments代表函數的參數。嚴格模式下,函數內部改變參數與arguments的聯系被切斷了,兩者不再存在聯動關系。

function f(a) {
  a = 2;
  return [a, arguments[0]];
}
f(1); // 正常模式為[2, 2]

function f(a) {
  'use strict';
  a = 2;
  return [a, arguments[0]];
}
f(1); // 嚴格模式為[2, 1]

上面代碼中,改變函數的參數,不會反應到arguments對象上來。

12、嚴格模式表示向下一個版本過渡
JavaScript 語言的下一個版本是 ECMAScript 6,為了平穩過渡,嚴格模式引入了一些 ES6 語法。

ES6 會引入塊級作用域。為了與新版本接軌,ES5 的嚴格模式只允許在全局作用域或函數作用域聲明函數。也就是說,不允許在非函數的代碼塊內聲明函數。

'use strict';
if (true) {
  function f1() { } // 語法錯誤
}

for (var i = 0; i < 5; i++) {
  function f2() { } // 語法錯誤
}

上面代碼在if代碼塊和for代碼塊中聲明了函數,ES5 環境會報錯。

注意,如果是 ES6 環境,上面的代碼不會報錯,因為 ES6 允許在代碼塊之中聲明函數。

13、保留字
為了向將來 JavaScript 的新版本過渡,嚴格模式新增了一些保留字(implements、interface、let、package、private、protected、public、static、yield等)。使用這些詞作為變量名將會報錯。

function package(protected) { // 語法錯誤
  'use strict';
  var implements; // 語法錯誤
}

字符串

字符串的創建

字符串(String)對象是JavaScript原生三大包裝對象之一,用來生成字符串對象。

var s1 = 'abc';
var s2 = new String('abc');

typeof s1 // "string"
typeof s2 // "object"

s2.valueOf() // "abc"

在上面的代碼中,變量s1是字符串,s2是對象。由於s2是字符串對象s2.valueOf方法返回的就是它所對應的原始字符串。

字符串對象是一個類似數組的對象(很像數組,但不是數組)。

new String('abc')
// String {0: "a", 1: "b", 2: "c", length: 3}

(new String('abc'))[1] // "b"

上面代碼中,字符串abc對應的字符串對象,有數值鍵(0、1、2)和length屬性,所以可以像數組那樣取值。

除了用作構造函數,String對象還可以當作工具方法使用,將任意類型的值轉為字符串。

String(true) // "true"
String(5) // "5"

上面代碼將布爾值true和數值5,分別轉換為字符串。

字符串實例方法之常用API

1、 String.prototype.charAt()
charAt方法返回指定位置的字符,參數是從0開始編號的位置。

var str = "hello,world";

console.log(str.charAt(1)); // e

這個方法我們可以使用數組下標來代替。

var str = "hello,world";
console.log(str[1]);//e

需要注意的是,如果參數為負數,或者大於等於字符串的長度,charAt返回空字符串。

2、String.prototype.slice()
slice方法用於從原字符串取出子字符串並返回,不改變原字符串。它的第一個參數是子字符串的開始位置,第二個參數是子字符串的結束位置(不含該位置)。

'JavaScript'.slice(0, 4) // "Java"

如果省略第二個參數,則表示子字符串一直到原字符串結束。

'JavaScript'.slice(4) // "Script"

如果參數是負值,表示從結尾開始倒數計算的位置,即該負值加上字符串長度。

'JavaScript'.slice(-6) // "Script"
'JavaScript'.slice(0, -6) // "Java"
'JavaScript'.slice(-2, -1) // "p"

如果第一個參數大於第二個參數,slice方法返回一個空字符串。

'JavaScript'.slice(2, 1) // ""

3、String.prototype.substring()
substring方法用於從原字符串取出子字符串並返回,不改變原字符串,跟slice方法很相像。它的第一個參數表示子字符串的開始位置,第二個位置表示結束位置(返回結果不含該位置)。

'JavaScript'.substring(0, 4) // "Java"

如果省略第二個參數,則表示子字符串一直到原字符串的結束。

'JavaScript'.substring(4) // "Script"

如果第一個參數大於第二個參數,substring方法會自動更換兩個參數的位置。

'JavaScript'.substring(10, 4) // "Script"
// 等同於
'JavaScript'.substring(4, 10) // "Script"

上面代碼中,調換substring方法的兩個參數,都得到同樣的結果。

如果參數是負數,substring方法會自動將負數轉為0。

'JavaScript'.substring(-3) // "JavaScript"
'JavaScript'.substring(4, -3) // "Java"

上面代碼中,第二個例子的參數-3會自動變成0,等同於'JavaScript'.substring(4, 0)。由於第二個參數小於第一個參數,會自動互換位置,所以返回Java。

由於這些規則違反直覺,因此不建議使用substring方法,應該優先使用slice。

4、String.prototype.indexOf() ,String.prototype.lastIndexOf()

indexOf方法用於確定一個字符串在另一個字符串中第一次出現的位置,返回的結果是匹配開始的位置。如果返回-1,就表示不匹配。

'hello world'.indexOf('o') // 4
'JavaScript'.indexOf('script') // -1

indexOf方法還可以接受第二個參數,表示從該位置開始向后匹配。

'hello world'.indexOf('o', 6) // 7

lastIndexOf方法的用法跟indexOf方法一致,主要的區別是lastIndexOf從尾部開始匹配,indexOf則是從頭部開始匹配。

'hello world'.lastIndexOf('o') // 7

另外,lastIndexOf的第二個參數表示從該位置起向前匹配。

'hello world'.lastIndexOf('o', 6) // 4

5、String.prototype.search(),String.prototype.replace()
search方法的用法基本等於match,但是返回值為匹配的第一個位置。如果沒有找到匹配,則返回-1

'cat, bat, sat, fat'.search('at') // 1

search方法還可以使用正則表達式作為參數。

replace方法用於替換匹配的子字符串,一般情況下只替換第一個匹配(除非使用帶有g修飾符的正則表達式)。

'aaa'.replace('a', 'b') // "baa"

replace方法還可以使用正則表達式作為參數.

6、String.prototype.split()
split方法按照給定規則分割字符串,返回一個由分割出來的子字符串組成的數組。

'a|b|c'.split('|') // ["a", "b", "c"]

如果分割規則為空字符串,則返回數組的成員是原字符串的每一個字符。

'a|b|c'.split('') // ["a", "|", "b", "|", "c"]

如果省略參數,則返回數組的唯一成員就是原字符串。

'a|b|c'.split() // ["a|b|c"]

如果滿足分割規則的兩個部分緊鄰着(即兩個分割符中間沒有其他字符),則返回數組之中會有一個空字符串。

'a||c'.split('|') // ['a', '', 'c']

如果滿足分割規則的部分處於字符串的開頭或結尾(即它的前面或后面沒有其他字符),則返回數組的第一個或最后一個成員是一個空字符串。

'|b|c'.split('|') // ["", "b", "c"]
'a|b|'.split('|') // ["a", "b", ""]

split方法還可以接受第二個參數,限定返回數組的最大成員數。

'a|b|c'.split('|', 0) // []
'a|b|c'.split('|', 1) // ["a"]
'a|b|c'.split('|', 2) // ["a", "b"]
'a|b|c'.split('|', 3) // ["a", "b", "c"]
'a|b|c'.split('|', 4) // ["a", "b", "c"]

上面代碼中,split方法的第二個參數,決定了返回數組的成員數。

split方法還可以使用正則表達式作為參數.

7、String.prototype.charCodeAt()
charCodeAt方法返回字符串指定位置的 Unicode 碼點(十進制表示),相當於String.fromCharCode()的逆操作。

'abc'.charCodeAt(1) // 98

上面代碼中,abc的1號位置的字符是b,它的 Unicode 碼點是98。

如果沒有任何參數,charCodeAt返回首字符的 Unicode 碼點。

'abc'.charCodeAt() // 97

如果參數為負數,或大於等於字符串的長度,charCodeAt返回NaN。

'abc'.charCodeAt(-1) // NaN
'abc'.charCodeAt(4) // NaN

注意,charCodeAt方法返回的 Unicode 碼點不會大於65536(0xFFFF),也就是說,只返回兩個字節的字符的碼點。如果遇到碼點大於 65536 的字符(四個字節的字符),必需連續使用兩次charCodeAt,不僅讀入charCodeAt(i),還要讀入charCodeAt(i+1),將兩個值放在一起,才能得到准確的字符。

8、String.prototype.concat()
concat方法用於連接兩個字符串,返回一個新字符串,不改變原字符串。

var s1 = 'abc';
var s2 = 'def';

s1.concat(s2) // "abcdef"
s1 // "abc"

該方法可以接受多個參數。

'a'.concat('b', 'c') // "abc"

如果參數不是字符串,concat方法會將其先轉為字符串,然后再連接。

var one = 1;
var two = 2;
var three = '3';

''.concat(one, two, three) // "123"
one + two + three // "33"

上面代碼中,concat方法將參數先轉成字符串再連接,所以返回的是一個三個字符的字符串。作為對比,加號運算符在兩個運算數都是數值時,不會轉換類型,所以返回的是一個兩個字符的字符串。

9、String.prototype.trim()
trim方法用於去除字符串兩端的空格,返回一個新字符串,不改變原字符串。

'  hello world  '.trim()
// "hello world"

該方法去除的不僅是空格,還包括制表符(\t、\v)、換行符(\n)和回車符(\r)。

'\r\nabc \t'.trim() // 'abc'

10、String.prototype.toLowerCase(),String.prototype.toUpperCase()

toLowerCase方法用於將一個字符串全部轉為小寫,toUpperCase則是全部轉為大寫。它們都返回一個新字符串,不改變原字符串。

'Hello World'.toLowerCase()
// "hello world"

'Hello World'.toUpperCase()
// "HELLO WORLD"

11、String.prototype.match()

match方法用於確定原字符串是否匹配某個子字符串,返回一個數組,成員為匹配的第一個字符串。如果沒有找到匹配,則返回null。

'cat, bat, sat, fat'.match('at') // ["at"]
'cat, bat, sat, fat'.match('xt') // null

返回的數組還有index屬性和input屬性,分別表示匹配字符串開始的位置和原始字符串。

var matches = 'cat, bat, sat, fat'.match('at');
matches.index // 1
matches.input // "cat, bat, sat, fat"

小練習:
留言過濾:


<!DOCTYPE html>
<html lang="en">
<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>Document</title>
</head>
<body>
  <h1>留言過濾</h1>
  <textarea  id="txt1" cols="30" rows="10"></textarea>
  <br>
  <input type="button" id="btn1" value="點擊過濾"> <br>
  <textarea  id="txt2" cols="30" rows="10"></textarea>
</body>
<script>
// 獲取元素  
var oTxt1 = document.getElementById("txt1");
var oTxt2 = document.getElementById("txt2");
var btn = document.getElementById("btn1");

btn.onclick = function () {
  // alert(123)
  var re = /DDS|fuck|FUCK|DSD/g;
  oTxt2.value = oTxt1.value.replace(re,"***");
};

</script>
</html>

簡單的注冊驗證:

<!DOCTYPE html>
<html lang="en">
<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>Document</title>
</head>
<body>
  <h1>簡易版注冊</h1>
  <hr>
  <form action="" name="myform">
    <table>
      <tr>
        <td>用戶名:</td>
        <td><input type="text" name="username" onblur="checkUser()"></td>
        <td id="username_info"></td>
      </tr>

      <tr>
        <td>郵箱:</td>
        <td><input type="email" name="email" onblur="checkEmail()"></td>
        <td id="email_info"></td>
      </tr>

      <tr>
        <td>密碼:</td>
        <td><input type="password" name="pwd" onblur="checkPwd()"></td>
        <td id="pwd_info"></td>
      </tr>

      <tr>
        <td>確認密碼:</td>
        <td><input type="password" name="repwd" onblur="checkRePwd()"></td>
        <td id="repwd_info"></td>
      </tr>

      <tr>
        <td></td>
        <td><input type="submit" value="提交數據"></td>
        <td></td>
      </tr>
    </table>
  </form>
</body>
<script>
  // 驗證用戶名 
  function checkUser() {
    // 獲取用戶輸入
    var value = document.myform.username.value;
    // 獲取id
    var td = document.getElementById('username_info');

    // 使用正則進行驗證
    if(value.search(/^\w{6,12}$/) === -1){
      td.innerHTML = "用戶名必須是6-12位數字、字母、下划線";
      td.style.color = "red";
    }else {
      td.innerHTML = "用戶名可用";
      td.style.color = "green";
    }
    
  }

  // 驗證郵箱
  function checkEmail() {
    // 獲取用戶輸入
    var value = document.myform.email.value;
    // 獲取id
    var td = document.getElementById('email_info');

    // 使用正則進行驗證
    if(value.search(/^\w+@\w+(\.\w+){1,3}$/) === -1){
      td.innerHTML = "郵箱格式不正確";
      td.style.color = "red";
    }else {
      td.innerHTML = "郵箱可用";
      td.style.color = "green";
    }
    
  }

  // 驗證密碼
  function checkPwd() {
    // 獲取用戶輸入
    var pwd = document.myform.pwd.value;
    // 獲取id
    var td = document.getElementById('pwd_info');

    // 使用正則進行驗證
    if(pwd.length < 6 || pwd.length > 18){
      td.innerHTML = "密碼長度必須是6-18位";
      td.style.color = "red";
    }else {
      td.innerHTML = "密碼可用";
      td.style.color = "green";
    }
    
  }

  // 驗證第二次輸入的密碼
  function checkRePwd() {
    // 獲取用戶輸入
    var pwd = document.myform.pwd.value;
    var repwd = document.myform.repwd.value;
    // 獲取id
    var td = document.getElementById('repwd_info');

    if(pwd !== repwd){
      td.innerHTML = "兩次密碼不一致";
      td.style.color = "red";
    }else{
      td.innerHTML = "輸入正確";
      td.style.color = "green";
    }
  }
</script>
</html>

統計字符串中每個字符出現的次數

// 創建一個字符串和一個對象
var str = "adminhelloworldadn",obj = {};

// 開啟循環進行處理
for(var i=0;i<str.length;i++){
  obj[str[i]] = (obj[str[i]] + 1) || 1;
}
console.log(obj); // 打印出來是一個對象的形式 
// 為了便於閱讀,轉換成json
var json = JSON.stringify(obj);
console.log(json); // {"a":2,"d":3,"m":1,"i":1,"n":2,"h":1,"e":1,"l":3,"o":2,"w":1,"r":1}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM