前端基本素养与代码规范

395 阅读10分钟

1.css命名类时不要缩写,不要类似把class="container" => class="ctn"
2.不该多写的css一句不要多写 margin-left:auto; margin-right:auto;绝对优于margin:0 auto
3.构造函数的名字要首字母大写且名词词性 比如 new Object() , new Person()
4.普通函数要动词开头且开头小写 createUser
5.用递归/循环 seTimeout去代替setInterval
6.原生JS DOM 查询元素时只用querySelector与querySelectorAll,不要使用getElementsByXXX
7.原生JS获取属性用setAttribute与getAttribute就够了
8.node.dataset-xxx 类似这种 或者是node.class 这种低级错误不用犯

  • 前者正确写法node.dataset.xxx JS会把-xxx误认为是减法
  • 后者呢则是保留的关键字应改为 className 9.jQuery变量声明的基本尊重
let $div = $(...) 

10.meta标签去复制淘宝的即可
11.保底值

let c=10
let b;
let a=b||c;
console.log(a)

12.如果有值则执行
如果值不是null undefined 0 '' false 即可执行

  • 0可能有潜在风险
let content=''
if(content){
     alert("执行");
}

12.1 当前者是falsy时就执行后者

  • 短路逻辑
  • 但只适用于后面仅有一句话的简单操作
null||console.log('123')

12.2 如果有就维持现状,如果没有就给个空数组

// 这有一个哈希表
let arr=[{n:1,content:'a'},{n:1,content:'b'},{n:1,content:'c'},{n:2,content:'d'},{n:3,content:'e'}]

// 对这个哈希表 重新分组
let obj={};
arr.forEach((item)=>{
const {n,content}=item
obj[n]=obj[n]||[]; // 如果有就维持现状,如果没有就给个空数组
obj[n].push(item);
})
console.log(obj)

13.避免过于冗长的if else嵌套

  • 就好像英语不喜欢头重脚轻,喜欢提前把主要的抛在前面
  • 那么这种做法也是可以借鉴的
function func(){
  var result;
  if( conditionA ) {
    if( condintionB ) {
        result = 'Success';
    } else {
        result = 'Error1';
    }
  } else {
    result = 'Error2'
  }
  return result;
}
function func(){
  if( !conditionA ) {
    return 'Error2'
  }
  if( !condintionB ) {
    return 'Error1'
  }
  return 'Success';
}

14.foreach避免滥用,可以函数式编程代替以增加语义性

  • 遍历的时候也经常产生大量的嵌套,
  • 如下代码所示,
  • 我们先对数组元素做一次合法性校验,通过后再做一次新的操作,最后把操作结果追加到新数组里。
const func = (arr) => {
    const res = []
    arr.forEach( (e) => {
        if( e !== 'Onion' ){
            res.push(`${e} Run!`)
        }
    })
    return res;
}

仔细观察这就是一个filter加map的过程。我们不妨试试函数式编程:

const func = (arr) => {
    return arr
            .filer( (e) => e !== 'Onion' )
            .map( (e) => `${e} Run!` );
}

15.判断一个变量的合法性,用Array.includes

  • 如果一个变量的值是以下10种情况之一,那么就...
const init(type) {
    if( type === 'Seminar' || type === 'Interview' ) {
        console.log('valid');
    }
    ...
    console.error('invalide');
}

如果合法的类型只有两种,代码其实也没啥问题。只是一般的业务很容易有后续的延展。今后将合法类型增加到10种的话,上述代码里将是一大长串的if判断。这种代码可读性极差,我们不如转换一下思想,把非法类型储到数组里,用Array.includes来帮你完成冗长的判断。之后每增加一种新的类型,只需在数组后追加新字符串就行了。

const init(type) {
    const invalidArray = ['Seminar', 'Interview']
    if( invalidArray.includes(type) ) {
        console.log('valid');
    }
    ...
}

16.使用object索引 如果变量a的值为value1则返回result1,a的值为value2则返回result2,

const dateFormat = (dateTime) => {
    const format = this.$i18n.locale === 'en' ? 'mmm d, yyyy' : 'yyyy年m月d日';
    return DateFormat(dateTime, format);
}
// 正确写法
const localeFormats = {
    en: 'mmm d, yyyy',
    zh: 'yyyy年m月d日',
    ja: 'yyyy/m/d',
}
const format = localeFormats[this.$i18n.locale];
  1. 检查一个字符串A是以...开头的
let string="http://www.baidu.com"
if(string.indexOf('http://')===0){console.log('123')}

18.从一处拿值,拿完值再做判断,如果这个值是a则给对应结果,不然就不动这个a

const localpath=path==='/'?'/index.html':path;

19.获取一个字符串的后缀

	let localpath='/index.html'
    let index=localpath.lastIndexOf('.');
    let filetype=localpath.substring(index);
    const filetypes={
      '.html':'text/html',
      '.css':'text/css',
      '.js':'text/javascript',
    }
    console.log(filetypes[filetype])
  1. 如果数组根本是空的,最后一项.id给他一个初始值
let arr=[{id:1},{id:2},{id:3}];
arr.splice(0) // 全部删光
let lastOneId=arr[arr.length-1].id; // 错误示范,这句用下面两句去替换
console.log(lastOneId) // 直接error 因为undefined.id哪里有啊?

把错误示范替换为以下,应该去判断item有无,而不是直接取id

let lastOne=arr[arr.length-1];
let lastOneId=lastOne?lastOne.id:1
  1. 获取一个数据的时候永远要考虑,如果拿不到怎么办,如果是空怎么处理
  2. 想知道id为0的对象在不在数组内,在的话就返回这个对象
  3. 对象取值永远用 []
let obj={"0.7809727906203332":{"user_id":1},name:'ryan',age:18}
let a="age"; 

// 成功案例
console.log(obj["0.7809727906203332"])
console.log(obj[0.7809727906203332]) 
console.log(obj["name"]) 
console.log(obj.name) // 虽然可以但不推荐,要记就记通解
console.log(obj[a])  
// 失败案例
console.log(obj.0.7809727906203332) 
console.log(obj."0.7809727906203332")
console.log(obj."name") 
console.log(obj[name])
console.log(obj.a) 
  1. 取子字符串用slice 或者substring 而不要用substr,后者过时了 会被淘汰
  • 我觉得用slice更好,因为字符串数组都可以slice,slice和substring没什么区别 25 localStorage存数据的时候只能存字符串哈 不要存数字和布尔,取出来的时候要判断一下是否为空,给个保底值 26 行为样式分离: js不要去操纵css,不好的习惯,而是去使用addClass removeClass这类
    27 表驱动编程的思想
    就是利用哈希表去让代码变得简化
    28 解构赋值不仅数组还可以对象 三个点可以叫展开运算符, 本质是浅克隆
let obj={name:'ryan'};
let obj1={...obj};  
// 可以用于webpack.config.base.js这类基础配置文件给别的配置文件继承时使用
let obj={name:{name:'ryan'}}
let aaa={...obj}
aaa.name.name=1111
console.log(obj)

28.1 函数传参时的解构赋值

function fn({data}){
  let obj={};
  Object.assign(obj,data)
  console.log(obj)
}
fn({data:{n:1}})
  • 我给fn传一个对象,这个对象中套着一个对象
  • fn接收到这个对象时把里面这个对象取出来放到一个新对象里面去 29 防御性书写代码
console&&console.error&&console.error('这里才执行呢')

30 箭头函数中需要注意的点

先上结论 函数中只有一句话 且为return xxx的时候才可以简写成 { xxx },但如果唯一的一句话不是return xxx的时候就不可以简写!

现在来分析一下代码有何区别,答案是无任何区别

function f1(){
	console.log(1) 
    // f1中虽然没有return语句但是js会自动补上return undefined
}
function f2(){
	return console.log(1) 、
    // 这里依旧会执行console.log(1),并且console.log()这个函数返回值是undefined因此f2()依旧return undefined
}
  • 综上所述,上述两者都是先执行console.log然后再往外return undefined

那么我们接着讨论箭头函数中关于return的问题

function fn(){
	return 111
}
f1=()=>{ fn() }  // 1
f1() // 语句1
f2=()=>fn() // 2
f2() // 语句2
  • 请问语句1与语句2一样吗? 答案当然是 语句1是得到undefined 语句2是得到111
  • 因为2处其实是f2=()=>{ return fn()} 的简化 31 判断如果值是这三个中的任意一个的话,则执行业务代码
let name='a';
if(['a','b','c'].indexOf(name)===0){
	console.log('值为三个中的一个现在执行业务代码')
}
let name='a';
if(['a','b','c'].includes(name)){
	console.log('值为三个中的一个现在执行业务代码')
}
let name='a';
if(name==='a'||name==='b'||name==='c' ){
	console.log('值为三个中的一个现在执行业务代码')
}

32 如果n存在,则n等于n,不存在则n=1

let n=window.location.hash.substr(1)
n=n||1;

33 截取子字符串的api就用substring,不要去用substr

  • substring包括头不包括尾,第二个参数可以理解为是长度
  • 参数不要去写负数!
  • 不会对原字符串修改,要复制新的一份
let str='123'
str.substring(0,str.length) === str
str.substring(0,str.length-1) === '12'

34 对象中的方法慎用箭头函数

// 错误示例
// 箭头函数没有this;
// 对象的花括号也不存在作用域,因为这里指的是 window.bbb === undefined
window.aaa={
  bbb:100,
  fn:()=>{
  console.log(this.bbb)
  }
}
// 正确示例
window.aaa={
  bbb:100,
  fn(){
  console.log(this.bbb)
  }
}
// 正确示例
window.aaa={
  bbb:100,
  fn:function(){
  console.log(this.bbb)
  }
}

35 声明对象时 调用自身属性的两种情况

let obj={name:'ryan',name1:obj.name} 
// 这是错误的,因为这里面的代码在定义的时候就会被执行对吗,
// 这不是函数体内部,思考一下,我刚进入obj的时候还没走出obj就有人说obj.name哪里能拿到呢?
let obj={name:'ryan',name1(){ console.log(obj.name) }} 
// 这是正确的,因为定义的时候并不会跑进函数体内部去执行代码

36 析构语法常用于函数参数是一个对象时

let obj={name:'ryan',id:'007'}
function fn(obj){
  const {name,id}=obj;
  console.log(name)
  console.log(id)
  // 简化了一下两句
  // const name=obj.name;
  // const id=obj.id
  // 也简化了你到处使用obj.name, obj.id
}
fn(obj)

36.1 析构在Vue中也经常用到

const {recordList}=this;
const recordList=this.recordList;

36.2 析构甚至可以用于数组

const [date,time]='今天T七点'.split('T');
console.log(date);
console.log(time);

36.3 析构重命名

let obj={value:'111'}
let {value:valuebababa}=obj
console.log(valuebababa)

37 对象的字面量中是不能够写模板字符串的,这在Vue的class书写中会遇到

let obj={`aaa`:123} // error

37.5 如果对象的key中想要使用变量则请用[]语法,这是es6的内容

let aaa=123
let obj={[aaa]:123}
console.log(obj['123']) // 123

38 const 遇到复杂类型时只能保证地址不给修改,要配合Object.freeze

const 遇到复杂类型时只能保证地址不给修改,而透过地址修改值的诸如push仍然可以修改值

const a=[1,2,3]
a.push(4) // 成功
console.log(a) // [1,2,3,4]
const aaa=Object.freeze([1,2,3])
aaa.push(4) // 不可以

39 如何避免连续多次点按钮发请求?

let isLoading=false; // 外部定义一个变量

// 触发的函数体内部
if(isLoading){return} // 写在第一行 表示你每次点进来我都判断一下,如果正在加载我就直接出去了
isLoading=true; // 第二行赋值
// 接收到参数以后,重新置false
isLoading=false;

js.jirengu.com/wiyocivaya/…

  1. 获取页面宽高
const width=document.documentElement.clientWidth;
if(width<500){
做一些手机上的事情
}
  1. 让带有滚动条的元素滚动到最右侧/ 滚动到最底部
element.scrollLeft/scrollTop = element.scrollWidth;
  1. 隐藏滚动条
 ::-webkit-scrollbar{
      
    }
  1. map函数用于从一个都是对象的数组中分组
let arr = [{name:'张三',age:40},{name:'李四',age:38}];
let nameGroup = _.map(arr,_.property('name'));
let ageGroup = _.map(arr,_.property('age'));
console.log(nameGroup,ageGroup)
  1. sort函数如何比较日期字符串大小
  • 不能相减比较,只能比较大小
var arr=['2020-01-01','2020-02-01','2020-01-07'];
let arr1=arr.sort((a,b)=>{
	if(a>b){
    	return 1;
	}else if(a===b){
    	return 0;
	}else{
		return -1;
	}
})
console.log(arr1)
  1. filter 的正确使用姿势,通常配合 match 一同使用
let arr=['book','abc','look']
let result=arr.filter((item)=>{
	return item.match(/oo/)
})
console.log(result)
  1. 如何理解 filtermap 之间的差异

为什么过滤用的是 filter 而不是 map ? map 不是遍历嘛? 这是因为 return 的逻辑不同

let arr=['book','abc','look']
let result=arr.map((item)=>{ 
	return item.match(/oo/)
})
console.log(result) // [Array(1), null, Array(1)]

为什么 map 配合 match 得到这么奇怪的结果?

  • filter 函数中的 return 用于判断 true/false ,如果为 true 则放进新的数组,如果是 falsy 值则不放入数组,
'book'.match(/00/) // 返回一个数组 filter便认为true
'bo'.match(/00/) // 返回nullfilter便认为false
  • map 中的 return 用于将结果放进新数组中
'book'.match(/00/) // 返回一个数组 map就把这个数组结果放进新数组
'bo'.match(/00/) // 返回null,map就把这个null放进新数组
  1. 制作导航栏的逻辑
预先在 view 中搭好'首页','上一页','下一页'的按钮,然后循环显示一个放在中间的数组
1. data 中设置一个数组 `[1,2,3,4,5,'...']` // 其中 '...' 不可点选  
2. click 时选中样式 .active
3. 点击 下标为4 的元素时逐步后移,点击 下标为0 的元素时逐步前移
4. 点击上一页,下一页时将,调用jquery操作dom点击当前选中按钮的前一个/后一个,

Vue版本在线示例

  1. hover-focus-active 伪类顺序
  1. :hover与onmouseover onmouseout区别
  • 前者纯css控制,无法操纵dom,后者可以操纵dom 在线示例
  1. console.time
console.time(1);
setTimeout(function(){
    console.timeEnd(1);
},1670);