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 ('example@example.com' ));
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**:用于从其他模块导入内容,可以导入具名导出、默认导出,或将所有导出作为一个对象导入。
动态导入 :允许在运行时按需加载模块。
模块作用域 :每个模块有自己的作用域,避免了全局命名冲突。