# 前端爱好者学习周刊:第3期

2019-11-23

html
css
js

# 【Html+Css】

# 1. 溢出文字的scss

给新手前端的✋5段救命🚀css代码(scss mixin) (opens new window)

/**
* 溢出省略号
* @param {Number} 行数
*/
@mixin ellipsis($rowCount: 1) {
  @if $rowCount <=1 {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  } @else {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: $rowCount;
    -webkit-box-orient: vertical;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 【JavaScript】:

# 1. js小技巧

Tips to write better Conditionals in JavaScript (opens new window)

  • 将多个if || 的操作可以做成array.includes的写法;
if (animal === 'dog' || animal === 'cat') {
  console.log(`I have a ${animal}`);
}
//===================
const animals = ['dog', 'cat', 'hamster', 'turtle']; 

if (animals.includes(animal)) {
 console.log(`I have a ${animal}`);
}
1
2
3
4
5
6
7
8
9
  • 一个函数中,能早返回就早返回
  • 第3条中的写的用object的形式代替switch中的Map形式之前没用过:
const fruitColor = new Map()
    .set('red', ['apple', 'strawberry'])
    .set('yellow', ['banana', 'pineapple'])
    .set('purple', ['grape', 'plum']);

function printFruits(color) {
  return fruitColor.get(color) || [];
}
1
2
3
4
5
6
7
8
  • 第5条中提到的检测数组中是否每个都匹配或者存在匹配的使用Array.everyArray.some

想起了match和test----好吧,此二种针对于字符串

# 2.将回调写成Promise

Converting callbacks to promises (opens new window)

const shootPeasPromise = (...args) => {
  return new Promise((resolve, reject) => {
    // This is a not a Node styled callback. 
    // 1. data is the first argument 
    // 2. err is the second argument
    shootPeas(...args, (data, err) => {
      if (err) return reject(err)
      resolve(data)
    })
  })
}
1
2
3
4
5
6
7
8
9
10
11

如果有多个参数的时候,需要以数组形式,不能写成resolve(data, size),正确应该是resolve([data, size])。因为Promise只能返回一个参数。

# 3.理解 Javascript 执行上下文和执行栈

理解 Javascript 执行上下文和执行栈 (opens new window)

执行上下文的类型:

  • 全局执行上下文
  • 函数执行上下文
  • Eval 函数执行上下文

执行栈: LIFO(后进先出)结构

执行上下文是如何被创建的:

  • 1)创建阶段;

    • 确定 this 的值,也被称为 This Binding。
    • LexicalEnvironment(词法环境) 组件被创建。
    • VariableEnvironment(变量环境) 组件被创建。

    This Binding:

    在全局执行上下文中,this 的值指向全局对象,在浏览器中,this 的值指向 window 对象。 在函数执行上下文中,this 的值取决于函数的调用方式。如果它被一个对象引用调用,那么 this 的值被设置为该对象,否则 this 的值被设置为全局对象或 undefined(严格模式下)。例如:

    let person = {  
      name: 'peter',  
      birthYear: 1994,  
      calcAge: function() {  
        console.log(2018 - this.birthYear);  
      }  
    }
    
    person.calcAge();// 24
    // 'this' 指向 'person', 因为 'calcAge' 是被 'person' 对象引用调用的。
    
    let calculateAge = person.calcAge;  
    calculateAge();   //NaN
    // 'this' 指向全局 window 对象,因为没有给出任何对象引用
    
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    词法环境有两种类型:

    • 全局环境
    • 函数环境
    function foo(a, b) {  
      console.log(arguments)//{"0": 2,"1": 3, callee:f foo(a,b)...}
      var c = a + b;  
    }  
    foo(2, 3);
    
    
    1
    2
    3
    4
    5
    6

    环境记录有两种类型

    • 声明性环境记录
    • 对象环境记录

    变量环境指的是var声明的变量。let和const声明的变量在创建阶段是未初始化,而var声明的在创建阶段是undefined,所以var的变量提升以及let和const的提前调用报错可解;

  • 2)执行阶段

完成对所有变量的分配,最后执行代码

# 4.服务端渲染和客户端渲染

服务端渲染和客户端渲染 (opens new window)

客户端请求:

在客户端渲染中, 客户端至少要对服务端发送两次请求

  • (1)用户在浏览器输入请求的地址例如:172.0.0.1:8080 到服务器, 服务器接受到客户端的请求拿到一个没有被数据渲染的空页面

  • (2)客户端拿到服务端的空字符串页面,然后从上往下开始执行里面的代码,当执行到script中有请求或者渲染等代码时,就会对服务器再次发出请求

  • (3)服务端接收到客户端的第二次请求,就把响应的数据发送给客户端,然后客户端再进行渲染

服务端渲染:

  • 1)客户端只发送一次请求,服务端直接返回给客户端一个被渲染好的页面

两者的区别,以及什么场合使用:

  • 1、客户端渲染需要对服务端进行两次请求,响应的开销较大,而服务端渲染只需要客户端对服务端进行一次请求

  • 2、如何查看一个网页是客户端渲染还是服务端渲染:可以通过右键查看源代码的形式

    客户端渲染: 右击查看源代码找不到内容

    服务段渲染:是可以在源代码中找到内容的

  • 3、网站一般都是用客户端渲染和服务端渲染结合的形式

  • 4、正真的网站既不是纯异步,也不是纯服务端渲染,而是两者结合

  • 5、商品的商品列表采用的是服务端渲染,目的是为了SEO搜索引擎优化,而他的商品评论为了用户体验,用户体验更好

  • 6、服务端渲染可以被爬虫抓取到,客户端渲染爬虫抓取不到

# 5.JavaScript深入之执行上下文栈和变量对象

JavaScript深入之执行上下文栈和变量对象 (opens new window)

JS 引擎并不是一行一行地分析和执行程序,而是一段一段地分析执行,会先进行编译阶段然后才是执行阶段。

  • 变量提升

  • 函数提升

  • 函数声明优先级高于变量声明--- 举例如下:

    foo();  // foo2
    var foo = function() {
        console.log('foo1');
    }
    
    foo();  // foo1,foo重新赋值
    
    function foo() {
        console.log('foo2');
    }
    
    foo(); // foo1
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

函数上下文: 在函数上下文中,用活动对象(activation object, AO)来表示变量对象。

活动对象和变量对象的区别在于

  • 1、变量对象(VO)是规范上或者是JS引擎上实现的,并不能在JS环境中直接访问。
  • 2、当进入到一个执行上下文后,这个变量对象才会被激活,所以叫活动对象(AO),这时候活动对象上的各种属性才能被访问。

执行过程两个阶段

  • 进入执行上下文
  • 代码执行

总结如下:

1、全局上下文的变量对象初始化是全局对象

2、函数上下文的变量对象初始化只包括 Arguments 对象

3、在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始的属性值

4、在代码执行阶段,会再次修改变量对象的属性值

# 6.JavaScript深入之内存空间详细图解

JavaScript深入之内存空间详细图解 (opens new window)

变量的存放

基本数据类型(Undefined、Null、Boolean、Number 、String和Symbol)存放在栈中,对象的地址存放在栈中,对象的数值放在堆中。当查询引用类型的变量时, 先从栈中读取内存地址, 然后再通过地址找到堆中的值。

栈快堆慢。

var a = { name: '前端开发' }
var b = a;
a = null;

a // null
b //  { name: '前端开发' }
1
2
3
4
5
6

null是基本类型,a = null之后只是把a存储在栈内存中地址改变成了基本类型null,并不会影响堆内存中的对象,所以b的值不受影响

# 7.MVVM

什么是 mvvm?:

MVVM 是 Model-View-ViewModel 的缩写。mvvm 是一种设计思想。Model 层代表数据模型,也可以在 Model 中定义数据修改和操作的业务逻辑;View 代表 UI 组件,它负责将数据模型转化成 UI 展现出来,ViewModel 是一个同步 View 和 Model 的对象。

在 MVVM 架构下,View 和 Model 之间并没有直接的联系,而是通过 ViewModel 进行交互,Model 和 ViewModel 之间的交互是双向的, 因此 View 数据的变化会同步到 Model 中,而 Model 数据的变化也会立即反应到 View 上。

ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而 View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作 DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。

mvvm 和 mvc 区别?:

mvc 和 mvvm 其实区别并不大。都是一种设计思想。主要就是 mvc 中 Controller 演变成 mvvm 中的 viewModel。mvvm 主要解决了 mvc 中大量的 DOM 操作使页面渲染性能降低,加载速度变慢,影响用户体验。 和当 Model 频繁发生变化,开发者需要主动更新到 View 。

# 8.垃圾回收算法

垃圾回收算法:

  • 引用计数

现代浏览器已不再使用,IE依旧使用

引用计数算法定义“内存不再使用”的标准很简单,就是看一个对象是否有指向它的引用。如果没有其他对象指向它了,说明该对象已经不再需要了。

引用计数致命————循环引用:如果两个对象相互引用,尽管他们已不再使用,但是垃圾回收器不会进行回收,最终可能会导致内存泄露

var div = document.createElement("div");
div.onclick = function() {
    console.log("click");
};
1
2
3
4

上面的就是一个循环引用。

变量div有事件处理函数的引用,同时事件处理函数也有div的引用,因为div变量可在函数内被访问,所以循环引用就出现了。

  • 标记清除(常用)

标记清除算法将“不再使用的对象”定义为“无法到达的对象”。即从根部(在JS中就是全局对象)出发定时扫描内存中的对象,凡是能从根部到达的对象,保留。那些从根部出发无法触及到的对象被标记为不再使用,稍后进行回收。

现在对于主流浏览器来说,只需要切断需要回收的对象与根部的联系。

算法步骤:

  • 1)、垃圾回收器创建了一个“roots”列表。roots 通常是代码中全局变量的引用。JavaScript 中,“window” 对象是一个全局变量,被当作 root 。window 对象总是存在,因此垃圾回收器可以检查它和它的所有子对象是否存在(即不是垃圾);
  • 2)、所有的 roots 被检查和标记为激活(即不是垃圾)。所有的子对象也被递归地检查。从 root 开始的所有对象如果是可达的,它就不被当作垃圾。
  • 3)、所有未被标记的内存会被当做垃圾,收集器现在可以释放内存,归还给操作系统了。

最常见的内存泄露一般都与DOM元素绑定有关:

email.message = document.createElement(“div”);
displayList.appendChild(email.message)
// 稍后从displayList中清除DOM元素
displayList.removeAllChildren();
1
2
3
4

上面代码中,div元素已经从DOM树中清除,但是该div元素还绑定在email对象中,所以如果email对象存在,那么该div元素就会一直保存在内存中

# 9.问答

# 1). const 声明一个只读的常量,那可以修改const的值?

const foo = {};

// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123

// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
1
2
3
4
5
6
7
8

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

# 2).从内存来看 null 和 undefined 本质的区别是什么?

给一个全局变量赋值为null,相当于将这个变量的指针对象以及值清空,如果是给对象的属性 赋值为null,或者局部变量赋值为null,相当于给这个属性分配了一块空的内存,然后值为null, JS会回收全局变量为null的对象。

给一个全局变量赋值为undefined,相当于将这个对象的值清空,但是这个对象依旧存在,如果是给对象的属性赋值 为undefined,说明这个值为空值

声明了一个变量,但未对其初始化时,这个变量的值就是undefined,它是 JavaScript 基本类型 之一。

对于尚未声明过的变量,只能执行一项操作,即使用typeof操作符检测其数据类型,使用其他的操作都会报错。

值 null 特指对象的值未设置,它是 JavaScript 基本类型 之一。

值 null 是一个字面量,它不像undefined 是全局对象的一个属性。null 是表示缺少的标识,指示变量未指向任何对象。

// foo不存在,它从来没有被定义过或者是初始化过:
foo;
"ReferenceError: foo is not defined"

// foo现在已经是知存在的,但是它没有类型或者是值:
var foo = null; 
console.log(foo);	// null
1
2
3
4
5
6
7

# 10.四种常见的JS内存泄漏

JavaScript深入之4类常见内存泄漏及如何避免 (opens new window)

  • 1).意外的全局变量

解决办法:在 JavaScript 文件头部加上 'use strict',使用严格模式避免意外的全局变量,此时上例中的this指向undefined。如果必须使用全局变量存储大量数据时,确保用完以后把它设置为 null 或者重新定义。

  • 2).被遗忘的计时器或回调函数

对于定时器----及时清理定时器;

    var element = document.getElementById('button');
    function onClick(event) {
        element.innerHTML = 'text';
    }
    element.addEventListener('click', onClick);
1
2
3
4
5

现代的浏览器(包括 IE 和 Microsoft Edge)使用了更先进的垃圾回收算法(标记清除),已经可以正确检测和处理循环引用了。即回收节点内存时,不必非要调用 removeEventListener 了

  • 3).脱离 DOM 的引用
var elements = {
    button: document.getElementById('button'),
    image: document.getElementById('image'),
    text: document.getElementById('text')
};
function doStuff() {
    image.src = 'http://some.url/image';
    button.click();
    console.log(text.innerHTML);
    // 更多逻辑
}
function removeButton() {
    // 按钮是 body 的后代元素
    document.body.removeChild(document.getElementById('button'));
    // 此时,仍旧存在一个全局的 #button 的引用
    // elements 字典。button 元素仍旧在内存中,不能被 GC 回收。
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  • 4).闭包

闭包的关键是匿名函数可以访问父级作用域的变量。