良好习惯
5 个提升 js 编码水平的实例
判断数据类型
typeof 是否能正确判断类型
因为由于历史原因,在判断原始类型时,typeof null会等于object。而且对于对象来说,除了函数,都会转换成 object。
typeof 1; // 'number'
typeof "1"; // 'string'
typeof null; //"object"
typeof []; // 'object'
typeof {}; // 'object'
typeof window.alert; // 'function'
instanceof 是否能正确判断类型
虽然 instanceof 是通过原型链来判断的,但是对于对象来说,Array也会被转换成Object,而且也不能区分基本类型string和boolean。
function Func() {}
const func = new Func();
console.log(func instanceof Func); // true
const obj = {};
const arr = [];
obj instanceof Object; // true
arr instanceof Object; // true
arr instanceof Array; // true
const str = "abc";
const str2 = new String("abc");
str instanceof String; // false
str2 instanceof String; // true
Object.prototype.toString.call();
因为每个对象都有一个 toString()方法,当要将对象表示为文本值或以预期字符串的方式引用对象时,会自动调用该方法。默认情况下,从 Object 派生的每个对象都会继承 toString()方法。如果此方法未在自定义对象中被覆盖,则toString()返回[Object type],其中 type 是对象类型。所以就有以下例子:
Object.prototype.toString.call(new Date()); // [object Date]
Object.prototype.toString.call("1"); // [object String]
Object.prototype.toString.call(1); // [object Numer]
Object.prototype.toString.call(undefined); // [object Undefined]
Object.prototype.toString.call(null); // [object Null]
var type = function(data) {
var toString = Object.prototype.toString;
var dataType =
data instanceof Element
? "element" // 为了统一DOM节点类型输出
: toString
.call(data)
.replace(/\[object\s(.+)\]/, "$1")
.toLowerCase();
return dataType;
};
type("a"); // string
type(1); // number
type(window); // window
type(document.querySelector("h1")); // element
通用的数组/类数组对象封装
但是如果我们要循环一个类数组对象呢?
例如 NodeList。直接循环是会报错的:
document.querySelectorAll("div").map(e => e) // Uncaught TypeError: document.querySelectorAll(...).map is not a function
[...document.querySelectorAll("div")].map(e => e)
var listMap = function(array, type, fn) {
return !fn ? array : Array.prototype[type]["call"](array, fn)
};
var divs = document.querySelectorAll("div");
listMap(divs, "forEach", function(e) {
console.log(e)
});
获取 dom 元素节点的偏移量
牛逼的 reduce
数据去重
const data = [
{
name: "Kris",
age: "24"
},
{
name: "Andy",
age: "25"
},
{
name: "Kitty",
age: "25"
},
{
name: "Andy",
age: "25"
},
{
name: "Kitty",
age: "25"
},
{
name: "Andy",
age: "25"
},
{
name: "Kitty",
age: "25"
}
];
现在我们要去重里面 name 重复的对象,这时候我们可以利用 reduce
const dataReducer = (prev, cur, idx) => {
let obj = {};
const { name } = cur;
obj[name] = cur; //obj:Kris: {name: "Kris", age: "24"}
return {
...prev,
...obj
};
};
const reducedData = data.reduce(dataReducer, {});
let newData = Object.values(reducedData);
// 0: {name: "Kris", age: "24"}
// 1: {name: "Andy", age: "25"}
// 2: {name: "Kitty", age: "25"}
批量生成对象元素
const createList = (item, idx) => {
let obj = {};
obj[`a{idx}`] = "data";
return obj;
};
const listReducer = (acc, cur) => (!acc ? { ...cur } : { ...cur, ...acc });
const obj = Array.from(new Array(20), createList).reduce(listReducer);
强类型检查
用
===代替==
变量
用
知名其意的方式为变量命名,通过这种方式,当再次看到变量名时,就能大概理解其中的用意
不要在变量名中添加额外的不需要的单词
let nameValue;
let theProduct;
let name;
let product;
函数
函数名称应该是
动词或短语,用以说明其背后的意图以及参数的意图。函数的名字应该说明他们做了什么。
避免使用大量参数,理想情况下,函数应该指定两个或更少的参数。参数越少,测试函数就越容易,参数多的情况可以使用对象。
function getUsers(fields, fromDate, toDate) {}
function getUsers({ fields, fromDate, toDate }) {}
使用
默认参数替代 ||操作
一个函数应该只做一件事,不要在一个函数中执行多个操作
使用Object.assign设置对象默认值(back)
const shapeConfig = {
type: 'cube',
width: 200,
height: null,
};
function createShape = (config){
config.type = config.type || 'cube';
config.width = config.width || 200;
config.height = config.height || 250;
}
createShape(shapeConfig);
const shapeConfig = {
type: 'cube',
width: 200,
};
function createShape = (config){
config = Object.assign({
type: 'cube',
widht: 200,
height: 250,
}, config);
}
createShape(shapeConfig);
不要使用标志作为参数,因为它们告诉函数做的比它应该做的多(back)
function createFile(name, isPublic) {
if (isPublic) {
fs.create(`./public/${name}`);
} else {
fs.create(name);
}
}
function createFile(name) {
fs.create(name);
}
function createPublicFile(name) {
createFile(`./public/${name}`);
}
不要污染全局变量,如果需要扩展现有对象,请使用 ES6 类和继承,而不是在原生对象的原型链上创建函数(back)
Array.prototype.myFunc = function myFunc() {};
class SuperArray extends Array {
myFunc() {}
}
条件
避免使用反面条件
使用条件简写,仅对布尔值使用此方法,并且如果确信该值不会是undefined 或null的,则使用此方法
if (isValid === true) {
}
if (isValid) {
}
尽可能避免条件句,而是使用
多态性和继承
Class Car{
getMax(){
switch(this.type){
case 'Ford':
return this.someFactor+this.anotherFactor;
case 'Mazda':
return this.someFactor;
case 'Mclaren':
return this.someFactor-this.anotherFactor;
}
}
}
Class Car {
}
Class Ford extends Car {
getMax(){
return this.someFactor+this.anotherFactor;
}
}
……
类
使用链接,许多库(如 jQuery 和 Lodash)都使用这种模式。在类中,只需在
每个函数的末尾返回this就可以将更多的该类方法链接到它上。
class 是 JS 中新的语法糖,工作方式就像以前的原型但比原型的方式更简单易懂
JavaScript 设计模式
设计原则
Tips:在学习设计模式的时候,尽量分开去学,即先学习设计,然后再去学模式,这样的话,对于之后的理解更加容易一些,什么设计呢,其实就是指设计原则,而模式这里指的就是就是我们要讲的设计模式。现在共有 5 大设计原则,不管是哪种设计模式,都是遵循设计原则的。下面来分别介绍这 5 大设计原则。
- 1.
单一职责原则
单一职责原则原则就是每个程序只负责做好一件事情,如果功能过于复杂就拆分开,每个部分保持独立。这个其实也符合我们当下流行框架 Vue 和 React 的组件化开发,把一个复杂的页面拆分成一些零散的组件,并且每个组件保持独立,同时也可在不同的页面当中实现复用。 - 2.
开放封闭原则
开发封闭原则大白话的意思就是对扩展开放,对修改封闭。放到实际开发中如何去理解呢,我们日常的网站和 APP 开发每周都有发不同的版本来增加需求,那么增加需求的时候,尽量要做到扩展新代码,而非修改已有代码,如果我们修改已有代码无疑增加了风险,因为本来原来的代码是没有问题的,加了新的代码之后必然会增加不可预知的风险,当然有的个别需求必须修改已有代码,这个另说。同时这个原则也是我们软件设计的终极目标。 - 3.
李氏置换原则
子类能够覆盖父类,父类能出现的地方,子类就可以出现,这个原则其实在 Java 等语言当中是较为常见的,多用于继承,而 JavaScript 作为弱类型语言,继承使用其实是很少的,这里简单提一下。 - 4.
接口独立原则
接口独立原则的含义是保持接口的单一独立,避免出现胖接口,JavaScript 中是没有接口(typescript 例外),使用较少, 它是有点类似于单一职责原则,这里更关注接口。 - 5.
依赖倒置原则
依赖倒置原则的含义是面向接口编程,依赖于抽象而不依赖于具体,使用方只关注接口而不关注具体类的实现,同样这里也是 JavaScript 中使用较少(没有接口&弱类型)
设计模式
工厂模式
工厂模式概念
工厂模式是
由一个方法来确定是要创建哪个类的实例,在前端当中最为常见的工厂模式就是new操作的单独封装,当遇到 new 操作的时候,就要考虑是否该使用工厂模式。这里也可以结合生活中的例子去思考。当你去购买汉堡,直接点餐取餐,不会自己亲手做,商店要“封装”做汉堡的工作,做好直接给买者。也就是说通过提供原材料,最终得到是汉堡还是炸鸡,是由你自己决定的。
工厂模式前端中实例
- 1.jQuery 当中的
$('')
jQuery 当中的\$('div'),这里的$选择器就是已经封装好的API,这里我们直接使用即可。下面简单实现一个 JQuery 的\$操作符,帮助大家加深理解。
class jQuery {
constructor(selector) {
let slice = Array.prototype.slice;
let dom = slice.call(document.querySelectorAll(selector));
let len = dom ? dom.length : 0;
for (let i = 0; i < len; i++) {
this[i] = dom[i];
}
this.length = len;
this.selector = selector || "";
}
append(node) {}
html(data) {}
//等等API
}
window.$ = function(selector) {
return new jQuery(selector);
};
- 2.Vue 异步组件
这个大家应该比较熟悉,而且官方文档讲的也非常详细,这里直接饮用官方文档的案例,在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染
Vue.component("async-example", function(resolve, reject) {
setTimeout(function() {
// 向 `resolve` 回调传递组件定义
resolve({
template: "<div>I am async!</div>"
});
}, 1000);
});
单例模式
单例模式
符合单一职责原则,用大白话描述单例模式就是系统中被唯一使用,例如:电子商务网站常见的购物车和登录都是单例模式的运用。
单例模式前端中的实例
-
1.jQuery 中的$('')
仍旧是jQuery当中的$(' ')选择器,整个 jQuery 框架当中有一个这样的选择器。 -
2.Redux 和 Vuex
不管是 Redux 还是 Vuex,里面的状态 store 都是唯一的,Redux中的store只能通过Reducer去修改,而 Vuex 中的 store 只能通过 Mutation 修改,其余修改方式都是错误的。
适配器模式
适配器模式的含义是
旧接口格式和使用者不兼容,中间加一个适配器接口。生活当中随处可见符合适配器模式的例子,如:插头转换器,电脑接口转换器。
适配器模式前端中的实例
ajax({
url: "/getList",
type: "Post",
dataType: "json",
data: {
id: "123"
}
}).done(function() {});
但是这个时候你接到的项目当中都是:\$.ajax({...}),这个时候我们只需要加一层适配器即可,代码如下:
let $ = {
ajax: function(options) {
return ajax(options);
}
};
装饰器模式
装饰器模式,装饰我们可以理解为就是
给一个东西装饰另外一些东西使其更好看,对应到前端中就是为对象添加新功能,并且不改变其原有的结构和功能,这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。在我们日常生活中用到的手机壳就是经典的例子
代理模式
代理模式是使用者无权访问目标对象,中间加代理,通过代理做授权和控制,我们经常会用到这个模式,不管实际的开发也好,还是网络部署当中,都能够看到它的身影。如:科学上网 谷歌搜索
代理模式前端中的实例
- 1.网页中的事件代理
其实网页的事件代理也是非常常考的面试题之一,其实就是把元素绑定到父元素上面,而不是对其下面的每一个子元素都进行相应的绑定,下面举一个具体的实例:
<div id="item">
<a href="#">a1</a>
<a href="#">a2</a>
<a href="#">a3</a>
<a href="#">a4</a>
<div>
<button>点击增加一个a标签</button>
<script>
let div1 = document.getElementById('div1')
div1.addEventListener('click',function(e) {
let target = e.target
if(e.nodeName === 'A') {
alert(target.innerHTML)
}
})
</script>
- 2.jQuery 中的
$proxy
比如我们经常会遇到这样一种情况,如下代码所示
$("#div1").click(function() {
$(this).addClass("red");
});
$("#div1").click(function() {
setTimeout(function() {
// this 不符合期望
$(this).addClass("red");
}, 1000);
});
解决的方法可能有的同学已经想到是先将 this 赋值给一个变量。
$("#div1").click(function() {
setTimeout(function() {
let _this = this;
// this 不符合期望
$(_this).addClass("red");
}, 1000);
});
是的这种方法是对的,但是这样就会增加一个变量,所以这里用$proxy解决更好,代码如下:
$("#div1").click(function() {
setTimeout(
$proxy(function() {
$(this).addClass("red");
}),
1000
);
});
观察者模式
观察者模式就是
只要你作为订阅者订阅了某个事件,当事件触发的时候,发布者就会通知你。这里可以类比我们去咖啡厅点咖啡,当我们点了咖啡之后,就可以去做别的事情,当咖啡完成时,服务员就会叫你来取,你到时候取走咖啡即可。
观察者模式前端中的实例
- 1.网页中的事件绑定
<button id="btn1">btn</button>
<scirpt>
$('#btn1').click(function() {
console.log(1)
})
</script>
- 2.Node.js 的自定义事件 EventEmitter
const EventEmitter = require("events").EventEmitter;
const emitter1 = new EventEmitter();
emitter1.on("some", () => {
//监听some事件
console.log("some event is occured 1");
});
emitter1.on("some", () => {
//监听some事件
console.log("some event is occured 2");
});
emitter.emit("some");
状态模式
- 状态总数(state)是有限的。
- 任一时刻,只处在一种状态之中。
- 某种条件下,会从一种状态转变(transition)到另一种状态。