随处可见的JavaScript学习笔记-基础乱炖篇
题图--引自网络,侵删,请联系我所有的过失在未犯以前,都已定下应处的惩罚
引言
在这一篇,我打算放飞自我,既然是乱炖..那么就可以光明正大的想到什么写什么,全无章法,天马行空。
然,内容不会脱离基础的范围,所以不会多么难懂,来源则是偶然发现的读书笔记和以前在开发中记录的一些小Tip。
既然是Tip,不免会显得零碎,既然有前言,我想定个大纲是为写作之常理,笔记依然。
主要包括以下内容:
- 编程风格和推荐的编程规范(通常团队会有一个规范,虽然不一定强制执行)。
- 一些小知识点,有些可能有助于解决Bug有些则这辈子不会用到第二次。
- 一些常用的代码块,通常来源于网上,多少会掺杂一些个人理解。
和网上大多数的文章一般,由于水平有限,在这种可以有很多解释的概念上,理解往往会出现偏差,这份笔记也不能免俗。
假如有读者的存在,还希望在读这篇时保持自己独立思考的能力,避免被不必要的带偏。
笔者虽会竟可能的去验证这些内容,但毕竟金无足赤,这篇文章输出的内容将会很主观,如有建议还望赐教。
编程规范和一些约定成俗的内容
这部分,我也不一定能严格执行,而且很主观,很多内容仅仅是适用于之前做的项目,可以有选择的遵循或者干脆全然不顾。
“程序是写给人读的,只是偶尔让计算机执行一下”
良好的编程习惯能使人在写代码时思维更加敏捷,编写起来更为流畅,甚至执行起来效率更高。
而统一的编程风格则可以使一个团队的项目具备更好的可读性,通常可读性就意味着便于维护和扩展。
通常情况,一个项目不是作完就算了的,它需要迭代,升级,添加需求。
而如若为了赶工时,而至可读性于不顾,到项目即将完成的拂晓,迎接你的恐怕将是物理上早上四点的太阳。
首先,让我们来看看在我为数尚短的生产生活中,比较常见到的规范。
//绝对不要出现魔法数字和魔法变量,无论何时何地何因都不要
//所谓魔法数字:通常是1,2,3,4
//而魔法变量则通常是:a,b,c,d 它们往往很万能
switch(a){
case 1:
//业务逻辑
..........
//
break;
case 2:
//业务逻辑
.......
//
break;
case 3:
//兴许完全无关的业务逻辑
.....
//
break;
}
//没有注释的情况下这些1 2 3 就是魔法数字,没人知道它是怎么来的,即使有注释它们依旧难懂。
//而a是个魔法变量,没人知道它是做什么的即使有注释。
上面这段代码还有一个问题,没有默认操作,不知道什么时候就会报错,如果之前没有对数据类型做限制,也许会更糟糕。
在实际的生产生活中,在这种代码上做扩展将如履薄冰,有时重新写一遍反而更快。
//那魔法数字和魔法变量该怎么改进呢?
//我看见过一种命名方式:也就是以功能命名,避免使用Flag,来一个例子。
var documenFlag //看到这个鬼知道是什么,某种意义上和魔法变量差不多
var documenFormType //正确的做法
//明显下面这个要好懂得多
//魔法数字通常产生于枚举类型,比如一个Select,很多人图省事直接将数据库里提取出来的值就这么应用于前端页面。
//通常后端对于枚举值会有一个详细的映射关系注释,问题是前端通常没有,虽然在写Select时感觉不出太大影响。
//然而如果这个Select关系到前端的业务逻辑,就会变得很难懂,假如一个月后你需要回来维护你的代码,看到一个
//if(a==2){//业务逻辑}..你会花很多的时间来弄明白a从哪来,2又是什么。
//我个人遇到这种情况,会在进入业务逻辑之前写一个映射,将1,2,3,4,5转换成名词。之后使用这些字符串作判断。
//一些常见的约定
var pageIndex //变量
var _PageIndex //受保护的变量
var PAGE_INDEX //常量
//其它还有p_pageIndex //表参数 g_pageIndex //表全局变量,但这通常不常见
//这是在let,const之前最常见的约定了,即时有Let const依旧可以这么写,只是把var 换掉,它们很好懂。
//一种简单的编码风格
//文件命名
PageIndex.html //双首字母大小写
PageInde.min.js //统一min为压缩混淆后的文件
PageInde-1.0.min.js //‘-’链接版本号
//限定一行的字数比如120个,';' 不可忽略, ',' 分行, 缩进'tab' 四个空格
//这里我无意讨论 ';'的作用,很多人认为不应该写,认为徒增操作量,没用,认为不写更好看。
//然而我认为无论从执行还是语义来讲它还是必要的。
//况且现实来讲,很多现有系统并不是基于ES6或以上的语法编写的,维护它们的时候最好还是把';'加上。
//数学运算
1 + 1 =2 //等于单边空格,运算符双边空格
if (type) {
}
function Add (x) {
}
//() 关键字的另一边空格
// {} 外侧空格
// 即时只有一个If 也不允许写在单行里。
// 避免不同层次变量重名,局部变量集中在一处。比如:
var pageIndex = 0;
function next(a){
var pageIndex = 1, //避免,可以换成 _pageIndex 或者 subIndexPage
lastIndex = 0,
nexIndex = 2;
//纯举例,现实中不会有这种需要
....//业务逻辑
}
就像我之前说的,这些规则并不绝对要求遵守。
要我来说即使只是规范变量命名就能极大的提高代码的可读性了。
编程风格可以做的事无巨细,展开来说也是一本书的内容,既然这里只是基础,我觉得稍微提的这点就足够了。
JavaScript豆知识系列
刚学完JavaScript那会我很喜欢探求一些小知识点,也就是所谓豆知识,它们通常并非完全没什么卵用。
事实上到现在我大都不记得还有啥了,所幸我找到了记在印象笔记上的一些片段。
//声明提前
//js 所有的声明都会被提前,var a=1 ;在执行的时候会变成var a ;/..隔着其它代码.../ a = 1
//函数类型会自动提到顶部去,除非你这么写..var a = function(){}...
//一个局部变量的列子
var scope = "global";
function f() {
alert(scope); //你以为是"global"?
//输出undefined 因为 下面的局部变量声明提前了
var scope = "local"; //覆盖全局变量
alert(scope);
}
//这也是为啥局部变量建议要一起在顶部声明的原因
//页面可优化的部分
1、访问元素的某些属性
2、通过JavaScript修改元素的CSS属性
3、在onScroll中做耗时任务
4、图片的预处理(事先裁剪图片,而不是依赖浏览器在布局时的缩放)
5、在其他Event Handler中做耗时任务
6、过多的动画
7、过多的数据处理(可以考虑放入WebWorker内执行)
//基本没实践过,通常后端业务逻辑接口返回的速度比前端渲染要慢。
//而影响前端渲染的最大因素往往是带宽的大小。我曾经用加带宽这个答案来调戏面试管,关于页面优化的问题..
//深拷贝和浅拷贝
//JavaScript中万物皆对象,而对象通常是引用类型的。
//具体来说所谓对象是存的其实是一个指向内存中某处的地址。
//这就造成了通过 = 号赋值的对象它们指向的是同一个对象,一荣俱荣,一损俱损。
//解决这个问题就是深拷贝,思路通常就是用循环把所有的值取出来赋到一个新对象上去。
/*
jquery中可以使用$.extend深拷贝 $.extend(true,bindData,oldData); 深拷贝
ES6中assign() 可以实现extend的装饰功能,拷贝的对象中的对象和数组是浅拷贝。
*/
//换成代码
function cloneObj(obj){
var newObj=obj.constructor=Arry?[]:{};
if(typeof obj !=="object"){
return;
}
else{
for(key in obj){
newObj[key]=obj[key];
}
}
return newObj;
}
//基础款,内部对象数组还是浅拷贝的..
function deepClone(obj){
var str,newObj=obj.constructor==Array?[]:{};
if(typeof obj!=="object"){
return
}
else{
for(key in obj){
newObj[key]=typeof obj[key]==='object'?deepclons(obj[key]):obj[key];
}
}
return newObj;
}
//加强款,函数还是浅拷贝来的..
//其实现实中..直接转JSON再赋值再转回来就可以了...不会写的这么麻烦..以上两个例子我从来没用过..
//原型继承
/*
构造函数的原型继承,在函数中使用 argument.callee.prototype= 可以的定义原型
Object 操作
获得父级原型对象:
subObj.__proto__ (兼容问题);
Object.getPrototypeOf(obj);
修改 obj.attrName="attrValue"; //自有
Function.prototype.attrName="attrValue"; //原型
删除 delete 只能删除自有成员
判断属性自有:obj.hasOwnProperty("attrName");
自定义子对象的原型
childObj.__proto__=FatherObj //兼容问题
Object.setPrototpeOf(childObj,FatherObj);
ECM5 的create方法
var a=Object.create( 原型对象 );
可以传入不同的值得到一个继承该原型对象的object 可传入null得到一个没有原型的纯粹的object
也可传入Array等得到继承相应构造函数的object。
*/
//除了hasOwnProperty很多时候很有用..其它基本没怎么用过..
//实践中有一次将一个函数挂到Object上去..最后出了一万个BUG..
//从此,对于操作内置对象都是谨慎小心。
//跨域,不知道为什么,很多人面试喜欢问跨域,然而在实践中,基本不会出现跨域的场景,对于前端来说这不安全。
//当然也可以说是我菜。
//简单来说,通过scrpit和img标签去请求别的域名的js文件就是最简单的跨域做法。
//还需要更复杂的操作,要么交给后端..要么去找大佬吧。
//冒泡和拖拽
/*
addEventListener('事件名',函数对象,捕获)
捕获:true false true 捕获阶段提前触发,false 冒泡阶段触发
事件触发的三个阶段:1、捕获阶段,2、触发 3、,冒泡阶段
Event操作
取消冒泡:
e.stopPropagation()
e.target 获取事件的目标函数
取消事件:e.preventDefault();
事件的坐标:
相对于屏幕:e.screenX/screenY
相对于页面左上角的坐标: e.pageX/pageY
相对于文档显示区左上角的坐标: e.clientX/x e.clienY/y
相对于元素左上角的坐标 offsetX/offsetY
*/
//我曾经背过拖拽的的面试题,当时可以说是理解的深入骨髓,但是随着之后从来没用过,很快就忘完了。
常用JavaScript Tip及代码块
在中小型项目中,JavaScript往往东一榔头西一锤子的拼凑,自己写的可能更多的是数据处理。
这时候找现成代码的本事通常会是决定了一个前端晚上十一点是坐家里喝酸奶,还是坐在办公室喝咖啡。
//无题
console.time(label) console.timeEnd(label) //测试一段方法开始和结束的时间
encodeURI(str) ;encodeURIComponent(str); //中文的编码和解码,避免乱码用两层
encodeURI() //编码
encodeURIComponent()//编码
decodeURI() //解码
decodeURIComponent() //解码
base64 API //原生不支持中文
window.atob(); //解码
window.btoa(); // 编码
//过滤HTML结构
function filterHtml(str) {
str = str.replace(/<\/?[^>]*>/g,''); //去除HTML tag
str.value = str.replace(/[ | ]*\n/g,'\n'); //去除行尾空白
str = str.replace(/\n[\s| | ]*\r/g,'\n'); //去除多余空行
return str;
}
//去空格
function trim(str){
return str.replace(/\s|\xA0/g,"");
}
//url 参数获取
function getQueryString(name) {
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
var r = window.location.search.substr(1).match(reg);
if (r != null) {
return unescape(r[2]);
}
return null;
}
//js 键盘
document.onkeydown = function(e){
var keyNum = window.event ? e.keyCode : e.which; //window.event 兼容IE
//判断keyNum的值给对应事件
}
jQuery:
$(document).keydown(function(e){
var keyNum = e;
});
//浏览器视口
jquery:
$(document).ready(function()
{
alert($(window).height()); //浏览器时下窗口可视区域高度
alert($(document).height()); //浏览器时下窗口文档的高度
alert($(document.body).height());//浏览器时下窗口文档body的高度
alert($(document.body).outerHeight(true));//浏览器时下窗口文档body的总高度 包括border padding margin
alert($(window).width()); //浏览器时下窗口可视区域宽度
alert($(document).width());//浏览器时下窗口文档对于象宽度
alert($(document.body).width());//浏览器时下窗口文档body的高度
alert($(document.body).outerWidth(true));//浏览器时下窗口文档body的总宽度 包括border padding margin
}
)
浏览器窗口改变事件:
$(window).resize(function(){
});
//表单JSON化
$.fn.serializeObject = function()
{
var obj = {};
var arr = this.serializeArray();
$.each(arr, function() {
if (obj[this.name]) {
if (!obj[this.name].push) {
obj[this.name] = [obj[this.name]];
}
obj[this.name].push(this.value || '');
} else {
obj[this.name] = this.value || '';
}
});
return obj;
};
//一种基于闭包写的插件方法
(function(window,document){
//逻辑代码
window.methodName = window.methodName || methodName
})(window,document)
//阻止冒泡
原生JS
function (e){
var event = e || window.event ;//兼容写法
//阻止冒泡
event.cancelBubble = true; //IE
event.stopPropagation();
//阻止默认事件(a标签,input)
event.returnValue = false; //IE
event.parentDefault();
//使用jq的话
return false //既能阻止冒泡,也能阻止默认事件。
}
//六位随机验证码
// 方法一
('000000' + Math.floor(Math.random() * 999999)).slice(-6);
// 方法二
Math.random().toString().slice(-6);
// 方法三
Math.random().toFixed(6).slice(-6);
// 方法四
'' + Math.floor(Math.random() * 999999);
//十六进制颜色代码
function() {
return '#'+('00000'+
(Math.random()*0x1000000<<0).toString(16)).slice(-6)};
//多维数组展开
var foo = [1, [2, 3], ['4', 5, ['6',7,[8]]], [9], 10];
// 方法一
// 限制:数组项不能出现`,`,同时数组项全部变成了字符数字
foo.toString().split(','); // ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
// 方法二
// 转换后数组项全部变成数字了
eval('[' + foo + ']'); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// 方法三,使用ES6展开操作符
// 写法太过麻烦,太过死板
[1, ...[2, 3], ...['4', 5, ...['6',7,...[8]]], ...[9], 10]; // [1, 2, 3, "4", 5, "6", 7, 8, 9, 10]
// 方法四
JSON.parse(`[${JSON.stringify(foo).replace(/\[|]/g, '')}]`); // [1, 2, 3, "4", 5, "6", 7, 8, 9, 10]
// 方法五
const flatten = (ary) => ary.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []);
flatten(foo); // [1, 2, 3, "4", 5, "6", 7, 8, 9, 10]
// 方法六
function flatten(a) {
return Array.isArray(a) ? [].concat(...a.map(flatten)) : a;
}
flatten(foo); // [1, 2, 3, "4", 5, "6", 7, 8, 9, 10]
//特殊字符转译
function htmlspecialchars (str) {
var str = str.toString().replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"');
return str;
}
htmlspecialchars('&jfkds<>'); // "&jfkds<>"
事实上,过度依赖找现成的代码会逐渐影响创造力。
但是以轮播为例,你不可能在十分钟内写出比现成库还要好的轮播。而使用现成的库做轮播十分钟已经足够了。
然而只会用现成的库,这将使你成为一个正真的码农,虽然很多时候这是没有办法的事情。
但至少,把你在用的库的原理搞懂,不然假如让你定制它所不具备的功能时你会很尴尬。
好吧,通常这种时候你会去找满足需求的库,我知道的。
结语
这是一篇没什么目的性的笔记,主要由我俩年前的学习笔记和偶尔记录一下的工作笔记拼凑而成。
以文学来类比就是散文了,最后的代码块,网上估计很多文章中都出现过,侧面反映了它们很有用。
然后其实真正决定你晚上十一点的并不是这个,而是你知道的功能库,以及你们老大能给你争取多少时间。
更多的时候其实更依赖于你所在的团队到底几个人...
至此,我认为算是JavaScript基础的部分基本就结束了。
之后,要还能写,也许就是ES6 Node 设计模式,项目实践一类的。
幸运的是它们依旧随处可见。