首页 > 技术文章 > [已读带总结] Effective JavaScript 编写高质量JavaScript代码的68个有效方法

pengchenggang 2021-05-13 14:05 原文

目录

电子书下载:https://www.jb51.net/books/328297.html

第2章

第11条 熟练掌握闭包

https://www.cnblogs.com/wengxuesong/p/5499005.html
总结 函数传递的信息包含函数本身,和它的外层环境。(这句话是能否理解透js的根本)

// 通用版闭包
(function(window, undefined){})(window)

// 做 leaflet 我改了一下
(function(L, undefined){})(L)

第3章 使用函数

第18条 理解函数调用、方法调用及构造函数调用之间的不同

https://www.cnblogs.com/wengxuesong/p/5526533.html
简单说 function 内部的 this为父级对象。
如果这个function在对象中,this就是这个对象。
如果这个function在全局中,this就是window
如果这个function是构造函数,this就是new的时候,外部包了一个对象{},this指向了这个对象。

第19条 熟练掌握高阶函数

https://www.cnblogs.com/wengxuesong/p/5531242.html
高阶函数,简单说就是 function的入参或return出参是函数。
一般情况就是 function的入参是函数,最常见的例子就是callback入参或者map some sort等函数
函数当入参的核心含义就是把具体业务逻辑踢出去,把已确定的业务包起来。

第20条 使用call方法自定义接收者来调用方法

https://www.cnblogs.com/wengxuesong/p/5532254.html
1 用函数的call方法,指定函数内的this

function abc (a1) {
  // 这里的this指向父级
}
abc('a') // 函数体内指向父级,这里要是在页面就指向window

var aaa = {}
abc.call(aaa,'a') // 这个时候 aaa对象就是 abc函数的this,后面参数正常传送

2 入参是个函数的时候,可以调用call,更改,入参函数的this (俗称高阶函数)

var table = {
  fff: function (text) {
    console.info('text', text) // this指向父级
  }
}

function abc(f, objThis) {
  f.call(objThis,'a') // 这里this指向的就是objThis
}

abc(table.fff, table) // 这样fff的函数里的this,就指向 table了

第21条 使用apply方法通过不同数量的参数调用函数

https://www.cnblogs.com/wengxuesong/p/5545281.html
apply()方法接收两个参数,一个是对象,一个是参数数组。
apply(objThis, paramsArr) 这里的第一个参数 和 call的一样, 所以重点就是第2个参数, paramsArr,就是可变参数包成了一个数组。
如果 apply就一个参数的话 .apply(objThis) === .call(objThis)

arguments 是函数内接受的数组名称 举个例子

function abc () {
  console.info('arguments', arguments)
}

ES5 bind()方法
bind(objThis)也是接收一个参数,和call基本一样,区别是 它返回函数,并不运行。
举例:

var color='red';
var o={color:'blue'};
function sayColor(){
    console.log(this.color);
}
var oSayColor=sayColor.bind(o);
oSayColor();//"blue"

第22条 使用arguments创建可变参数的函数

https://www.cnblogs.com/wengxuesong/p/5549553.html
arguments 就是将参数 打包 变成一个数组
几个属性
arguments[0] 第1个参数
arguments.length length代表参数长度。
arguments.callee callee是一个指针指向拥有这个arguments对象的函数。
arguments.caller caller是一个在ES3并没有定义的属性。
callee 是指向函数,caller是谁调用的函数

在严格模式下,arguments.callee和arguments.caller都会报错。
ES5中arguments.caller的值始终是undefined。

第23条 永远不要修改arguments对象

https://www.cnblogs.com/pengchenggang/p/14764316.html

  1. 注意 arguments 并不是个真数组,他是一个对象
  2. 转化数组用 var args=[].slice.call(arguments);

第24条 使用变量保存arguments对象

https://www.cnblogs.com/wengxuesong/p/5553760.html
迭代器(iterator)的小例子,里面return的时候,调用父级的arguments,直接写的话就是本级的arguments,所以可以把父级的arguments 存个变量,里面引用。
这个类似 const _this = this 的概念,可以 const _arguments = arguments

第25条 使用bind方法提取具有确定接收者的方法

https://www.cnblogs.com/wengxuesong/p/5555714.html
bind(objThis) === 绑定函数的this功能,返回当前函数

var you={
    gF:[],
    add:function(s){
        this.gF.push(s);
    },
    all:function(){
        return this.gF.join("-");
    }
}

var girls=["西施","王昭君","貂蝉","杨贵妃"];//按人物历史出场顺序,呵呵
girls.forEach(you.add.bind(you));
you.all();//"西施,王昭君,貂蝉,杨贵妃"

girls.forEach(you.add.bind(you)); 这里如果不用bind(you), you.add函数里面的this,在forEach就是指向forEach了。这里bind(you)后,this就是you对象,就不会错了。
简单说,就是参数的一个反向填写的技术,正常都是 obj.abc('a'),现在这个是 ['a'].forEach(obj.abc.bind(obj))

第26条 使用bind方法实现函数的柯里化

https://www.cnblogs.com/wengxuesong/p/5563057.html
什么是柯里化?

  1. 函数外包层函数。 2. 入参简化 3. 被简化的入参提供默认值

第27条 使用闭包而不是字符串来封装代码

https://www.cnblogs.com/wengxuesong/p/5563369.html
1 闭包 包含父级信息 如变量 函数等
2 字符串+eval 不包含父级信息 它的父级信息是window
总结 字符串+eval 会污染window,并可能造成冲突,闭包优势明显

第28条 不要信赖函数对象的toString方法

https://www.cnblogs.com/wengxuesong/p/5567790.html
总结 toString方法 各厂商实现不一 并且不能显示闭包代码,可参考使用。

第29条 避免使用非标准的栈检查属性

https://www.cnblogs.com/wengxuesong/p/5570821.html
总结 arguments.callee 和 arguments.caller 谨慎使用,因为不一定被支持。

第3章 使用函数--个人总结

https://www.cnblogs.com/wengxuesong/p/5577683.html

第4章 对象和原型

第30条 理解prototype、getPrototypeOf和__ptoto__之间的不同

https://www.cnblogs.com/wengxuesong/p/5580231.html
1 prototype 是 Class类 的定义属性
2 __proto__ 是 new后对象的 属性
3 getPrototypeOf 是 通过new后的对象,获取 prototype

Object.getPrototypeOf(u)===User.prototype;//true

第31条 使用Object.getPrototypeOf函数而不要使用__proto__属性

https://www.cnblogs.com/wengxuesong/p/5580458.html
总结 getPrototypeOf常有 __proto__不常有

第32条 始终不要修改__proto__属性

https://www.cnblogs.com/wengxuesong/p/5580797.html
总结 本来__proto__就不常有,要是再修改,结果可能无法保证。

第33条 使构造函数与new操作符无关

https://www.cnblogs.com/wengxuesong/p/5583089.html
总结 构造函数内判断,如果调用者没有通过new生成对象,就自己new一个对象 return

这里的教学意义更大一些,主要讲下单继承和多继承的算法

第34条 在原型中存储方法

https://www.cnblogs.com/wengxuesong/p/5584984.html

User.prototype.checkPwd=function(pwd){
  return hash(pwd)===this.pwd;
}

prototype Class原型定义方法,new后的对象共用这个方法,不会造成内存浪费

第35条 使用闭包存储私有数据

https://www.cnblogs.com/wengxuesong/p/5584996.html
总结:强调闭包中的变量为私有变量,外层访问不到。这样更具有安全性。

第36条 只将实例状态存储在实例对象中

https://www.cnblogs.com/wengxuesong/p/5589845.html
总结:如果将数据放到原型中,那么数据就会共享。当new多个对象的时候,就会发生数据错乱。

第37条 认识到this变量的隐式绑定问题

https://www.cnblogs.com/wengxuesong/p/5589854.html
总结:callback中的this是内部的this,不是外部的this
解决方案1 map方法考虑周到,带了个绑定外部this的参

lines.map(function(line){ return line.split(this.regexp); },this);

解决方案2 创建self=this方法

var self=this;
  return lines.map(function(line){
     return line.split(self.regexp);
  });

解决方案3 使用.bind(this)方法

return lines.map(function(line){
     return line.split(this.regexp);
  }.bind(this));

第38条 在子类的构造函数中调用父类的构造函数

https://www.cnblogs.com/wengxuesong/p/5592972.html

function SpaceShip(scene,x,y){ // 子类
  Actor.call(this,scene,x,y); // 父类 注意这里没有new,用的call绑定当前子类的this为父类this
  this.points=0;
}
// 将父类的prototype绑定到子类上,因为构造函数运行和父类的属性是独立的
SpaceShip.prototype=Object.create(Actor.prototype);

总结 这个感觉是好古老的继承方法。但是学会了感觉很透彻。

第39条 不要重用父类的属性名

https://www.cnblogs.com/wengxuesong/p/5594476.html
总结 基于上一个古老的继承方式,这个继承方式,是把父类的全部都灌到子类,这里父类拥有id属性,子类也有id属性,然后就冲突了。

这种继承方式,就像是全局变量污染window一样,不好用,处处小心。

第40条:避免继承标准类

https://www.cnblogs.com/wengxuesong/p/5602793.html
总结 继承标准类后,标准类的特殊方法不会在子类中,还要在子类重写,意义性质不大。

第41条 将原型视为实现细节

https://www.cnblogs.com/wengxuesong/p/5602798.html
总结 原型方法就是公共方法,公共方法写完的就不要进行修改,修改就会出问题。A调用,改好后,B调用就可能报错。
里面还有一句:对象是接口,原型是实现。这个类似java的 接口和实现,对此有待考究。

第42条 避免使用轻率的猴子补丁

https://www.cnblogs.com/wengxuesong/p/5602806.html
猴子补丁:后期在原型上添加公共函数
ployfill: 垫片 判断是否有此函数,如果没有,再进行赋值函数

if(typeof Array.prototype.map!=="function"){
    Array.prototype.map=function(f,thisArg){
       var res=[];
       for(var i=0,n=this.length;i < n;i++){
         res[i]=f.call(thisArg,this[i],i);
       }
       return res;
   }
}

总结 共用的函数 就不要产生覆盖,简单说你可以用你内部的函数。

第4章 对象和原型--个人总结

https://www.cnblogs.com/wengxuesong/p/5602841.html

第5章 数组和字典

第43条 使用Object的直接实例构造轻量级的字典

https://www.cnblogs.com/wengxuesong/p/5609493.html
只返回对象自身的可枚举属性

var triangle = {a:1, b:2, c:3};
 
function ColoredTriangle() {
  this.color = "red";
}
 
ColoredTriangle.prototype = triangle;
 
var obj = new ColoredTriangle();
 
for (var prop in obj) { // 返回 所有属性 包括方法 属性 原型
  if( obj.hasOwnProperty( prop ) ) { // 不包含原型
    console.log("o." + prop + " = " + obj[prop]);
  }
}
// "o.color = red"

总结 获取obj的包含原型的所有属性 和 obj不包含原型的所有属性

第44条 使用null原型以防止原型污染

https://www.cnblogs.com/wengxuesong/p/5613178.html

var x=Object.create(null);
Object.getPrototypeOf(x)==null;//true

总结 null这个空原型很干净

第45条 使用hasOwnProperty方法以避免原型污染

https://www.cnblogs.com/wengxuesong/p/5613189.html

var dict={};
Object.prototpye.hasOwnProperty.call(dict, 'zhangsan') // false

总结 直接用Object的方法,可以避免继承的子类将此方法污染,用call将那个对象,也就是this传入,就能正确调用了。

第46条 使用数组而不要使用字典来存储有序集合

https://www.cnblogs.com/wengxuesong/p/5619745.html
总结 {}无序 []有序

第47条 绝不要在Object.prototype中增加可枚举的属性

https://www.cnblogs.com/wengxuesong/p/5619747.html
设置不可枚举的方法

Object.defineProperty(Object.prototype,'allKeys',{
    value:function(){
        var res=[];
        for(var key in this){
           res.push(key);
        }
        return res;
    },
    writable:true,
    enumerable:false,
    configurable:true
});

({a:1,b:2,c:3}).allKeys();//['a','b','c']

总结 Object是有定义私有函数的方法的,请大家认真对待。

第48条:避免在枚举期间修改对象

https://www.cnblogs.com/wengxuesong/p/5622204.html
总结 使用for...in循环遍历对象,然后又修改这个对象。no咗no代,thx。
这篇里主要讲解了网络的指针指向,然后遍历寻找是否包含。

第49条:数组迭代要优先使用for循环而不是for...in循环

https://www.cnblogs.com/wengxuesong/p/5622211.html
总结 for(;;)遍历数组 for(in)遍历对象,for(in)会遍历出原型属性

第50条:迭代方法优于循环

https://www.cnblogs.com/wengxuesong/p/5626137.html
总结 下面的请熟练使用

Array.prototype.forEach
Array.prototype.map
Array.prototype.filter
Array.prototype.some
Array.prototype.every

第51条:在类数组对象上复用通用的数组方法

https://www.cnblogs.com/wengxuesong/p/5626364.html
总结 arguments 就是 类数组对象
类数组对象 看如下代码就明白了

var arrayLike={0:'a',1:'b',2:'c',length:3};
var res=Array.prototype.map.call(arrayLike,function(s){
    return s.toUpperCase();
});
res;//['A','B','C']

第52条:数组字面量优于数组构造函数

https://www.cnblogs.com/wengxuesong/p/5626367.html
总结 数组字面量 var a=[1,2,3,5,7,8];
数组构造函数 var a=new Array(1,2,3,5,7,8);
[]new Array好,因为Array可能会被人复写。

这得是谁和这个代码有仇啊 复写Array

第5章:数组和字典--个人总结

https://www.cnblogs.com/wengxuesong/p/5628934.html

第6章 库和API设计

第53条:保持一致的约定

https://www.cnblogs.com/wengxuesong/p/5633581.html
总结 入参的两种 1 顺序 2 对象 不论哪种,大家有共识就好。

第54条:将undefined看做“没有值”

https://www.cnblogs.com/wengxuesong/p/5633593.html
总结 如何获得undefined 见下面的代码

var x; // 未赋值的变量的初始值即为undefined。
x;//undefined

var obj={}; // 访问对象不存在的属性也会产生undefined。
obj.x;//undefined

// 一个函数体结尾使用未带参数的return语句,或未使用return语句都会返回值undefined。
function f(){ 
    return;
}
function g(){}
f();//undefined
g();//undefined

// 未给函数参数提供实参则该参数的值为undefined。
function f(x){
    return x;
}
f();//undefined

第55条:接收关键字参数的选项对象

https://www.cnblogs.com/wengxuesong/p/5640493.html
总结 当函数入参较多的时候,用对象传参

doSomething({
  sex: 'girl',
  yourName: 'Ami'
})

第56条:避免不必要的状态

https://www.cnblogs.com/wengxuesong/p/5641325.html
总结 无状态函数 就是 纯函数,有状态的函数 就是和内部有关联。尽量写纯函数。

第57条:使用结构类型设计灵活的接口

https://www.cnblogs.com/wengxuesong/p/5644673.html
总结 面向接口编程

var app=new Wiki(Wiki.formats.MEDIAWIKI);

function Wiki(format){
    this.format=format;
}

Wiki.prototype.displayPage=function(source){
    var page=this.format(source);
    var title=page.getTitle();
    var author=page.getAuthor();
    var output=page.toHTML();
    //...
}

Wiki.formats.MEDIAWIKI=function(source){
    //....
    return {
        getTitle:function(){},
        getAuthor:function(){},
        toHTML:function(){}
    }
};

第58条:区分数组对象和类数组对象

https://www.cnblogs.com/wengxuesong/p/5647761.html
总结 数组对象 [{},{}] 类数组对象 {0:'1',1:'2', length:5}
注意 类数组对象 不是数组,不能用数组方法,处理方法见51条。

第59条:避免过度的强制转换

https://www.cnblogs.com/wengxuesong/p/5652178.html
总结 强制转换 会包含错误信息容差 导致后期bug不好查

第60条:支持方法链

https://www.cnblogs.com/wengxuesong/p/5661131.html
总结 链式调用,就是函数最后 return this

// 你看 var a = new String('kkk'); a.tostring() 什么意思? 数据上能带方法。。
// 咱们 var b = new Bbb(); 最后返回的是个对象,要是显示数据 b.a 才是数据 b.c() 是方法
// 自己new的 出来都是json 没有基本类型,但是该怎么用,还怎么用。

// 特意写了个demo测试下
function AaaClass () {
  this.k = '5'
  this.j = '6'
}
AaaClass.prototype.fun = function () {
  console.info('fun')
}
var a = new AaaClass()
console.info('a', a)
a.fun()
// 输出结果
// a AaaClass {k: "5", j: "6"}
//    j: "6"
//    k: "5"
//    __proto__:
//      fun: ƒ ()
//      arguments:
//      constructor: ƒ AaaClass()
//      __proto__: Object
// fun

第6章:库和API设计--个人总结

https://www.cnblogs.com/wengxuesong/p/5661157.html

第7章 并发

第61条:不要阻塞I/O事件队列

https://www.cnblogs.com/wengxuesong/p/5674544.html
总结 异步的时候 用 callback
升级方案 this.$api().then()
现在方案 const ac = this.$getAc() ac.use() ac.run()

第62条:在异步序列中使用嵌套或命名的回调函数

https://www.cnblogs.com/wengxuesong/p/5692711.html
总结 这个就是 讲callback回调地狱 现在已用 $getAc 解决

第63条:当心丢弃错误

https://www.cnblogs.com/wengxuesong/p/5694695.html
总结 同步错误 和 异步错误 的抓取
同步错误 try {} catch(error) {}
异步错误 $api('url',params).then(res=> {}, err => {})

第64条:对异步循环使用递归

https://www.cnblogs.com/wengxuesong/p/5710977.html
总结 文章说 $api 调一个列表,就得是递归,现在已用 $getAc 解决该问题。

第65条:不要在计算时阻塞事件队列

https://www.cnblogs.com/wengxuesong/p/5713571.html
总结 当有一个需要长时间的 大量计算的操作,可以开启个新线程去做,它有自己独立的上下文,nodejs worker-farm
纯前台应该不会用到这块

第66条:使用计数器来执行并行操作

https://www.cnblogs.com/wengxuesong/p/5718098.html
总结 $api 并发问题 现已被 axios.all(iterable) 解决

第67条:绝不要同步地调用异步的回调函数

https://www.cnblogs.com/wengxuesong/p/5730269.html
总结 标题说的很明白 这是给小白的提示吧

第68条:使用promise模式清洁异步逻辑

https://www.cnblogs.com/wengxuesong/p/5737709.html
总结 $api 封装的axios, 封装的promise,所以promise属于必会知识。

第7章:并发--个人总结

https://www.cnblogs.com/wengxuesong/p/5742503.html


Effective JavaScript 这本书从第3章开始梳理的,前两章等有时间再看,整体的感受就是 new的前后要非常明白。new前是Class,new后是Obj。他们的基础是function。function的上下文很重要,闭包很重要。最后讲的异步部分,已封装$getAc。

js基础决定 vue react等下一阶段的理解。

这本书真的很不错。

推荐阅读