8-JavaScript最佳实践 1. 最佳实践 避免全局变量
请尽量少地使用全局变量
它包括所有的数据类型、对象和函数
全局变量和函数可被其他脚本覆盖
请使用局部变量替代, 并学习如何使用闭包
始终声明局部变量
所有在函数中使用的变量应该被声明为局部变量
局部变量必须通过 var 关键词来声明, 否则它们将变成全局变量
严格模式不允许未声明的变量
为什么使用严格模式
消除Javascript语法的一些不合理、不严谨之处, 减少一些怪异行为
消除代码运行的一些不安全之处, 保证代码运行的安全
提高编译器效率, 增加运行速度
为未来新版本的Javascript做好铺垫。
严格模式
ES6的模块自动采用严格模式, 不管模块头部有没有use strict
严格模式有以下限制
变量必须声明后再使用
函数的参数不能有同名属性, 否则报错
不能使用with语句
不能对只读属性赋值, 否则报错
不能使用前缀0表示⼋进制数, 否则报错
不能删除不可删除的属性, 否则报错
不能使用delete prop
删除变量, 会报错, 只能删除属性delete global[prop]
eval不会在它的外层作用域引入变量
eval和arguments不能被重新赋值
arguments不会自动反映函数参数的变化
不能使用arguments.callee
不能使用arguments.caller
禁止this指向全局对象
不能使用 fn.caller
和 fn.arguments
获取函数调用的堆栈
增加了保留字(比如protected, static 和 interface)
在顶部声明
一项好的编码习惯是把所有声明放在每段脚本或函数的顶部。
这么做的好处是:
获得更整洁的代码
提供了查找局部变量的好位置
更容易避免不需要的全局变量
减少不需要的重新声明的可能性
初始化变量
在您声明变量时对其进行初始化是个好习惯
这么做的好处是:
更整洁的代码
在单独的位置来初始化变量
避免未定义值
请不要声明数值、字符串或布尔对象
请始终将数值、字符串或布尔值视作原始值。而非对象。
如果把这些类型声明为对象, 会拖慢执行速度, 并产生讨厌的副作用
1 2 3 4 5 6 7 8 9 var x = "John" ; var y = new String ("John" ); (x === y) var x = new String ("John" ); var y = new String ("John" ); (x == y)
请勿使用 new Object()
请使用 {}
来代替 new Object()
请使用 ""
来代替 new String()
请使用 0
来代替 new Number()
请使用 false
来代替 new Boolean()
请使用 []
来代替 new Array()
请使用 /()/
来代替 new RegExp()
请使用 function (){}
来代替 new Function()
意识到自动类型转换
请意识到数值会被意外转换为字符串或 NaN(Not a Number)
JavaScript 属于松散类型。变量可包含不同的数据类型, 并且变量能够改变其数据类型
1 2 3 4 5 6 7 8 9 10 11 var x = "Hello" ; x = 5 ; var x = 5 + 7 ; var x = 5 + "7" ; var x = 5 - 7 ; var x = 5 - "7" ; var x = 5 - "x" ; "Hello" - "Dolly"
使用 ===
比较
== 比较运算符总是在比较之前进行类型转换(以匹配类型)
=== 运算符会强制对值和类型进行比较
1 2 3 4 5 6 7 8 0 == "" ; 1 == "1" ; 1 == true ; 0 === "" ; 1 === "1" ; 1 === true ;
使用 Parameter Defaults
如果调用函数时缺少一个参数, 那么这个缺失参数的值会被设置为 undefined
undefined 值会破坏您的代码。为参数设置默认值是一个好习惯
1 2 3 4 5 function myFunction (x, y ) { if (y === undefined ) { y = 0 ; } }
用 default 来结束 switch 1 2 3 4 5 6 7 8 9 10 11 switch (new Date ().getDay ()) { case 0 : day = "Sunday" ; break ; ... case 6 : day = "Saturday" ; break ; default : day = "Unknown" ; }
避免使用 eval()
eval() 函数用于将文本作为代码来允许。在几乎所有情况下, 都没有必要使用它
因为允许任意代码运行, 它同时也意味着安全问题
“For in” Statements
遍历对象内的成员时, 你也会得到方法函数。为了解决这个问题, 应始终将你的代码包装在一个 if 语句中来过滤信息
1 2 3 4 5 for (key in object) { if (object.hasOwnProperty (key ) { ...then do something... } }
减少循环中的活动
循环每迭代一次, 循环中的每条语句, 包括 for 语句, 都会被执行
能够放在循环之外的语句或赋值会使循环运行得更快
1 2 3 4 5 6 7 var i;for (i = 0 ; i < arr.length ; i++) {}var i;var l = arr.length ;for (i = 0 ; i < l; i++) {}
2. Vanilla JS What is Vanilla JS?
vanillaJS 是史上最轻量跨平台前端框架, 没有之一
vanillaJS 小巧而符合直觉, Bootstrap5 舍弃了 JQuery , 使用的知名企业很多
其实就是原生 js
模块化
VanillaJS包括如下模块, 下载安装时可以只选择需要的模块, 以便提高性能。
核心功能、DOM(遍历/选择器)、基于原型的对象系统、AJAX、动画、事件系统、正则表达式、函数作为第一类对象、闭包、数学库、数组库、字符串库
如何使用
通过ID获取元素
JS框架的优点
JS框架封装了复杂困难的代码
JS框架能加快开发速度, 更快完成项目
JS框架让你更专注于产品内容的价值, 而不是实现过程
JS框架让合作更简单, 大家都对基础代码有共同的理解
JS框架还会强迫你练习, 多实践, 顺能生巧
JS框架的问题
每个项目的开发都会遇到框架文档没有说明的问题, 这时候就要深入框架查找原因, 这时候就需要对原生JavaScript的深度掌握
新框架频繁发布, 更新快速, 一旦确定了项目的技术栈, 随着时间, 如何升级更新是个问题
Why Vanilla JavaScript
学会 Vanilla JavaScript能真正理解JS框架, 甚至能为其贡献代码, 还能帮助选择合适的框架
如果不知道Web基本原则, 语言本身的演变和新框架的不断到来。。。
知道纯JS将成为一个能够(不用疯狂搜索原因)解决复杂问题的关键工程师
增加通用能力和生产力, 不管在前端还是后端
创新的工具, 而不只是执行
指导什么时候使用或者不使用框架
更好地了解浏览器和计算机工作原理
3. 正则表达式 什么是正则表达式?
正则表达式是用于匹配字符串中字符组合的模式
正则表达式是构成搜索模式(search pattern)的字符序列
当您搜索文本中的数据时, 您可使用搜索模式来描述您搜索的内容
正则表达式可以是单字符, 或者更复杂的模式
正则表达式可用于执行所有类型的文本搜索和文本替换操作
1 2 3 4 /([\w\-]+\@[\w\-]+\.[\w\-]+)/var re = /ab+c/ ;
使用简单模式
匹配单个字符: .
.
匹配单个字符, 除了换行和行结束符
/.oo.y/
匹配 “Doocy”, “goofy”, “LooNy”, …
修饰符 i
执行对大小写不敏感的匹配
/mart/i
matches “Marty Stepp”, “smart fellow”, “WALMART”, …
特殊字符: |, (), ^, \
| 选择匹配
/abc|def|g/
匹配 “abc”, “def”, or “g”
() 子表达式:把一个表达式分割为几个子表达式是非常有用的
/(Homer|Marge) Simpson/
匹配 “Homer Simpson” or “Marge Simpson”
^ 匹配输入的开始。如果多行标志被设置为 true, 那么也匹配换行符后紧跟的位置
/^A/
并不会匹配 “an A” 中的 ‘A’, 但是会匹配 “An E” 中的 ‘A’
\$ 匹配输入的结束。如果多行标志被设置为 true, 那么也匹配换行符前的位置
例如, /t$/
并不会匹配 “eater” 中的 ‘t’, 但是会匹配 “eat” 中的 ‘t’
转义
如果要匹配特殊字符: /\\$.[]()^*+?
/<br \/>/
匹配 <br />
标记
量词: *, +, ?
* 匹配 0 次或更多次
/abc*/ 匹配 “ab”, “abc”, “abcc”, “abccc”, ..
/a(bc)*/ 匹配 “a”, “abc”, “abcbc”, “abcbcbc”, …
/a.*a/ 匹配 “aa”, “aba”, “a8qa”, “a!?_a”, …
+ 匹配 1 次或更多次
/a(bc)+/
匹配 “abc”, “abcbc”, “abcbcbc”, …
/Goo+gle/
匹配 “Google”, “Gooogle”, “Goooogle”, …
? 匹配 0 或 1 次
更多量词: {min,max}
{min,max}, 允许重复的次数, 其中, “min” 是 0 或一个正整数, “max” 是一个正整数, 而 max > min, 至少匹配 “min” 次, 最多匹配 “max” 次
/a(bc){2,4}/
matches “abcbc”, “abcbcbc”, or “abcbcbcbc”
省略形式
{2,} 匹配至少 2 次
{,6} 匹配最多 6 次
{3} 匹配正好 3 次
字符集: []
[] 字符集。 匹配任何一个包含的字符。
/[bcd]art/
匹配包含 “bart”, “cart”, and “dart” 的字符串
等同于 “/(b|c|d)art/“ 但更短
不需要转义
[]中, 许多修饰符作为正常字符
/what[!*?]*/
matches “what”, “what!”, “what?**!”, “what??!”, …
匹配 DNA (strings of A, C, G, or T) 的正则表达式?
字符范围: [start-end]
可以使用连字符来指定字符范围, 但如果连字符显示为方括号中的第一个或最后一个字符, 则它将被视为作为普通字符包含在字符集中的文字连字符。也可以在字符集中包含字符类。
/[a-z]/
匹配任一小写字母
/[a-zA-Z0-9]/
匹配任一大小写字母和数字
起始 ^ 表示一个否定的或被补充的字符集。也就是说, 它匹配任何没有包含在括号中的字符
/[^abcd]/
匹配除了 a, b, c, or d 以外的字母
字符集中, - 需要转义
/[+\-]?[0-9]+/
匹配 + 或 -, 随后至少一位数字
匹配评估分数 A, B+, or D- 的正则表达式?
字符
特殊字符集:
\d
匹配任何数字(阿拉伯数字)。 相当于 [0-9]
\D
匹配任何非数字(阿拉伯数字)的字符。相当于[^0-9]
\w
匹配基本拉丁字母中的任何字母数字字符, 包括下划线。相当于 [A-Zaz0-9_]
\W
匹配任何不是来自基本拉丁字母的单词字符。相当于 [^A-Zaz0-9_]
\s
匹配一个空白字符, 包括空格、制表符、换页符和换行符
\S
匹配一个非空白字符
至少$1000.00的正则表达式?
JavaScript 正则表达式的使用
在 JavaScript 中, 正则表达式常用于两个字符串方法:search() 和 replace()
1 2 var str = "Visit W3School" ;var n = str.search (/w3school/i );
在 JavaScript 中, RegExp 对象是带有预定义属性和方法的正则表达式对象
使用 test(), test() 方法是一个正则表达式方法
1 2 var patt = /e/ ; patt.test ("The best things in life are free!" );
使用 exec(), 用于检索字符串中的正则表达式的匹配。该函数返回一个数组, 其中存放匹配的结果。如果未找到匹配, 则返回值为 null
1 /e/.exec ("The best things in life are free" );
JavaScript RegExp 对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 var patt = new RegExp (pattern,modifiers);var patt = /pattern/m odifiers;var re = new RegExp ("\\w+" );var re = /\w+/ ;var re = /\\/gm ;var reg = new RegExp ("\\\\" ,"gm" );var foo = "abc\\123" ; console .log (re.test (foo)); console .log (reg.test (foo));
4. 函数 编写更好的函数
Don’t Repeat Yourself (DRY) 不重复造轮子: 封装为函数, 对象模块
Do One Thing (DOT) 一次只做一件事情:提升代码复用性, 易读性与可调式性
Keep It Simple Stupid (KISS) 保持简单:技巧, 高深 晦涩, 一行代码中安排多个原子性任务
Less Is More 少即是多:易读, 避免一次执行多个任务, 函数内容尽可能精简, 不贪多, 代码量独立完成一个功能点, 拆分多个子函数
函数定义
arguments对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function foo ( ) { return arguments .callee ; }foo (); var score = 6 ;if (score>5 ){ function grade ( ) { return 'pass' ; } } else { function grade ( ) { return 'fail' ; } }
函数表达式
函数表达式的优点是可以像给变量赋值一样将函数赋给变量
可以依靠函数表达式可靠地遵循应用程序逻辑。例如, 条件分支中按预期工作
缺点是函数表达式创建匿名函数, 除非显式提供名称
1 2 3 4 var bar = function ( ) { return arguments .callee ; };bar ();
对象字面量(方法字面量) 1 2 3 4 5 6 var baz={ f : function ( ) { return arguments .callee ; } }; baz.f ();
优点
使用对象字面量对相关函数进行分组非常容易。代码更有条理, 可读性更强。上下文中的代码更容易理解和维护
容易拆解和排列:如果模块变得太大, 可以更容易地重新排列代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var lightBulbAPI = { toggle : function ( ) {}, getState : function ( ) {}, off : function ( ) {}, on : function ( ) {}, blink : function ( ) {} };var lightbulbAPI = { toggle : function toggle ( ) {}, getState : function getState ( ) {}, off : function off ( ) {}, on : function on ( ) {}, blink : function blink ( ) {} };
5. 原型
可参考 https://zh.javascript.info/prototype-inheritance
Prototypes
原型对象为所有对象实例所共享, 因此这些实例也共享了原型函数的成员。通过内部属性绑定到原型
1 2 3 4 5 var book = { title : "High Performance JavaScript" , publisher : "Yahoo! Press" };alert (book.toString ());
例子—原型链
1 2 3 4 5 6 7 8 9 10 11 12 13 function Book (title, publisher ) { this .title = title; this .publisher = publisher; }Book .prototype .sayTitle = function ( ) { alert (this .title ); };var book1 = new Book ("High Performance JavaScript" , "Yahoo! Press" );var book2 = new Book ("JavaScript: The Good Parts" , "Yahoo! Press" );alert (book1 instanceof Book ); alert (book1 instanceof Object ); book1.sayTitle (); alert (book1.toString ());
Class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Point { constructor (x, y ) { this .x = x; this .y = y; } toString ( ) { return '(' + this .x + ', ' + this .y + ')' ; } }var p1 = new Point (5 , 5 ); p1.toString (); typeof Point p1.constructor == Point
函数声明可以提升
函数表达式、class 不可以提升, 需要先声明再使用
extends 1 2 3 4 5 6 7 8 9 10 11 12 class Square extends Point { constructor (x ){ super (x, x); } toString ( ){ return super .toString () + 'Square!' ; } }var s1 = new Square (4 ); s1.toString (); s1 instanceof Point s1 instanceof Square
__proto__ 1 2 Square .__proto__ === Point Square .prototype .__proto__ === Point .prototype
super 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class A { constructor (a ){ this .x = a; } } A.prototype .y = 2 ;class B extends A { constructor (a ){ super (); } getY ( ){ super () return super .y } }
原生构造函数的继承 1 2 3 4 5 6 7 8 9 10 11 class MyArray extends Array { constructor (...args ) { super (...args); } }var arr = new MyArray (); arr[0 ] = 12 ; arr.length arr.length = 0 ; arr[0 ]
static 1 2 3 4 5 6 7 8 9 10 11 class A { static add (x, y ){ return x + y; } } A.add (1 , 2 );var a = new A (); a.add ()class B extends A {} B.add (2 , 2 )
6. this
在函数体中, 非显式或隐式地简单调用函数时, 在严格模式下, 函数内的 this 会被绑定到 undefined 上, 在非严格模式下则会被绑定到全局对象 window/global 上
1 2 3 4 5 6 7 8 9 function f1 ( ) { console .log (this ); }function f2 ( ) { "use strict" ; console .log (this ); }f1 (); f2 ();
1 2 3 4 5 6 7 8 9 10 var foo = { bar : 10 , fn : function ( ) { console .log (this ) console .log (this .bar ) } } foo.fn (); var fn1 = foo.fn ;fn1 ();
一般使用 new 方法调用构造函数时, 构造函数内的 this 会被绑定到新创建的对象上
1 2 3 4 5 function Foo ( ) { this .bar = "Lucas" }const instance = new Foo ()console .log (instance.bar )
一般通过 call/apply/bind 方法显示调用函数时, 函数体内的 this 会被绑定到指定参数的对象上
1 2 3 4 5 6 7 8 9 10 const foo = { name : 'lucas' , logName : function ( ) { console .log (this .name ) } }const bar = { name : 'mike' }console .log (foo.logName .call (bar))
一般通过上下文对象调用函数时, 函数体内的this会被绑定到该对象上
1 2 3 4 5 6 7 var student = { name : 'Lucas' , fn : function ( ) { return this } }console .log (student.fn () === student)
1 2 3 4 5 6 7 8 9 10 var person = { name : 'Lucas' , brother : { name : 'Mike' , fn : function ( ) { return this .name } } }console .log (person.brother .fn ())
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 var o1 = { text : 'o1' , fn : function ( ) { return this .text } }var o2 = { text : 'o2' , fn : function ( ) { return o1.fn () } }var o3 = { text : 'o3' , fn : function ( ) { var fn = o1.fn return fn () } }console .log (o1.fn ()) console .log (o2.fn ()) console .log (o3.fn ())
在箭头函数中, this的指向是由外层(函数或全局)作用域来决定的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const foo = { fn : function ( ){ setTimeout (function ( ) { console .log (this ) }) } }console .log (foo.fn ()) var foo = { fn : function ( ){ setTimeout (() => { console .log (this ) }) } }console .log (foo.fn ())
this优先级
显式绑定:通过call、apply、bind、new
隐式绑定:根据调用关系确定this指向
1 2 3 4 5 6 7 8 9 10 11 12 13 function foo (a ) { console .log (this .a ) }var obj1 = { a : 1 , foo : foo }var obj2 = { a : 2 , foo : foo } obj1.foo .call (obj2) obj2.foo .call (obj1)
1 2 3 4 5 6 7 8 9 10 function foo (a ) { this .a = a }var obj1 = {}var bar = foo.bind (obj1)bar (2 )console .log (obj1.a ) var baz = new bar (3 )console .log (baz.a )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function foo ( ) { return a => { console .log (this .a ) }; }var obj1 = { a : 2 }var obj2 = { a : 3 }var bar = foo.call (obj1) console .log (bar.call (obj2))
7. Babel Babel
Babel 是一个 JavaScript 编译器
Babel 是一个工具链, 主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法, 以便能够运行在当前和旧版本的浏览器或其他环境中
下面列出的是 Babel 能为你做的事情:
语法转换
通过 Polyfill 方式在目标环境中添加缺失的特性 (通过 @babel/polyfill 模块)
源码转换 (codemods)
…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const sum = (a,b )=>a+b"use strict" ;var sum = function sum (a, b ) { return a + b; };for (let i=0 ;i<5 ;i++){} console .log (i);for (var i=0 ;i<5 ;i++){} console .log (i);"use strict" ;for (var _i = 0 ; _i < 5 ; _i++) {}console .log (i);for (var i = 0 ; i < 5 ; i++) {}console .log (i);