JavaScript语言精粹阅读笔记

avatarplhDigital nomad

This 的4种调用模式

  • 方法调用模式
  • 函数调用模式
  • 构造器调用模式
  • apply调用模式

方法调用模式

var myObject = {
  value: 0,
  increase: function(inc) {          // 此处不能用 => 来声明函数
    console.log(this);               // myObject本身
    this.value += typeof inc==='number'?inc:1;
  }
}
console.log(myObject.value);    // 0
myObject.increase();
console.log(myObject.value);    // 1
myObject.increase(33);
console.log(myObject.value);    // 34

这种通过this调用上下文对象的方法称为公共方法。this和上下文之间的绑定发生在它调用的时候。这种超级延迟的绑定使得this可以高度复用。

函数调用

当一个函数并非一个对象的属性的时候,那么他就被当作一个函数来调用,此时


'use strict';
var myObject = {
  value: 0,
  increment: function (inc){
    this.value+= typeof inc==="number"?inc:1;
  }
}
let add = (a,b) => a+b;
myObject.double = function (){
  var that = this;                           //   此处this指向函数本身
  var helper = function (){
    console.log('myObject.double', thar);    //  此处this在非严格模式☞全局,严格模式undefined
    that.value = add(that.value, that.value)
  }
  helper()
}
myObject.double();
console.log(myObject);

构造器调用模式

js语言是一门基于原型继承的语言,可以从其他对象继承属性,该语言是无类型的。这和当前编程语言的主流风格不符合,但是如果你在函数调用之前加上new的话,那么背地里将会创造一个连接到函数prototype成员的新对象,同时this会绑定到那个新对象上面。同时new前缀也会改变return语句的行为。

'use strict';
var Quo = function (str){
  this.status = str;
}
Quo.prototype.get_status = function (){
  return this.status;
}
var myQuo = new Quo('confused');
console.log(myQuo.get_status());   // confused

一个函数创造的目的就是为了结合new前缀来调用,那么我们称之为构造函数,但是JavaScript语言精粹并不推荐这种构造方法。

Apply调用模式

因为js是一门函数式面向对象编程语言,所以函数拥有方法。 apply方法让我们构建一个参数数组传递给调用函数,他也允许我们选择this的值,apply接受2个参数,第一个是要绑定this的值,第二个就是一个参数数组。

doms = document.querySelectorAll('div');
Array.prototype.slice.apply(doms).map(e=> e.id);  // doms是没有map的方法,经过apply后拥有了map方法。

今天之前一直有个疑问,为什么大佬们这么喜欢用try--catch

var try_it = function (args){
  try {
    console.log(whatever);    //  ReferenceError  sdsd is not defined
  } catch (e) {
    console.log(e.name+" ",e.message);
  }
}
try_it();
console.log(console);

并不会因为报错就不执行之后的代码,这体验还是不错的。

用js写一个fade的褪色动画

为什么要这样呢,之前一直是用css的来写这种收缩动画,直到后来,css越来越大(压缩后200kb),我抑郁了。yes i promise you can use let instead of var in js everywhere.

const fade = (dom) => {
  let level = 1;
  const step = () => {
    dom.style.backgroundColor = `rgb(255,255,${level})`;
    if (level < 255) {
      level += 11;
      setTimeout(step, 100);
    } else {
      level = 1;
      setTimeout(step, 100);
    }
  };
  step();
};
fade(document.body);

the differnet between function and arrow function in javascript

  • 你没看错,箭头函数不能用arguments获得参数
'use strict';
const a = function () {
  console.log(arguments);
};
const b = () => {
  console.log(arguments);
};
a(1, 2, 3);
b(1, 2, 3);

image

  • 箭头函数不能使用new
  • 箭头函数 this 指向函数本身,而普通函数严格模式指向undefined,非严格模式指向global

模块模式

var serial_maker = function(){
  // 返回一个用来产生唯一字符串的对象
  // 位移字符串由:前缀+序列号
  // 这包括一个设置前缀的方法,一个设置序列号的方法
  // 和一个产生位移字符串的gensym的方法
  var prefix = ' ';
  var seq = 0;
  return {
    set_prifex: function(p) {
      prefix = String(p);
    },
    set_seq: function (s){
      seq = s;
    },
    gensym: function(){
      var result = prefix+seq;
      seq += 1;
      return result;
    }
  }
}
var seqer = serial_maker();
seqer.set_prifex("Q");
seqer.set_seq(1111)
var unique = seqer.gensym();
console.log(unique);  // Q1000

联级(jQuery)

返回this而不是undefined,这样就可以实现jquery的链式调用。这和函数式编程又有所不同。另一个返回的是data数组。用es6的class手写一个jQuery,实现链式调用,

class TQuery {
  constructor(tArg) {
    this.version = 0.1;
    this.author = 'pengliheng';
    this.DOM = [...document.querySelectorAll(tArg)]
  }
  css (attr, value) {
    this.DOM.forEach(dom=> dom.style[attr] = value )
    return this;
  }
}
window.$ = tArg => new TQuery(tArg);

实现一个最简单版本的jQuery链式调用,同时改变背景颜色,字体颜色,字体大小。

$('body').css('background','pink').css('color','red').css('font-size','50px');

链式调用的效率,由于jQuery返回的是整个this.所以他是低效的。

//耗时:104ms
for (let i = 0; i < 10000; i++) {
  $('div');
}
//耗时:4.7ms
for (let i = 0; i < 10000; i++) {
  document.querySelector('div');
}

原型与继承 (事实上,用浏览器打印出来就能对原型链继承看得一清二楚!)

const myMammal = {
  name: 'Herb the Mammal',
  getName() {
    return this.name;
  },
  says() {
    return this.saying || '';
  },
};
const myCat = Object.create(myMammal);
myCat.name = 'mimi';
myCat.saying = 'meow';
myCat.purr = (n) => {
  let i;
  let s = '';
  for (i = 0; i < n; i += 1) {
    if (s) {
      s += '-';
    }
    s += 'r';
  }
  return s;
};
myCat.getName = () => `${this.says} ${this.name} ${this.says}`;
console.log(myCat, myMammal);

image

函数化,

到目前为止,我们所看到的继承模式一个弱点就是没有隐私,因为所有属性都会被继承,即可见

识别变量的类型

首先想到的自然是typeof,但他的que缺陷也是显而易见的 image 细想一下,为何不用constructor,,看图,没错,识别了[]和{},但是null和NaN,undefined报错, image 改良一下的最终解决方案式,唯一美中不足的是,不能识别undefined,请再配合isNaN一起使用,用来识别number和NaN的区别 image 顺便一提,callapply的效果是一样的

Object.prototype.toString.call(123)      // [object Number]
Object.prototype.toString.apply(123)     // [object Number]

JavaScript - 精华

  • 函数是顶级对象:函数是有词法作用域的闭包。
  • 基于原型继承的动态对象
    • 对象是无类别的。我们可以通过普通的赋值给任何一个对象新增一个成员的属性,同时一个对象可以通过从原型继承成员属性。
  • 对象字面量和数组字面量:创建一个新对象和数组是非常方便的表示法。

JavaScript - 糟粕

  • ==!=, 为了不出bug,==应该禁止使用
'' == '0'            // false
0 == ''              // true
0 == '0'            // true
  • with, JavaScript提供了一个with语句,本意是快捷访问对象的属性,不过结果不可预料,应该禁止使用。 下面的语句
with (object) {
    a == b;
}

with 在这门语言的出现,本身影响了JavaScript处理器的速度,他阻断了变量名词法作用域的绑定,他的本意是好的。

  • eval 你可能会这样用
eval("myValue = myObject."+a+";")
  • continue 语句 continue 语句跳到语句的顶部,我发现一段代码通过重构后移除了continue语句之后,性能得到了改善。
  • ++,--语句,这个也禁止使用,建议用i+=1替换。
  • 位运算符
&       and 按位与
|       or   按位或
^       xor  按位异或
~       not  按位非
>>      带符号的右位移
>>>     无符号的(用0补足)右位移
<<      左位移  

通常用于执行硬件处理,但JavaScript不处理硬件,所以位运算符很慢

  • funtion语句对比function表达式 function 语句会发生被提升, 而var生命函数则不会。就我个人而言是建议使用var生命一个函数的。
function test(){}
var test = function(){}

一个语句不能以function开头,因为官方表示假定单词以function开头被认为是一个funciton语句。可以用如下做法避免他。

(function (){
  var hidden_val;
  // 这个函数对环境有一些影响,但不会引入新的全局变量
})
  • void 应该避免使用。

全书完。