JavaScript 包含了 ECMAScript、DOM、BOM。
DOM (Document Object Model) DOM 浏览器特有。
定义 : DOM 是一种编程接口,用于访问和操作网页的内容和结构。它将网页的内容表示为一个树状结构,使得 JavaScript 可以动态地访问和更新网页内容、结构和样式。
主要功能:
节点 : 通过 DOM,网页的每个部分(如元素、属性和文本)都可以视为一个节点。
节点操作 : JavaScript 可以创建、修改、删除和替换这些节点。
事件处理 : 通过 DOM,可以为网页中的事件(如点击、输入、加载等)添加事件监听器。
BOM (Browser Object Model)
定义 : BOM 是用于与浏览器窗口及其功能进行交互的一组对象和方法。它允许 JavaScript 访问和操作浏览器窗口、文档、历史记录、导航、位置等。
主要功能:
window
对象 : 代表浏览器窗口,提供了访问浏览器属性和方法的能力,例如 alert()
, confirm()
, prompt()
, setTimeout()
, clearTimeout()
等。
navigator
对象 : 提供关于浏览器的信息,如浏览器版本和用户代理。
screen
对象 : 提供关于显示屏的分辨率和尺寸的信息。
history
对象 : 允许你访问和操作浏览器的历史记录。
location
对象 : 允许你访问和修改浏览器的 URL。
ECMA Script 6
let 和 const 命令 1 2 3 4 5 6 7 8 9 10 11 12 { let a = 10 ; var b = 1 ; } a b a ^ ReferenceError : a is not defined
1 2 3 4 5 6 7 8 9 for (let i = 0 ; i < 10 ; i++){} console .log (i)console .log (i) ^ ReferenceError : i is not defined
1 2 3 4 5 6 7 var a = []for (var i = 0 ; i < 10 ; i++) { a[i] = function ( ) { console .log (i); } } a[6 ]();
1 2 3 4 5 6 7 var a = []for (let i = 0 ; i < 10 ; i++) { a[i] = function ( ) { console .log (i); } } a[6 ]();
变量提升 :
1 2 3 4 5 6 7 8 console .log (a); let a = 2 ;console .log (a); ^ ReferenceError : Cannot access 'a' before initialization
1 2 3 4 5 console .log (a);var a = 2 ;undefined
暂时性死区 :
1 2 3 4 5 6 7 8 9 10 11 12 var tmp = 123 ;if (true ) { tmp = "abc" ; let tmp; } tmp = "abc" ; ^ ReferenceError : Cannot access 'tmp' before initialization
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 if (true ){ tmp = 'abc' ; console .log (tmp); let tmp; console .log (tmp); tmp = 123 ; console .log (tmp); } D :\jsPro\work\test.js :3 tmp = 'abc' ; ^ ReferenceError : Cannot access 'tmp' before initialization
1 2 3 4 5 6 7 8 9 10 function bar (x = y, y = 2 ) { return [x,y]; } bar ();function bar (x = y, y = 2 ) { ^ ReferenceError : Cannot access 'y' before initialization
变量不能重复声明 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function ( ) { let a = 10 ; var a = ; } function ( ) { let a = 10 ; let a = 1 ; } function func (arg ) { let arg; } function func (arg ) { { let arg; } }
块级作用域 :
1 2 3 4 5 6 7 function f1 ( ) { let n = 5 ; if (true ) { let n = 10 ; } console .log (n) }
1 2 3 4 5 6 7 8 9 10 11 12 13 { let a = 'secret' ; function f ( ) { console .log ("inner" ) return a; } } f ()PS D :\jsPro\work> node test.js inner
1 2 3 4 5 6 7 8 9 10 11 12 let f;{ let a = 'secret' ; f = function ( ) { return a; } } console .log (f ());PS D :\jsPro\work> node test.js secret
const
1 2 3 const foo = {}; foo.prop = 123 ;
1 2 const a = [];a.push ("Hello" );
1 2 3 4 5 6 7 8 9 10 const a = [];a.push ("Hello" ); a.length = 0 ; a = ["Dave" ]; a = ["Dave" ]; ^ TypeError : Assignment to constant variable.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var a = [];var b = [];b.push ("hello" ); b.push ("world" ) console .log (b)a = b console .log (a)PS D :\jsPro\work> node test.js [ 'hello' , 'world' ] [ 'hello' , 'world' ]
Object.freeze方法 1 2 3 4 5 6 7 8 9 const foo = Object .freeze ({});foo.prop = 123 ; console .log (foo)PS D :\jsPro\work> node test.js {}
1 2 3 4 5 6 7 8 9 var constantize = (obj ) => { Object .freeze (obj); Object .keys (obj).forEach ( (key, value ) => { if (typeof obj[key] === 'object' ) { constantize (obj[key]); } }) }
Object.freeze()
只会冻结对象的第一层。如果对象的属性值也是对象(嵌套对象),这些嵌套对象不会被自动冻结。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 var constantize = (obj ) => { Object .freeze (obj); Object .keys (obj).forEach ((key ) => { if (typeof obj[key] === 'object' ) { constantize (obj[key]); } }); } const myObject = { name : 'John' , age : 30 , address : { city : 'New York' , zip : '10001' }, hobbies : ['reading' , 'gaming' ] }; constantize (myObject);myObject.name = 'Jane' ; myObject.address .city = 'Los Angeles' ; myObject.hobbies .push ('swimming' ); console .log (myObject);
run:
1 2 3 4 myObject.hobbies.push('swimming'); // 无效 ^ TypeError: Cannot add property 2, object is not extensible
这个错误说明 myObject.hobbies
数组已经被冻结,无法对其进行修改(如添加新元素)。
跨模块常量 1 2 3 export const A = 1 ;export const B = 2 ;export const C = 3 ;
全局对象的属性 全局对象是最顶层的对象。浏览器环境中指的是 window对象。node.js 中指 global 对象。
var 命令和 function 命令声明的全局变量依旧是全局对象的属性;let 命令、const 命令 和 class 命令声明的全局变量不属于全局对象的属性。
1 2 3 4 5 6 7 8 9 var a = 1 ;console .log (a) this .a = 2 console .log (this .a , a) window .a = 3 console .log (window .a , this .a , a)
run:
1 2 3 4 5 6 7 8 PS D:\jsPro\work> node test.js 1 2 1 D:\jsPro\work\test.js:7 window.a = 3 ^ ReferenceError: window is not defined
变量的结构赋值 数组结构赋值 1 2 3 var [a, b, c] = [1 , 2 , 3 ];console .log (a,b,c)
嵌套解构赋值 :
1 2 3 let [foo, [[bar], baz]] = [1 ,[[2 ], 3 ]];console .log (a,b,c)
1 2 let [ , , third] = [1 ,2 ,3 ]console .log (third)
1 2 let [x, ,y] = [1 ,2 ,3 ];console .log (x, y);
1 2 3 4 5 let [head, ...tail] = [1 ,2 ,3 ,4 ,5 ];console .log (head, tail)1 [ 2 , 3 , 4 , 5 ]
1 2 3 4 5 let [x, y, ...z] = ['a' ];console .log (x, y, z)a undefined []
不完全解构 :
1 2 3 4 let [x, y] = [1 ,2 ,3 ];console .log (x, y) let [a, [b], d] = [1 , [2 ,3 ],4 ];console .log (a,b,d)
1 2 var [x, y, z] = new Set (['a' , 'b' , 'c' ]);console .log (x, y, z);
1 2 3 4 5 6 7 8 9 10 11 12 function * fibs ( ) { var a = 0 ; var b = 1 ; while (true ) { yield a; [a,b] = [b, a + b]; } } var [a, b, c, d, e, f, g] = fibs ();console .log (a, b, c, d, e, f, g)
function\* fibs()
:
定义了一个生成器函数 fibs
,它会生成斐波那契数列的数字。
yield a;
:
yield
关键字会暂停函数执行,并返回当前的 a
值。每次调用 next()
时,函数会从这里继续执行。
[a, b] = [b, a + b];
:
使用解构赋值更新 a
和 b
的值。a
被更新为 b
,b
被更新为 a + b
,这是生成下一个斐波那契数所需的操作。
解构赋值默认值 :
1 2 var [foo = true ] = [];console .log (foo)
1 2 3 4 5 "use strict" ;var [x, y = 'b' ] = ['a' ];console .log (x, y) var [m, n = 'b' ] = ['a' , undefined ];console .log (m, n)
1 2 var [x = 1 ] = [undefined ];console .log (x)
1 2 var [x = 1 ] = [null ];console .log (x)
1 2 3 4 5 function f ( ) { console .log ('aaa' ); } let [x = f ()] = [1 ];
相当于:
1 2 3 4 5 6 7 8 let x;if ([1 ][0 ] === undefined ) { x = f (); } else { x = [1 ][0 ] } console .log (x)
1 2 3 4 5 function f ( ) { console .log ("aaa" ); } var [x = f ()] = [undefined ];
1 2 3 4 5 6 7 function f ( ) { return "aaa" ; } let arr = [1 ];let x = arr[0 ] === undefined ? f () : arr[0 ];console .log (x)
1 2 3 4 5 6 7 function f ( ) { return "aaa" ; } let arr = [undefined ];let x = arr[0 ] === undefined ? f () : arr[0 ];console .log (x)
对象解构赋值 1 2 var {foo, bar} = {foo : "aaa" , bar : "bbb" };console .log (foo, bar);
1 2 var {bar, foo} = {foo : "aaa" , bar : "bbb" };console .log (foo, bar);
1 2 var {baz} = {foo : "aaa" , bar : "bbb" };baz
1 2 var {foo : baz} = {foo : "aaa" , bar : "bbb" };console .log (baz)
1 2 3 let obj = { first : "hello" , last : "world" };let {first : f, last : l} = obj;console .log (f, l)
1 2 var {foo : foo, bar : bar} = {foo : "aaa" , bar : "bbb" }; var {foo, bar} = {foo : "aaa" , bar : "bbb" };
1 2 3 let foo;({foo} = {foo : 1 }); console .log (foo)
1 2 3 let baz;({bar :baz} = {bar : 1 }) console .log (baz)
嵌套结构的对象 :
1 2 3 4 5 6 7 8 9 10 var obj = { p : [ "hello" , { y : "World" } ] }; var {p : [x, {y}]} = obj;console .log (x, y)
{ } 和 [ ] :
对象使用 {}
来定义,表示键值对的集合。用于存储和组织数据,例如属性和方法。
1 2 3 4 5 6 7 8 9 10 const person = { name : 'Alice' , age : 30 , address : { city : 'Wonderland' , postalCode : '12345' } }; console .log (person.address .city );
数组使用 []
来定义,表示有序的元素集合。用于存储列表或序列的数据,例如索引元素的集合。
1 2 3 const numbers = [1 , 2 , 3 , [4 , 5 , [6 , 7 ]]];console .log (numbers[3 ][2 ][1 ]);
1 2 3 4 5 6 7 8 9 10 11 12 13 var node = { loc : { start : { line : 1 , column : 5 } } }; var {loc : {start : {line}}} = node;console .log (line) console .log (loc) console .log (start)
1 2 3 4 5 let obj = {};let arr = [];({foo : obj.prop , bar : arr[0 ]} = {foo : 123 , bar : true }); console .log (obj, arr);
对象解构指定默认值 :
1 2 var {x = 3 } = {};console .log (x)
1 2 var {x, y = 5 } = {x : 1 };console .log (x, y);
1 2 var {message : msg = "Something went wrong" } = {};console .log (msg)
1 2 var {x = 3 } = {x : undefined };console .log (x)
1 2 var {x = 3 } = {x : null };console .log (x)
1 2 var {foo} = {bar : 'baz' };console .log (foo)
1 2 3 var x;{x} = {x : 1 }; console .log (x)
1 2 3 var x;({x} = {x :1 }); console .log (x)
1 let {log, sin, cos} = Math ;
字符串解构赋值 1 2 const [a, b, c, d, e] = "hello" ;console .log (a,b,c,d,e)
1 2 let {length : len} = "hello" ;console .log (len)
数值和布尔值的解构赋值 1 2 let {toString : s} = 123 ;console .log (s === Number .prototype .toString )
1 2 let {toString : s} = true ;console .log (s === Boolean .prototype .toString )
数值和布尔值都有 toString 属性。
解构赋值时,等号右边的不是对象就会先转换成对象,在进行解构赋值。undefined 和 null 无法转换成对象,解构赋值会报错。
1 2 let {prop : x} = undefined ; let {prop : y} = null ;
函数参数解构赋值 1 2 3 4 5 6 7 8 9 function add ([x, y] ) { return x + y; } console .log (add ([1 ,2 ]));function ad (x, y ) { return x + y; } console .log (ad (1 ,2 ));
1 console .log ([[1 ,2 ],[3 ,4 ]].map (([a,b] ) => a + b));
函数参数解构赋值默认值 :
1 2 3 4 5 6 7 8 9 10 11 12 13 function move ({x = 0 , y = 0 } = {} ) { return [x, y]; } console .log (move ({x :3 , y :8 }));console .log (move ({x : 3 }));console .log (move ({}))console .log (move ())[ 3 , 8 ] [ 3 , 0 ] [ 0 , 0 ] [ 0 , 0 ]
1 2 3 4 5 6 7 8 9 10 11 12 13 function move ({x, y} = {x: 0 , y: 0 } ) { return [x, y]; } console .log (move ({x :3 , y :8 }));console .log (move ({x :3 }));console .log (move ({}))console .log (move ())[ 3 , 8 ] [ 3 , undefined ] [ undefined , undefined ] [ 0 , 0 ]
undefined 会触发函数参数的默认值。
1 2 3 console .log ([1 , undefined , 3 ].map ((x = 'yes' ) => x));[ 1 , 'yes' , 3 ]
圆括号的问题 1 2 3 4 var [(a)] = [1 ];var {x : (c)} = {};var {o : ({p : p})} = {o : {p : 2 }};
函数参数中,模式不能带有圆括号 :
1 2 function f ([(z)] ) { return z;}
1 2 3 4 ({p : a}) = {p : 42 }; ([a]) = [5 ]; [({p : a}), {x :c}] = [{},{}];
可以使用圆括号的情况 :
1 2 3 4 [(b)] = [3 ]; ({p : (d)} = {}); [(parseInt .prop )] = [3 ];
解构赋值的用途 交换变量值 :
从函数返回多个值 :
1 2 3 4 5 function example ( ) { return [1 ,2 ,3 ]; } var [a, b, c] = example ();console .log (a,b,c);
1 2 3 4 5 6 7 8 9 function example ( ) { return { foo : 1 , bar : 2 }; } var {foo, bar} = example ();console .log (foo,bar);
函数参数的定义 : 函数参数有序无序的情况下参数都能准确地一一对应:
1 2 3 4 function f ([x, y, z] ) { console .log (`x:${x} ,y:${y} ,z:${z} .` ) } f ([1 ,2 ,3 ])
1 2 3 4 function f ({x, y, z} ) { console .log (`x:${x} ,y:${y} ,z:${z} .` ) } f ({z :3 , x :1 , y :2 });
提取JSON数据 :
1 2 3 4 5 6 7 var jsonData = { id : 42 , status : "ok" , data : [867 , 5309 ] } let {id, status, data : number} = jsonData;console .log (id, status, number);
函数参数的默认值 :
1 2 3 4 5 6 7 8 9 10 11 jQuery.ajax = function (url, { async = true , beforeSend = function () {}, cache = true , complete = function () {}, crossDomain = false , global = true , //... more config } ) { };
函数参数的默认值避免了在函数体内部写 var foo = config.foo || 'default foo';
config.foo
: 这是一个可能存在的变量或对象属性。如果 config
对象中有一个 foo
属性,它的值会被使用。
||
(逻辑或运算符) : 逻辑或运算符会检查 config.foo
的值。如果 config.foo
是假值(undefined
、null
、false
、0
、NaN
或空字符串 ""
),则 ||
运算符会返回其右侧的值。
1 2 3 4 5 var config = { foo : undefined } var foo = config.foo || 'default foo' ;console .log (foo)
1 2 3 4 5 var config = { foo : null } var foo = config.foo || 'default foo' ;console .log (foo)
1 2 3 4 5 var config = { foo : 0 } var foo = config.foo || 'default foo' ;console .log (foo)
1 2 3 4 5 var config = { foo : false } var foo = config.foo || 'default foo' ;console .log (foo)
1 2 3 4 5 var config = { foo : "" } var foo = config.foo || 'default foo' ;console .log (foo)
1 2 3 4 5 var config = { foo : NaN } var foo = config.foo || 'default foo' ;console .log (foo)
1 2 3 4 5 6 7 8 function greet (name ) { var displayName = name || 'Guest' ; console .log ('Hello, ' + displayName); } greet (); greet ('Alice' );
假值 : 逻辑或运算符 ||
只会在左侧值是“假值”时使用右侧的值。假值包括 undefined
、null
、false
、0
、NaN
和空字符串 ""
。这意味着,如果 config.foo
的值是这些假值中的一个,默认值将会被使用。
更严格的默认值 : 如果想在 config.foo
为 undefined
或 null
时使用默认值,可以使用空值合并运算符 ??
(在 ES2020 中引入):??
运算符仅在左侧的值为 undefined
或 null
时使用右侧的值。
1 2 3 4 5 var config = { foo : NaN } var foo = config.foo ?? 'default foo' ;console .log (foo)
1 2 3 4 5 6 var config = { foo : 0 }; var foo = config.foo ?? 'default foo' ;console .log (foo);
遍历 Map 解构 :
任何部署了 Iterator 接口的对象可以使用 for … of 循环遍历。Map 原生支持 Iterator 接口。
1 2 3 4 5 6 var map = new Map ();map.set ('first' , 'hello' ); map.set ('second' , 'world' ); for (let [key, value] of map) { console .log (`key ${key} is ${value} ` ); }
只获取键:
1 for (let [key] of map) { ... ...}
只获取值:
1 for (let [value] of map) {... ...}
输入模块的指定的方法 :
1 const { SourceMapConsumer , SourceNode } = require ("source-map" );
字符串的扩展 Unicode 表示方法 1 2 3 4 5 6 7 8 9 console .log ("\u0061" ); console .log ("\uD842\uDFB7" ); console .log ("\u20BB7" ); console .log ("\u{20BB7}" ); console .log ("\u{41}\u{42}\u{43}" ); console .log ("hell\u{6F}" ); console .log ("\u{1F680}" ); console .log ("\uD83D\uDE80" ); console .log ("\u{1F680}" === "\uD83D\uDE80" )
javascript 6 中方法表示字符 :
1 2 3 4 5 6 'z' console .log ('\z' === 'z' ); console .log ('\172' === 'z' ); console .log ('\x7A' === 'z' ); console .log ('\u007A' === 'z' ); console .log ('\u{7A}' === 'z' )
codePointAt() JavaScript 内部 字符格式 UTF-16(两个字节)。需要4个字节存储的字符(码点大于 0xFFFF 的字符),JavaScript 认为是两个字符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var s = "𠮷" ;let {length : len} = s;console .log (len)console .log (s.charAt (0 ));console .log (s.charAt (1 ));console .log (s.charCodeAt (0 ));console .log (s.charCodeAt (1 ));2 � � 55362 57271
1 2 3 4 5 6 7 8 9 10 11 12 13 var s = "𠮷a" ;console .log (s.codePointAt (0 ));console .log (s.codePointAt (1 ));console .log (s.codePointAt (2 ));console .log (s.codePointAt (0 ).toString (16 ));console .log (s.codePointAt (2 ).toString (16 ))134071 57271 97 20bb7 61
1 2 3 4 5 6 7 var s = "𠮷a" ;for (let ch of s) { console .log (ch.codePointAt (0 ).toString (16 )); } 20bb7 61
1 2 3 4 5 6 7 function is32Bit (c ) { return c.codePointAt (0 ) > 0xFFFF ; } console .log (is32Bit ("𠮷" )); console .log (is32Bit ("a" ));
String.fromCodePoint() 1 2 console .log (String .fromCodePoint (0x20BB7 ))
1 2 3 4 5 6 7 8 console .log (String .fromCodePoint (0x78 , 0x1f680 , 0x79 ))console .log ('x\uD830\uDE80y' )console .log (String .fromCodePoint (0x78 , 0x1f680 , 0x79 ) === 'x\uD83D\uDE80y' )x🚀y xy true
字符串遍历接口 1 2 3 for (let ch of 'foo' ) { console .log (ch) }
1 2 3 4 5 6 7 8 var text = String .fromCodePoint (0x20BB7 );for (let i = 0 ; i < text.length ; i++) { console .log (text[i]) } � �
at() 1 2 console .log ('abc' .charAt (0 )); console .log ('𠮷' .charAt (0 ));
normalize() 1 2 3 4 5 6 7 8 9 10 11 12 console .log ("\u01D1" );console .log ("\u004F\u030c" );console .log ("\u01D1" === "\u004F\u030c" )console .log ("\u01D1" .length )console .log ("\u004F\u030c" .length )Ǒ Ǒ false 1 2
1 2 console .log ("\u01D1" .normalize () === "\u004F\u030c" .normalize ())
normalize()
参数:
NFC,默认参数,标准等价合成,(Normalization Form Canonical Composition),返回多个简单字符的合成字符。标准等价 指 视觉和语义上的等价。
NFD,Decomposition,标准等价分解,将 NFC 的合成分解成多个简单字符。
NFKC,兼容等价合成,Normalization Form Compatibility Composition,返回合成字符,语义等价,视觉不等价。
NFKD,兼容等价分解,NFKC 逆过来。
1 2 3 4 5 console .log ('\u004f\u030c' .normalize ('NFC' ).length )console .log ('\u004f\u030c' .normalize ('NFD' ).length )1 2
includes(), startsWith(), endsWith() 1 2 3 4 5 var s = "helloWorld.js" ;s.startsWith ("hello" ); s.endsWith (".js" ); s.includes ("oWor" );
repeat() 1 2 3 4 5 6 'x' .repeat (5 );'hello' .repeat (2 );let a = 'zero' ;console .log (`start${a.repeat(0 )} end` );
1 2 let a = 'zero' ;console .log (a.repeat (2.9 ));
1 2 let a = 'Zero' ;console .log (a.repeat ('2' ));
padStart(), padEnd() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 for (let i = 1 ; i < 14 ; i++) { console .log (String (i).padStart (3 , '0' )); } 001 002 003 004 005 006 007 008 009 010 011 012 013
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 for (let i = 1 ; i < 14 ; i++) { console .log (String (i).padStart (5 , 'ab' )); } abab1 abab2 abab3 abab4 abab5 abab6 abab7 abab8 abab9 aba10 aba11 aba12 aba13
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 for (let i = 1 ; i < 14 ; i++) { console .log (String (i).padEnd (5 , 'ab' )); } 1abab 2abab 3abab 4abab 5abab 6abab 7abab 8abab 9abab 10aba 11aba 12aba 13aba
模板字符串 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $("#result" ).append ( "There are <b>" + basket.count + "</b>" + "items in your basket, " + "<em>" + backet.onSale + "</em> are on sale!" ); $("#result" ).append ( `There are <b>${basket.count} </b> items in your basket <em>${backet.onSale} </em> are on sale!` );
1 2 3 4 5 6 7 8 9 10 11 console .log (`abcd 1234 yes no` ) abcd 1234 yes no
1 2 3 4 5 6 7 8 9 10 var x = 1 ;var y = 2 ;console .log (`x + y = ${x + y} ` );var obj = { x : 1 , y :2 }; console .log (`the sum of x and y in obj is ${obj.x + obj.y} ` );
1 2 3 4 function fn ( ) { return "hello world" ; } console .log (`start ${fn()} end` );
引用模板字符串本身:
写法一 :
1 2 3 let str = 'return ' + '`Hello ${name}!`' ; let func = new Function ('name' , str); console .log (func ('Jack' ))
new Function('name', str)
创建了一个新的函数。new Function()
是一个构造函数,用于动态创建 JavaScript 函数。这个构造函数允许代码在运行时生成新的函数,函数体的代码以字符串形式提供。
写法二 :
1 2 3 4 let str = '(name) => `Hello ${name}!`' ; let func = eval .call (null , str);console .log (func ('Jack' ));
在 JavaScript 中,箭头函数是一种简洁的函数表示方式,可以使用 =>
定义。
eval
是一个全局函数,它会将字符串作为 JavaScript 代码执行。在这里,eval
被用来将 str
变量中的字符串解析为实际的 JavaScript 函数。
eval.call(null, str)
的作用是将 str
作为参数传递给 eval
函数执行,call(null, ...)
是为了确保 eval
的上下文 (this
) 被设置为 null
。在这种情况下,this
的值是无关紧要的,但使用 call
可以确保 eval
的上下文不会受到干扰。
eval
执行后,str
字符串中的箭头函数会被解析为一个真正的 JavaScript 函数,并赋值给变量 func
。
实例:模板编译 模板编译是一个将模板字符串(通常包含动态数据插值和逻辑代码)转换成可执行的 JavaScript 代码的过程。这个过程的目的是生成最终的 HTML 代码或其他格式的输出,以便于动态内容的渲染。
1 2 3 4 5 6 7 var template = ` <ul> <% for(var i=0; i < data.supplies.length; i++) {%> <li><%= data.supplies[i] %></li> <% } %> </ul> ` ;
<% ... %>
: 这是一个包含 JavaScript 逻辑的代码块。这个块内的代码会被执行,但不会直接输出到结果中。
在这个例子中,使用了一个 for
循环来遍历 data.supplies
数组的元素。
<%= ... %>
: 这是一个输出表达式,用于将 JavaScript 表达式的结果插入到模板中。在这个例子中,它将 data.supplies[i]
的值插入到 <li>
元素中。
编译和渲染 :
要将这个模板字符串转换为实际的 HTML,需要使用模板引擎将其编译成函数,并将数据传递给这个函数进行渲染。许多 JavaScript 模板引擎(如 Underscore.js, EJS, Handlebars, 或 Mustache)都提供了这种功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 const {JSDOM } = require ('jsdom' ); const dom = new JSDOM (`<!DOCTYPE html> <html> <head> </head> <body> <div id="myDiv"> </div> </body> </html>` )const { window } = dom;var template = ` <ul> <% for(var i=0; i < data.supplies.length; i++) {%> <li><%= data.supplies[i] %></li> <% } %> </ul> ` ; function compile (template ) { var evalExpr = /<%=(.+?)%>/g ; var expr = /<%([\s\S]+?)%>/g ; template = template.replace (evalExpr, '`); \n echo( $1 ); \n echo(`' ).replace (expr, '`); \n $1 \n echo(`' ); template = 'echo(`' + template + '`);' ; var script = `(function parse(data){ var output = ""; function echo(html){ output += html; } ${ template} return output; })` ; return script; } const div = window .document .getElementById ('myDiv' );var parse = eval (compile (template));div.innerHTML = parse ({supplies : ["broom" , "mop" , "cleaner" ]}); console .log (window .document .documentElement .outerHTML );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <!DOCTYPE html > <html > <head > <script > alert ("Hello,World!" ); </script > </head > <body > <div id ="myDiv" > </div > </body > <script > var template = ` <ul> <% for(var i=0; i < data.supplies.length; i++) {%> <li><%= data.supplies[i] %></li> <% } %> </ul> ` ; function compile (template ) { var evalExpr = /<%=(.+?)%>/g ; var expr = /<%([\s\S]+?)%>/g ; template = template.replace (evalExpr, '`); \n echo( $1 ); \n echo(`' ).replace (expr, '`); \n $1 \n echo(`' ); template = 'echo(`' + template + '`);' ; var script = `(function parse(data){ var output = ""; function echo(html){ output += html; } ${ template} return output; })` ; return script; } var parse = eval (compile (template)); var div = this .document .getElementById ('myDiv' ); div.innerHTML = parse ({supplies : ["broom" , "mop" , "cleaner" ]}); </script > </html >
正则表达式 Regular Expressions (regex) 用于匹配、查找和替换字符串中字符模式的工具。
创建正则表达式 :
使用斜杠 (/
) 包围正则表达式的模式:
1 let regex = /pattern/ flags;
pattern
是正则表达式的模式。
flags
是可选的标志,用于控制正则表达式的行为。
构造函数 :
可以通过 RegExp
构造函数动态创建正则表达式:
1 let regex = new RegExp ('pattern' , 'flags' );
正则表达式标志 g
: 全局匹配,不仅匹配第一个,还匹配所有符合条件的字符串。
i
: 忽略大小写匹配。
m
: 多行匹配,使 ^
和 $
匹配行的开头和结尾。
s
: 点号(.
)匹配包括换行符在内的所有字符。
u
: Unicode 模式,支持 Unicode 字符集。
y
: 粘附模式,确保正则表达式从指定的位置开始匹配。
正则表达式模式 基本字符类
\d
: 匹配数字,等价于 [0-9]
。
\D
: 匹配非数字字符。
\w
: 匹配字母、数字和下划线,等价于 [a-zA-Z0-9_]
。
\W
: 匹配非字母、数字和下划线字符。
\s
: 匹配任何空白字符(空格、制表符、换行符等)。
\S
: 匹配非空白字符。
字符集和范围
[abc]
: 匹配字符 a
、b
或 c
。
[^abc]
: 匹配除了字符 a
、b
或 c
之外的字符。
[a-z]
: 匹配小写字母中的任何一个。
量词
\*
: 匹配前面的字符零次或多次。
+
: 匹配前面的字符一次或多次。
?
: 匹配前面的字符零次或一次。
{n}
: 匹配前面的字符恰好 n
次。
{n,}
: 匹配前面的字符至少 n
次。
{n,m}
: 匹配前面的字符至少 n
次,但不超过 m
次。
边界匹配
^
: 匹配输入字符串的开始。
$
: 匹配输入字符串的结束。
捕获组和非捕获组
()
: 捕获组,用于提取匹配的子字符串。
(?:)
: 非捕获组,用于分组但不捕获匹配的内容。
正则表达式方法 RegExp.prototype.test
测试一个字符串是否匹配正则表达式:
1 2 let regex = /hello/ ;console .log (regex.test ('hello world' ));
RegExp.prototype.exec
执行正则表达式匹配,返回一个数组或 null
:
1 2 3 let regex = /(\d+)/ ;let result = regex.exec ('The number is 42' );console .log (result);
String.prototype.match
使用正则表达式匹配字符串,返回匹配结果的数组:
1 2 3 4 let str = 'The numbers are 42 and 37' ;let regex = /\d+/g ;let result = str.match (regex);console .log (result);
String.prototype.replace
使用正则表达式替换字符串中的匹配项:
1 2 3 let str = 'Hello world' ;let result = str.replace (/world/ , 'there' );console .log (result);
String.prototype.split
使用正则表达式拆分字符串:
1 2 3 let str = 'one, two, three' ;let result = str.split (/,\s*/ );console .log (result);
String.prototype.search
返回值 :
返回第一个匹配的索引位置(从 0 开始),如果没有匹配项,则返回 -1
。
只能使用正则表达式的特性 :
search
方法只支持正则表达式,并且不能使用正则表达式的 g
(全局) 标志。如果提供的正则表达式有 g
标志,search
方法会忽略这个标志。
1 2 3 let str = 'Hello world!' ;let index = str.search (/world/ );console .log (index);
1 2 3 let str = 'Hello World!' ;let index = str.search (/world/i ); console .log (index);
1 2 3 let str = 'Hello!' ;let index = str.search (/world/ );console .log (index);
例子 验证邮箱地址 1 2 let emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ ;console .log (emailRegex.test ('[email protected] ' ));
1. ^
字符串开始
含义 : 匹配输入字符串的开始部分。确保整个字符串都符合正则表达式的模式。
2. [a-zA-Z0-9._%+-]+
本地部分
3. @
电子邮件的分隔符
含义 : 匹配电子邮件地址中的 @
符号,用于分隔本地部分和域名部分。
4. [a-zA-Z0-9.-]+
域名部分
5. \.
点号
含义 : 匹配一个实际的点号 .
。由于点号在正则表达式中有特殊含义(匹配任何字符),所以在这里需要用反斜杠 \
转义,表示字面量点号。
6. [a-zA-Z]{2,}
顶级域名
7. $
字符串结束
含义 : 匹配输入字符串的结束部分。确保整个字符串都符合正则表达式的模式。
提取日期 1 2 3 4 5 6 let dateRegex = /(\d{4})-(\d{2})-(\d{2})/ ;let dateStr = '2024-08-20' ;let match = dateRegex.exec (dateStr);if (match) { console .log (`Year: ${match[1 ]} , Month: ${match[2 ]} , Day: ${match[3 ]} ` ); }
1. (\d{4})
年份部分
\d
: 匹配一个数字字符,相当于 [0-9]
。
{4}
: 表示匹配前面的字符(数字)恰好 4 次。即,匹配 4 位数字。
()
: 圆括号用于捕获组,将匹配的内容存储到一个数组中,以便后续引用。在这个例子中,它捕获了年份部分。
2. -
分隔符
含义 : 匹配一个字面量的连字符 -
。用于分隔日期的不同部分(年、月、日)。
3. (\d{2})
月份部分
\d
: 匹配一个数字字符,相当于 [0-9]
。
{2}
: 表示匹配前面的字符(数字)恰好 2 次。即,匹配 2 位数字。
()
: 圆括号用于捕获组,将匹配的内容存储到一个数组中。在这个例子中,它捕获了月份部分。
4. -
分隔符
含义 : 匹配一个字面量的连字符 -
,用于分隔日期的不同部分(年、月、日)。
5. (\d{2})
日期部分
\d
: 匹配一个数字字符,相当于 [0-9]
。
{2}
: 表示匹配前面的字符(数字)恰好 2 次。即,匹配 2 位数字。
()
: 圆括号用于捕获组,将匹配的内容存储到一个数组中。在这个例子中,它捕获了日期部分。
RegExp 构造函数 1 2 3 4 5 var regex = new RegExp ("xyz" , "i" );var regex = /xyz/i ;var regex = new RegExp (/xyz/i );
1 2 3 4 5 6 var regex = /xyz/i ;exStr = "66623asdfggexyz" ; console .log (regex.exec (exStr));[ 'xyz' , index : 12 , input : '66623asdfggexyz' , groups : undefined ]
1 new RegExp (/abc/ig , 'i' ).flags
u标志 1 2 3 4 5 console .log ('\uD83D\uDC2A' ) console .log (/^\uD83D/u .test ('\uD83D\uDC2A' )); console .log (/\uD83D\uDC2A/u .test ('asdfaasdf\uD83D\uDC2Afasdfasd' )); console .log (/^\uD83D/ .test ('\uD83D\uDC2A' ));
点字符 :
.
匹配除换行以外的任意单个字符。码点大于 0xFFFF 的 Unicode 字符,点字符不能识别,必须加上 u标志。
1 2 3 var s = '𠮷' ;console .log (/^.$/ .test (s)) console .log (/^.$/u .test (s))
量词 :
1 2 3 4 5 6 7 8 9 10 11 12 13 var ss = '𠮷𠮷' ;var aa = 'aa' ;console .log (/a{2}/ .test (aa));console .log (/a{2}/u .test (aa));console .log (/𠮷{2}/ .test (ss));console .log (/𠮷{2}/u .test (ss));true true false true
1 2 console .log (/^\u{3}$/ .test ('uuu' )) console .log (/^\u{3}$/u .test ('uuu' ))
预定义模式 :
1 2 3 console .log (/^\S$/ .test ('𠮷' )) console .log (/^\S$/u .test ('𠮷' ))
正确返回字符串长度的函数:(字符串包含Unicode 字符)
1 2 3 4 5 6 7 function codePointLength (text ) { var result = text.match (/[\s\S]/gu ); return result ? result.length : 0 ; } var s = '𠮷𠮷\uD83D\uDC2A' ;console .log (s.length ) console .log (codePointLength (s))
1 2 3 4 console .log ('\u004B' ); console .log ('\u212A' ); console .log (/[a-z]/i .test ('\u212A' )); console .log (/[a-z]/iu .test ('\u212A' ));
y标志 y标志 - 粘连(sticky)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 var s = "aaa_aa_a" var r1 = /a+/g ; var r2 = /a+/y ; console .log (r1.exec (s)); console .log (r2.exec (s)); console .log (r1.exec (s)); console .log (r2.exec (s)); var r = /a+_/y ;console .log (r.exec (s)); console .log (r.exec (s)); [ 'aaa' , index : 0 , input : 'aaa_aa_a' , groups : undefined ] [ 'aaa' , index : 0 , input : 'aaa_aa_a' , groups : undefined ] [ 'aa' , index : 4 , input : 'aaa_aa_a' , groups : undefined ] null [ 'aaa_' , index : 0 , input : 'aaa_aa_a' , groups : undefined ] [ 'aa_' , index : 4 , input : 'aaa_aa_a' , groups : undefined ]
g标志:后一次匹配从上一次匹配成功的下一个位置开始,剩余的字符串中只要有符合正则表达式的就匹配。
y标志:后一次匹配从上一次匹配成功的下一个位置开始,但是要连在一起,中间不能有其他字符(上面的案例中中间有个 _
字符)。
y标志的应用,和 g标志相比,使用y标志可以发现被匹配字符串之间非法的字符。例如:从字符串提取 token 词元,y标志确保匹配之间不会有漏掉的字符。
sticky 属性 :
1 2 3 var regex = /hello\d/y ;console .log (regex.sticky );
flags 属性 :
1 2 3 console .log (/abc/ig .flags ); console .log (/abc/ig .source );
数值 二进制八进制 :
1 2 3 4 5 6 7 console .log (0b101 === 5 ); console .log (0o12 === 10 ); console .log (Number (0b101 ), Number (0o12 ));true true 5 10
**Number.isFinite(), Number.isNaN()**:
1 2 3 4 5 6 Number .isFinite (15 ); Number .isFinite (NaN ); Number .isFinite (Infinity ); Number .isFinite ('15' );
1 2 3 4 Number .isNaN (NaN ); Number .isNaN (666 ); Number .isNaN ("999" ); Number .isNaN (true );
Number.parseInt(), Number.parseFloat() 1 2 Number .parseInt ('12.34' ); Number .parseFloat ('3.1415926abc' );
Number.isInteger() 1 2 3 4 5 Number .isInteger (25 ); Number .isInteger (25.0 ); Number .isInteger (25.3 ); Number .isInteger ('11' ); Number .isInteger (true );
函数 通过解构赋值设置默认值 :
1 2 3 4 5 6 7 8 function func ({x, y = 1 } ) { console .log (x, y); } func ({}); func ({x :1 }); func ({x :1 , y :6 }); func ();
直接指定默认值 :
1 2 3 4 5 6 7 function func (x, y = "world" ) { console .log (x, y); } func ("hello" ); func ("hello" , "Jackey Song" ); func ("hello" , "" );
1 2 3 4 5 6 function fetch (url, { body='' , method='GET' , headers={} } ) { console .log (method); } fetch ('https://jackey-song.com' , {}); fetch ('https://jackey-song.com' );
1 2 3 4 5 function fetch (url, { body='' , method='GET' , headers={} } = {} ) { console .log (method); } fetch ('https://jackey-song.com' );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function f1 ({x = 0 , y = 0 } = {} ) { console .log (x, y); } function f2 ({x, y} = {x:0 , y:0 } ) { console .log (x, y); } f1 (); f2 (); f1 ({x : 5 , y :4 }); f2 ({x : 5 , y :4 }); f1 ({x : 5 }); f2 ({x : 5 }) f1 ({}); f2 ({});
指定默认值的参数应当放在最后一个 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function func (x, y, z=1 ) { console .log (x, y, z); } func (3 ,2 ); function m (x=1 , y, z ) { console .log (x, y, z); } m (2 ,3 ); m (undefined , 2 , 3 ); function n (x, y=1 , z ) { console .log (x, y, z); } n (2 , undefined , 3 );
函数的 length 参数个数 :
1 2 3 4 5 function func (x, y, z=1 ) { console .log (x, y, z); } console .log (func.length )
变量作用域 :
1 2 3 4 5 var x = 1 ;function f (x, y = x ) { console .log (y); } f (2 );
1 console .log (...[1 , 2 , 3 ]);
函数的name属性 :
1 2 3 4 5 6 7 8 9 10 function func ( ) { return 0 ; } var abc = function ( ) { return 0 ; } console .log (func.name ); console .log (abc.name );
1 2 3 4 5 6 7 8 9 const f = function func ( ) { return 0 ; } console .log (f.name ); var abc = function f ( ) { return 0 ; } console .log (abc.name );
箭头函数 使用 => 定义函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var f = v => v;var f = function (v ) { return v; } var f = ( ) => 5 ; var f = function ( ) { return 5 } var f = (a, b, c ) => a + b + c; console .log (f (1 ,2 ,3 )); var f = function (a, b, c ) { return a + b + c; }
1 2 3 var f = (a, b, c ) => {a = a + b + c; return a**2 }; console .log (f (1 , 1 , 1 ));
1 2 3 var f = name => ({id : 1 , name : name}); console .log (f ("Jackey Song" ));
1 2 3 4 5 [1 ,2 ,3 ]map (function (x ) { return x*x; }); [1 ,2 ,3 ]map (x => x*x);
1 2 const numbers = (...nums ) => nums;console .log (numbers (1 ,2 ,3 ,4 ,5 ));
1 2 const headAndTail = (head, ...tail ) => [head, tail];console .log (headAndTail (1 ,2 ,3 ,4 ,5 ));
箭头函数没有自己的 this
绑定。它们从定义时的上下文继承 this
,即箭头函数的 this
是在函数定义时确定的,不是调用时确定的。
1 2 3 4 5 6 7 8 9 function Counter ( ) { this .value = 0 ; setInterval (() => { this .value ++; console .log (this .value ); }, 1000 ); } new Counter ();
箭头函数不能用作构造函数,不能使用 new
关键字实例化对象。
箭头函数没有 arguments
对象。如果需要访问函数的参数,可以使用 rest
参数(...args
)。
1 2 3 4 5 6 7 8 9 10 function traditionalFunction ( ) { console .log (arguments ); } const arrowFunction = ( ) => { }; traditionalFunction (1 , 2 , 3 );arrowFunction (1 , 2 , 3 );
由于箭头函数不会创建自己的 this
绑定,它们在事件处理程序和其他需要 this
绑定的场景中特别有用。
1 2 3 4 5 6 7 8 9 10 11 12 13 class MyClass { constructor ( ) { this .name = 'MyClass' ; } greet ( ) { setTimeout (() => { console .log (`Hello from ${this .name} ` ); }, 1000 ); } } new MyClass ().greet ();
箭头函数不可使用 yield 命令,箭头函数不能用作 Generator 函数。
箭头函数不能使用 super
关键字,因为它们没有自己的 this
,因此无法访问父类的构造函数或方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Parent { constructor ( ) { this .name = 'Parent' ; } method ( ) { return () => { console .log (super .method ()); }; } } class Child extends Parent { constructor ( ) { super (); this .name = 'Child' ; } method ( ) { return 'Hello from Child' ; } } const child = new Child ();child.method ()();
1 2 3 4 5 6 7 8 9 function foo ( ) { setTimeout ( () => { console .log ("id:" , this .id ); }, 100 ); } foo ()foo.call ( {id : 42 });
1. foo()
调用方式 接调用函数 foo()
时,它会在当前的上下文中执行,而不是使用任何特定的 this
值。
1 2 3 4 5 6 7 function foo ( ) { setTimeout (() => { console .log ("id:" , this .id ); }, 100 ); } foo ();
this
绑定 :
在浏览器中,直接调用 foo()
时,this
通常会绑定到全局对象 window
(在严格模式下,this
是 undefined
)。
由于在全局上下文中 id
不存在,这段代码在浏览器的非严格模式下会输出 id: undefined
,在严格模式下会抛出错误,因为 this
是 undefined
,无法读取 id
属性。
2. foo.call({id: 42})
调用方式 当使用 foo.call({id: 42})
调用函数时,foo
会被执行,并且 this
会被显式地设置为 call
方法中的第一个参数。
1 2 3 4 5 6 7 function foo ( ) { setTimeout (() => { console .log ("id:" , this .id ); }, 100 ); } foo.call ({id : 42 });
this
绑定 :
在这种情况下,this
在 foo
函数体内会被绑定到 {id: 42}
对象。这意味着箭头函数内部的 this
也是 {id: 42}
。
因此,setTimeout
回调中的 this.id
会正确地输出 42
。
关键区别总结
foo()
: 直接调用时,this
是调用 foo
的上下文(全局对象或 undefined
),特别是在异步操作中,如 setTimeout
,this
的绑定可能会与期望的上下文不同。
foo.call(context)
: 使用 call
方法调用时,this
被显式地设置为 context
参数。在异步操作中,this
的值将根据 call
的参数来确定。
示例对比 示例 1: foo()
1 2 3 4 5 6 7 function foo ( ) { setTimeout (() => { console .log ("id:" , this .id ); }, 100 ); } foo ();
示例 2: foo.call({id: 42})
1 2 3 4 5 6 7 function foo ( ) { setTimeout (() => { console .log ("id:" , this .id ); }, 100 ); } foo.call ({id : 42 });
其他代码 :
1 2 3 4 5 6 7 8 9 var handler = { id : "123456" , init : function ( ) { document .addEventListener ("click" , event => this .doSomething (event.type ), false ); }, doSomething : function (type ) { console .log ("Handling " + type + " for " + this .id ); } };
1 2 3 4 5 6 7 function Timer () { this .seconds = 0 ; setInterval (() => this .seconds ++, 1000 ); } var timer = new Timer ();setTimeout (() => console .log (timer.seconds ), 3100 );
1 2 3 4 5 6 7 8 9 10 11 function foo ( ) { return () => { return () => { return () => { console .log ('id:' , this .id ); }; }; }; } foo.call ({id : 42 })()()();
构造函数 VS 普通函数 1. 函数名称的约定
构造函数 :
构造函数通常以大写字母开头,以便与普通函数区分。这是一个约定而非强制规则。例如:Person
, Car
, Timer
等。
1 2 3 function Person (name ) { this .name = name; }
普通函数 :
普通函数的名称通常以小写字母开头,虽然这并不是强制的,但可以帮助区分。例如:calculateTotal
, printMessage
等。
1 2 3 function calculateTotal (a, b ) { return a + b; }
2. new
关键字
构造函数 :
构造函数应使用 new
关键字调用。当用 new
调用构造函数时,它会创建一个新对象并将 this
绑定到这个新对象。
1 const person = new Person ('Alice' );
普通函数 :
普通函数不应该用 new
关键字调用。如果用 new
调用普通函数,它将不会按预期工作,通常会返回 undefined
或导致错误。
1 const total = calculateTotal (5 , 10 );
3. this
绑定
4. 返回值
构造函数 :
构造函数通常不显式返回值。它们自动返回新创建的对象。如果显式地返回一个对象,那个对象将作为构造函数的结果被返回。
1 2 3 4 function Person (name ) { this .name = name; }
普通函数 :
普通函数可以返回任何值,包括原始数据类型、对象或其他函数。返回值必须显式地指定。
1 2 3 function add (a, b ) { return a + b; }
5. 使用场景
构造函数 :
主要用于创建和初始化对象实例。它们用于定义对象的属性和方法,通常通过 new
关键字创建新实例。
1 2 3 4 5 6 function Car (make, model ) { this .make = make; this .model = model; } const myCar = new Car ('Toyota' , 'Corolla' );
普通函数 :
主要用于执行特定的任务或计算结果。普通函数可以被调用多次,但不创建对象实例。
1 2 3 4 5 function multiply (a, b ) { return a * b; } const result = multiply (4 , 5 );
总结
构造函数 :
名称以大写字母开头(通常为约定)。
使用 new
关键字调用。
在函数内部,this
绑定到新创建的对象。
通常不显式返回值(自动返回新对象)。
普通函数 :
名称以小写字母开头(虽然这不是强制的)。
不使用 new
关键字调用。
this
取决于调用上下文。
可以显式返回值。
函数绑定 函数绑定是 JavaScript 中处理 this
的一种机制,允许创建一个新的函数并预设其中的 this
值和/或参数。函数绑定主要有三种实现方式:Function.prototype.bind()
方法、.call()
和 .apply()
方法的使用,以及箭头函数的 this
绑定行为。
1. Function.prototype.bind()
方法 bind()
方法用于创建一个新的函数,这个新函数会将 this
绑定到指定的对象,并且可以预先设置一些参数。
在这个例子中,greetHello
是一个新的函数,它的 this
被绑定为 null
,并且 greeting
参数被预设为 'Hello'
。调用 greetHello('Alice')
实际上等价于 greet(null, 'Hello', 'Alice')
。
2. .call()
和 .apply()
方法 call()
和 apply()
方法用于立即调用函数,并可以显式地设置函数的 this
值和参数。
call()
方法 :
1 2 3 4 function functionName (arg1, arg2, ... ) { } functionName.call (thisArg, arg1, arg2, ...);
apply()
方法 :
1 2 3 4 function functionName (arg1, arg2, ... ) { } functionName.apply (thisArg, [arg1, arg2, ...]);
区别 :
call()
接受参数列表。
apply()
接受一个参数数组。
示例 :
1 2 3 4 5 6 function greet (greeting, name ) { console .log (`${greeting} , ${name} !` ); } greet.call (null , 'Hi' , 'Bob' ); greet.apply (null , ['Hi' , 'Bob' ]);
在这两个例子中,greet
函数的 this
被设置为 null
,并且传入的参数被分别通过 call()
和 apply()
方法传递。
3. 箭头函数的 this
绑定 箭头函数的 this
绑定规则不同于普通函数。箭头函数不创建自己的 this
上下文,它会从外围上下文中继承 this
。因此,箭头函数中的 this
是静态绑定的,在函数创建时就已经确定。
总结
bind()
: 创建一个新函数,设置 this
和预设参数。不会立即执行。
call()
和 apply()
: 立即调用函数,设置 this
和传递参数。call()
接受参数列表,apply()
接受参数数组。
函数绑定是 JavaScript 中处理 this
绑定和函数调用的关键技术,在各种上下文中控制 this
的值和行为。
对象 创建对象
对象字面量(Object Literal) : 这是创建对象最直接和常用的方法。
1 2 3 4 5 6 7 8 9 const person = { name : 'John' , age : 30 , greet : function (name ) { console .log (`Hello! ${name} ` ); } }; person.greet ("Jackey Song" );
构造函数(Constructor Function) : 定义一个构造函数,然后用 new
关键字创建对象。
1 2 3 4 5 6 7 8 9 10 function Person (name, age ) { this .name = name; this .age = age; this .greet = function ( ) { console .log ('Hello!' ); }; } const person = new Person ('John' , 30 );person.greet ();
Object.create()
方法 : 使用 Object.create()
方法可以创建一个新的对象,指定其原型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const personPrototype = { greet : function ( ) { console .log ('Hello!' ); } }; const person = Object .create (personPrototype);person.name = 'John' ; person.age = 30 ; person.greet = function ( ) { console .log (`hello, my name is${this .name} , and I am ${this .age} years old.` ); } person.greet ();
class
语法 : 使用 ES6 的 class
语法来定义一个类,然后创建对象实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Person { constructor (name, age ) { this .name = name; this .age = age; } greet ( ) { console .log (`Hello, my name is ${this .name} , My age is ${this .age} ` ); } } const person = new Person ('John' , 30 );person.greet ();
Object.assign()
方法 :Object.assign()
可以用于复制对象的属性。虽然它不是直接用来创建对象的方法,但可以用来从现有对象创建新对象。
1 2 3 const person = Object .assign ({}, { name : 'John' , age : 30 , greet : function ( ) { console .log (`I am ${this .name} . My age is ${this .age} .` ); } });person.greet ();
工厂函数(Factory Function) : 工厂函数是一个普通函数,用于创建并返回一个新的对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 function createPerson (name, age ) { return { name : name, age : age, greet : function ( ) { console .log ('Hello!' ); } }; } const person = createPerson ('John' , 30 );person.greet ();
这些方法中,对象字面量
和 class
语法是最直观和常用的,而 Object.create()
和 Object.assign()
提供了更多的灵活性。
1 2 3 var foo = 'bar' ;var baz = {foo}; console .log (baz);
1 2 3 4 function f (x, y ) { return {x, y}; } console .log (f ("abc" , "123" ));
1 2 3 4 5 var o = { method ( ) { return "hello world!" ; } }
属性赋值器 setter 、取值 getter :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 function createStorage ( ) { const storage = {}; return { getItem : function (key ) { return key in storage ? storage[key] : null ; }, setItem : function (key, value ) { storage[key] = value; }, clear : function ( ) { for (let key in storage) { if (storage.hasOwnProperty (key)) { delete storage[key]; } } } }; } const storage = createStorage ();storage.setItem ("name" , "Jackey Song" ); storage.setItem ("age" , "18" ); console .log (storage.getItem ("name" )); console .log (storage.getItem ("age" )); storage.clear (); console .log (storage.getItem ("name" )); console .log (storage.getItem ("age" ));
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 var obj = { setItem : function (key, value ) { this [key] = value; }, getItem : function (key ) { return key in this ? this [key] : null ; }, clear : function ( ) { for (let key in this ) { if (this .hasOwnProperty (key)) { delete this [key]; } } } }; obj.setItem ("name" , "Jackey Song" ); obj.setItem ("age" , "18" ); console .log (obj.getItem ("name" )); console .log (obj.getItem ("age" )); obj.clear (); console .log (obj);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Storage { constructor ( ) { this .storage = {}; } getItem (key ) { return key in this .storage ? this .storage [key] : null ; } setItem (key, value ) { this .storage [key] = value; } clear ( ) { this .storage = {}; } } const storage = new Storage ();storage.setItem ("name" , "Jackey Song" ); storage.setItem ("age" , "18" ); console .log (storage.getItem ("name" )); console .log (storage.getItem ("age" )); storage.clear (); console .log (storage.getItem ("name" )); console .log (storage.getItem ("age" ));
类 在 JavaScript 中,类(class
)是 ES6 引入的用于创建对象和处理继承的一种语法糖。它提供了一种更简洁的方式来定义构造函数和继承结构。
1. 定义类 使用 class
关键字来定义一个类。一个类包含构造函数和方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 class Person { constructor (name, age ) { this .name = name; this .age = age; } greet ( ) { console .log (`Hello, my name is ${this .name} and I am ${this .age} years old.` ); } } const person = new Person ('Jackey' , 1000 );person.greet ();
2. 构造函数 constructor
是一个特殊的方法,用于初始化类的新实例。每次创建类的实例时,constructor
方法会被调用。
3. static 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class MathUtils { static add (a, b ) { return a + b; } static multiply (a, b ) { return a * b; } constructor (name ){ this .name = name; } sub (a, b ) { return a - b; } } console .log (MathUtils .add (2 , 3 )); console .log (MathUtils .multiply (2 , 3 )); var mth = new MathUtils ();console .log (mth.sub (1 ,2 )); console .log (mth.add (1 ,2 ));
1 2 3 4 5 6 7 8 9 10 11 12 class Car { static wheels = 4 ; static getWheels ( ) { return Car .wheels ; } } console .log (Car .wheels ); console .log (Car .getWheels ());
1 2 3 4 5 6 7 8 9 10 class Vehicle { static start ( ) { console .log ('Vehicle started' ); } } class Car extends Vehicle {}Car .start ();
4. 继承 JavaScript 的类可以继承其他类。使用 extends
关键字来创建子类,并使用 super
关键字来调用父类的构造函数和方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Animal { constructor (name ) { this .name = name; } speak ( ) { console .log (`${this .name} makes a noise.` ); } } class Dog extends Animal { speak ( ) { super .speak (); console .log (`${this .name} barks.` ); } } const dog = new Dog ('Rex' );dog.speak ();
5. Getter 和 Setter 类中的 getter 和 setter 用于定义对象属性的访问和设置逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Person { constructor (name ) { this ._name = name; } get name () { return this ._name ; } set name (value ) { if (value.length > 0 ) { this ._name = value; } } } const person = new Person ('Jackey' );console .log (person.name ); person.name = 'John' ; console .log (person.name );
6. 私有属性 ES2022 引入了私有字段,用于定义类的私有属性。这些属性只能在类的内部访问。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Person { #name; constructor (name ) { this .#name = name; } getName ( ) { return this .#name; } } const person = new Person ('Jackey' );console .log (person.getName ());
异步 在 JavaScript 中,异步编程允许执行长时间运行的操作(如网络请求、文件读取等)而不会阻塞程序的其他部分。异步编程有助于提高应用的响应性和性能。以下是 JavaScript 中异步编程的主要概念和技术:
1. 回调函数(Callbacks) 回调函数是最基础的异步编程技术。将一个函数作为参数传递给另一个函数,并在某个操作完成后调用这个回调函数。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 function fetchData (callback ) { setTimeout (() => { const data = 'Some data' ; callback (data); }, 1000 ); } fetchData ((data ) => { console .log (data); }); console .log ("hello" );
2. Promise Promise
是一种更现代的异步处理机制,代表一个操作的最终完成(或失败)及其结果值。它允许你链式地处理多个异步操作。
创建 Promise:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const fetchData = ( ) => { return new Promise ((resolve, reject ) => { setTimeout (() => { const data = 'Some data' ; resolve (data); }, 1000 ); }); }; fetchData () .then (data => { console .log (data); }) .catch (error => { console .error (error); }); console .log ("hello" )
Promise 状态:
Pending
(待定):初始状态,既不是成功也不是失败。
Fulfilled
(已兑现):操作成功完成。
Rejected
(已拒绝):操作失败。
3. Promise.all 和 Promise.race
**Promise.all
**:接受一个包含多个 Promise 对象的数组,只有所有 Promise 都成功时,才会成功,返回一个包含每个 Promise 结果的数组。
1 2 3 4 5 6 7 8 9 10 const promise1 = Promise .resolve ('Data from promise1' );const promise2 = Promise .resolve ('Data from promise2' );Promise .all ([promise1, promise2]) .then (results => { console .log (results); }) .catch (error => { console .error (error); });
**Promise.race
**:接受一个包含多个 Promise 对象的数组,返回第一个完成的 Promise 的结果(不论成功还是失败)。
1 2 3 4 5 6 7 const promise1 = new Promise ((resolve, reject ) => setTimeout (resolve, 500 , 'Result from promise1' ));const promise2 = new Promise ((resolve, reject ) => setTimeout (resolve, 100 , 'Result from promise2' ));Promise .race ([promise1, promise2]) .then (result => { console .log (result); });
4. async/await async
和 await
是 ES2017(ES8)引入的异步编程语法糖,使异步代码看起来更像同步代码。async
用于定义异步函数,await
用于等待 Promise 解析。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const fetchData = ( ) => { return new Promise ((resolve ) => { setTimeout (() => { resolve ('Some data' ); }, 1000 ); }); }; const processData = async ( ) => { try { const data = await fetchData (); console .log (data); } catch (error) { console .error (error); } }; processData ();
特点:
**async
**:声明函数为异步函数,返回一个 Promise。
**await
**:在异步函数内部,暂停代码执行直到 Promise 解决,简化了异步操作的代码。
5. 错误处理 在异步编程中,错误处理是非常重要的。对于 Promise
,可以使用 .catch
处理错误;对于 async/await
,可以使用 try/catch
。
Promise 错误处理:
1 2 3 4 5 6 7 fetchData () .then (data => { console .log (data); }) .catch (error => { console .error (error); });
async/await 错误处理:
1 2 3 4 5 6 7 8 const processData = async ( ) => { try { const data = await fetchData (); console .log (data); } catch (error) { console .error (error); } };
6. 其他异步 API JavaScript 还提供了其他异步 API,例如:
**setTimeout
和 setInterval
**:用于延迟执行代码或定期执行代码。
fetch
API :用于网络请求,基于 Promises。
**XMLHttpRequest
**:传统的网络请求 API,较老,使用回调函数处理异步操作。
回调函数 :最基础的异步处理方式。
Promise :提供链式调用和更好的错误处理。
**Promise.all
和 Promise.race
**:用于处理多个 Promise。
**async/await
**:使异步代码看起来像同步代码,简化异步编程。
错误处理 :无论使用何种异步技术,正确的错误处理是关键。
模块 JavaScript 的模块系统允许将代码组织成独立的、可重用的部分,这样可以更好地管理和维护大型应用程序。
ES6 模块提供了 export
和 import
关键字,用于在不同的 JavaScript 文件之间共享代码。
打开或创建 package.json
文件,添加 "type": "module"
属性。
1. 导出(export
) export
关键字用于将模块中的变量、函数或类暴露给其他模块。可以有两种导出方式:
具名导出(Named Exports) :导出一个或多个具体的变量、函数或类。
1 2 3 4 5 6 7 8 9 10 11 12 export const pi = 3.14 ;export function add (a, b ) { return a + b; } export class Calculator { multiply (a, b ) { return a * b; } }
默认导出(Default Export) :每个模块只能有一个默认导出。默认导出用于导出一个主值(如函数、类或对象)。
1 2 3 4 export default function greet (name ) { return `Hello, ${name} !` ; }
结合具名导出和默认导出:
1 2 3 4 5 6 export const version = '1.0' ;export default function greet (name ) { return `Hello, ${name} !` ; }
2. 导入(import
) import
关键字用于从其他模块中导入导出的内容。导入时,需遵循相应的导出方式:
1 2 3 4 5 6 7 8 import { pi, add, Calculator } from './math.js' ;console .log (pi); console .log (add (2 , 3 )); const calc = new Calculator ();console .log (calc.multiply (2 , 3 ));
1 2 3 4 import greet from './utils.js' ;console .log (greet ('World' ));
导入所有内容 :使用 * as
语法可以导入一个模块的所有导出,并将它们作为一个对象来使用。
1 2 3 4 5 import * as math from './math.js' ;console .log (math.pi ); console .log (math.add (2 , 3 ));
3. 重新导出 可以在一个模块中重新导出其他模块的导出内容:
1 2 3 export { pi, add } from './math.js' ;export { default as greet } from './utils.js' ;
4. 动态导入 ES6 模块还支持动态导入,这允许在运行时按需加载模块。动态导入返回一个 Promise 对象,因此可以使用 .then()
或 await
处理。
1 2 3 4 5 6 7 async function loadModule ( ) { const { default : greet } = await import ('./utils.js' ); console .log (greet ('Dynamic Import' )); } loadModule ();
5. 模块的作用域 每个模块在 JavaScript 中都有自己的作用域。模块中的变量和函数默认是私有的,只有通过 export
才能公开给其他模块。
**export
**:用于导出模块中的内容,可以是具名导出或默认导出。
**import
**:用于从其他模块导入内容,可以导入具名导出、默认导出,或将所有导出作为一个对象导入。
动态导入 :允许在运行时按需加载模块。
模块作用域 :每个模块有自己的作用域,避免了全局命名冲突。