分类 精读 下的文章

在初级和中级前端面试笔试中,函数的防抖和节流要求候选人有能力去精通或者熟悉里面的机制,不仅仅要知道基本版的防抖和节流,还要知道更完美进阶的节流防抖,比如一些lodash中的实现等等,所以这个系列也是从基础版开始,我会和大家一起补充这方面的知识


配套代码地址在线调试和编辑
参考文章

防抖

【背景故事】
某商业楼每天早上上班高峰期的电梯非常堵,通常生活来讲,一个电梯肯定是要进去很多人的直到人满为止,这对于自身和电梯都是一种节约资源的表现,不可能出现每个人一人一个电梯的情况,所以对于电梯和我们,这是一种防抖函数的表现。

电梯每当停到一个楼层,会等我们进去,人没有塞满或者没有人它才会到达指定楼层。
在web端最容易遇到的就是input搜索,用户输入一个字符,会在指定的时间中监听是否还有字符进入,如果没有,那么就执行对应的函数,这样就解决了用户频繁输入字符而前端发起请求给服务器造成压力的问题
// 防抖函数
function debounce(fn, time = 300) {
  let timer = null;
  return function() {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, arguments);
    }, time);
  };
}

基础版的代码梳理,通过这个函数将fn传递,就能实现防抖的效果,当用户输入的第一个字符,会把timer函数赋予一个延时器,这个延时器执行了对应的fn,this是为了绑定执行的this指向,arguments则是这个函数的参数类数组对象,当用户在延时还未触发的时候再次输入字符,那么clearTimeout把上一个timer清除掉了,因为闭包的作用,timer不会被销毁,这样的一个函数就表示了,只要在延时间隔中(此时timer被赋值了),如果谁再调用此函数,会把上一个timer清空掉,重新延时,依次类推...

节流

【背景故事】
在0几年的时候包括可能现在都有,很多家庭知道水管把阀门开到最小,让水一滴一滴的掉下,是不会走水表的(不计费的),所以在我很小的时候就知道这个事情,虽然现在没有了,但是还是觉得很羞愧,节流函数就是起到的作用就是,把原本频繁的调用变得有秩序有间隔;

以监听scroll滚动为例:

// 节流函数
function throttle(fn, time = 300) {
  let canRun = true;
  return function() {
    if (!canRun) return;
    canRun = false;
    setTimeout(() => {
      fn.apply(this, arguments);
      canRun = true;
    }, time);
  };
}

$(window).on(
  "scroll",
  throttle(() => {
    console.log("触发了scroll");
  }, 300)
);

在这个函数中,canRun这个变量变化了3次,判断了一次如果为false就return掉

  1. 初始化时设置为true
  2. 判断canRun是否为false后设置了canRun为false
  3. 最终事件执行完毕之后设置canRun为true,否则就进入不了下一次的节流

其实这个函数从开始到结束的canRun形成了一次循环,这个循环唯一保证的就是只有事件结束才可以开始下一个事件

总结

节流:保证回调能在指定的时间间隔中依次有规律的执行
防抖:保证回调能在指定的时间间隔中等待是否有再次执行回调,如果没有则执行,如果有则重置继续等待;

我说我过年期间更新了3篇关于uniapp的文章,然后发布莫名消失,你们会信么?

timg.jpg

其实是真的,家里的网络非常不稳定,我连续发了2次文章,结果预览出来的效果仅仅只有一个自然段,剩余全部丢失,所以渐渐的我心态崩掉了,然后转眼就来到了4月,我仍然会每周坚持更新笔记/感悟,分享给大家。

这是一个全新的计划,关于分析vue的源码,那么我需要一个课程带着我学习,因为讲真,它有些门槛,像我第一次看底层源码,不知道从何看起,网上有太多的关于vue的实现,比如methods,computed实现,但是都太单一,所以我在youtube上找到课程,也是慕课网的金牌讲师ustbhuangyi

所以我会把这个课程参考的资料放到这个文章的顶部
他的博客,vue源码资料
正版课程,请大家看完一定要去购买支持正版,虽然我没购买
youtube免费课程,高清,可以翻墙免费看全集


这边配套的源码截止于目前文章发表的日期,版本是2.6.11,大家在学习vue源码的核心内容的时候,只要是2.0版本的最新的内容,基本上不会影响学习vue.js的灵魂。


首先来了解一下看到这个标题,这篇文章就简单的看一下vue的src源码中的目录分工,我们在写一般的vue工程时,也会分很多很多的文件进行模块化开发,这是大势所趋,比如api;common;util;components;assets;static;pages等等,那么在框架开发中,一般像我一样的初学者就很懵逼,所以我们一起来看看;

compiler

这是vue.js编译相关的代码,它包括把vue模板编译成AST抽象语法树,一般情况下,编译会在构建时进行,因为是很耗费时长的,像webpack或者vue-loader这样的插件;

core

这里的目录是vue.js的核心之处,我们所见到的2.0新特性的vdom,监听,状态,初始化,render等等都在这里

platforms

我们一般常用的vue.js是在web端,那么vue.js也支持编译成供weex这样的运行在native上的框架
这个目录就是vue.js针对不同的平台而编译的入口

server

vue.js支持服务端渲染内容,即这里的目录是在node中进行运行的,服务端运行将返回html字符串进行渲染,这里是在服务端的vue.js而不是web端的vue.js

sfc

这里的parser.js会把编写的.vue文件转换成js对象

shared

vue.js用到的公共的方法,它将是所有的目录公共可访问的内容


这两天会根据计划更新其他的内容,这个部分会一直进行下去,不定时更新

今天是周天,本来周五晚上就想写这一篇总结,但是每周分享的题总结还没结束,只能结束之后新写这一个文章。
其实,昨天在逛github的时候给我推荐了几个不错的仓库博客,类似于《一周攻克一个前端难点》,我也会有时间去学习,并且把学习到的干货分享到博客上来。

关于这边《ECMAScript 6 入门》,我没有买这个书而是看阮一峰老师的电子开源书籍,周五看了第一章关于let和const,然后加深了我对于作用域,死区的理解,然后在书中也有非常多的demo例子,这些例子在笔试中还是高频率的出现的,一些前端开发者在面试(入行不深的程序员)选择google出来的答案,背下来....

但是这非常不实用,因为你不了解这种题如何做,在工作中如何避免bug。
在新专栏的开始之前,我想多说几句,和大家分享一下我的学习方法,我也是入行不深,仅仅只有半年多而已,但是写代码即将2年多,
我学习前端的方法也非常简单,项目实战(有利于能接触到更多的场景)-》刷题(渣渣程序员只能从语言角度上刷题,涉及不到算法数据结构的题)-》笔记和博客(每天总结笔记,每一段时间总结博客)要养成良好的习惯,不管多忙,在地铁和公交车上一定要把笔记写完,随手复习,工作中可以翻阅;

正题开始............

es6是前端开发者必须掌握的一个知识点,不管是培训结构还是某些大学的前端课程,传授的es6都相对非常浅,没有去认真的一篇一篇去消化巩固,作者也是这样的,所以现在开始我们将一起一步一步精读阮一峰老师的这本非常棒的书籍,把es6完全消化,能够让你在工作中面试中杀出一条血路。

u=3907368649,2159858588&fm=26&gp=0.jpg

首先我们来快速了解一下let;

Let

我们尝试写这样一段代码:


{
    let a = 1;
    var b = 10;
}

console.log(a); // Uncaught ReferenceError: a is not defined
console.log(b); // 10

这个例子说明了let只会在声明它的代码块中生效,而var则相反

我们一起再看一个例子:

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[1](); // 10

这个循环中的i是一个全局变量,每一次循环全局的变量i都会发生改变,所以尽管它循环100次,数组中console.log中的i永远指向全局变量即100,最后一次循环的赋值;

但是如果把上面的var换成let就不一样,let只在自己的块中存在,所以每次循环都会重新let一次i变量,所以每次输出的console.log中的i指的是当前循环到的i

但是你肯定会说:

既然是重新let一次i,那么每次它应该都是0呀,为什么还能记忆上次的i值呢?这不科学...

那是因为js引擎会记住上一次i的值,为下次循环做运算,这一部分是js帮助开发者做好的。

js的for循环还有一个特别之处:设置循环变量的是一个父作用域,而循环体是一个子作用域

for (let i = 0; i < 10; i++) {
    let i = '123';
    console.log(i) // 123
}

正确运行,并且成功赋值打印新的值;

Let - 关于变量提升

js中的var是存在变量提升的,什么是变量提升呢?

console.log(i); // undefined
var i = "I love you"

在变量未被附值的时候,js解析代码会变成下面这个样子:

var i;
console.log(i);
i = "I love you"

但是let并非这样,它没有所谓的变量提升,只要在声明前使用,则会报错;


console.log(a); // Uncaught ReferenceError: a is not defined
let a = "yes";

所以var会让变量提前声明,而let不会提前声明,如果声明前用到了它,就会报错Uncaught ReferenceError;

Let - 暂时性死区

文章的开头我们降到了死区这个概念,之前呀,我也不太懂这个死区,以为是一个很难的概念,没想到和let有关系,非常简单;

var a = "123";
{
    a = "125";
    let a = "456";
}

ES6明确规定,let和const声明了一个变量,那么会和当前作用域块进行绑定,如果在声明前使用到了变量,那么就会报错;
这在代码中我们称之为 "暂时性死区"

那么如果我们不向变量赋值会不会报错呢? 事实证明typeof也不是一个正确的操作


typeof x; // Uncaught ReferenceError
let x;

如果我们检测一个不存在的变量

typeof notfound; // undefined

所以说这也意味着我们编写代码的时候要在声明之后使用它;

在阮一峰老师的文章中也提到了不常见的死区的案例:

function test(x = y, y = 2){

}

test();

x的默认值是还未声明的y,所以会报错;

var x = x;  // 不报错
let x = x; // 报错

其实上面这个代码,之前就解释过了,var出来的变量即已对变量进行初始化,所以等号右边的x就是undefined
而let出来的变量没有变量提升,然后又在变量x声明之前使用到了x,这显然是错误的,因为之前使用的就是死区;

因此死区的触发有很多,检测,赋值,使用, 等等等;

Let - 不允许同时声明在同作用域中

{
    var a = 1;
    let a = 1;
}

或者

{
    let a = 1;
    let a = 1;
}

又或者

funtion test(arg){
    let arg = "123";
}

这些都会报错,但是在不同作用域中是可以的

funtion test1(arg){
    {
        let arg = "12";
    }
}

it is ok!!

关于块级作用域

面试中经常会被问到作用域, 全局作用域,函数作用域,在es6中新的概念就是块级作用域,之前面试过其他的前端开发者谈到块级作用域解决了什么问题,和let,const的关系,他们也是没有一个很准确的答案;

这边用阮一峰老师的2个demo例子:


var temp = new Date();
function test(){
    console.log(temp);
    if(true){
        var temp = "hello"
    }
}

test(); // undefined

上面这个例子之所以输入未定义,是var变量会造成变量提升,即var temp; 内层的temp替换掉了new Date的那个变量,提升到了函数作用域的顶级;

还有一种场景就是,我们在用循环的时候,使用var,导致循环结束var的变量依然存在,造成变量泄露;

事实上,let和const就是块级作用域,因为我们知道在前面的内容讲到let只能在当前作用域下使用,和其他的作用域不相干,这就是形成了块级作用域;

let a = 1;
if(true){
    let a = "1111"
}
console.log(a); // 1

ES6允许我们块级作用域嵌套

{{{{{
    console.log(1)
}}}}}

即内层的可以读取到外层同名变量,外层不能读取内层的同名变量;

块级作用域和函数声明

ES5规定,块级作用域里面不能写函数声明语句,但是浏览器为了兼容以前的旧代码,没有遵守这个规定;
但是ES6明确规定,块级作用域下可以写函数声明,但是函数声明类似于let,在作用域之外不能使用;

看一下下面这个demo:

function f() { console.log('I am outside!'); }
(function () {
  if (false) {
    // 重复声明一次函数f
    function f() { console.log('I am inside!'); }
  }

  f();
}());

如果是在ES5中写这个函数,那么最终会输出 "I am inside!" ,因为会变量提升提前到函数顶部,所以会输出inside
但是如果在ES6中声明,理论来讲会输出outside,因为es6规定了函数声明相当于let,只在自己作用域生效,不会影响其他作用域,也没有变量提升,但是如果在ES6中就会输出undefined;

为什么呢?说好的规定呢?说好的let呢?

微信截图_20191027154441.png

原来浏览器又有自己实现的一套方法,输出undefined的原因还是被提升了

var f = undefined;

所以我们在作用域中写函数,不要用函数声明,要使用函数表达式,这样最稳妥...

const

ES6新增的常量,我喜欢叫它常量,因为它不可改变;

const a = 1;
a = 12; // TypeError: Assignment to constant variable.

且不能这样写

const a;

不能像var和let一样先定义再赋值;

const和let一样,只能在声明的块级作用域有效
const和let一样,也有死区
const和let一样,也不能重复声明

const的本质其实并不是const的变量值不能被改变,而是指针(地址)是固定的;

const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
---------------《ECMAScript 6 入门》

所以我们可以改变const的数据结构

const props = {};
props.name = "1";
props.age = "12";
props = "12"; // 报错



结束咯,喜欢这篇总结,就收藏起来吧~~
原文阮一峰老师地址: http://es6.ruanyifeng.com/#docs/let