整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

前端-干货分享:更牛逼的CSS管理方法-层(CSS Layers)

用 CSS 最困难的部分之一是处理CSS的权重值,它可以决定到底哪条规则会最终被应用,尤其是如果你想在 Bootstrap 这样的框架中覆盖其已有样式,更加显得麻烦。不过随着 CSS 层的引入,这一切都发生了变化。 这个新功能允许您创建自己的自定义 CSS 层,这是有史以来第一次确定所有 CSS 代码权重的层次结构。 在本文中,我将剖析这对您意味着什么,它是如何工作的,以及您今天如何开始使用它。

什么是层(Layers)

创建您自己的自定义图层是 CSS 的新功能,但图层从一开始就存在于 CSS 中。 CSS 中有 3 个不同的层来管理所有样式的工作方式。

浏览器(也称为用户代理)样式 - user agent style
用户样式 - User Styles
作者样式 - Author Styles

浏览器样式是应用于浏览器的默认样式。这就是为什么 Chrome 和 Safari 中的按钮看起来不同的原因。在浏览器层中找到的样式在浏览器之间是不同的,并且给每个浏览器一个独特的外观。

下一层是用户样式,这并不是您真正需要担心的事情。这些通常是用户可以编写并注入浏览器的自定义样式,但浏览器不再真正支持这些样式。用户可能会更改一些浏览器设置,这些设置会向该图层添加样式,但在大多数情况下,可以完全忽略该层。

最后,我们来到作者层。这是您最熟悉的层,因为您编写的每一段 CSS 代码都属于这一层。

这些层分开的原因是因为它可以很容易地覆盖浏览器样式和用户样式中定义的代码,因为层定义了自己的层次结构,完全忽略了权重的影响。

这 3 个 CSS 层是有序的(浏览器样式、用户样式、然后是作者样式),后面层中的每个样式都将覆盖前一层的任何样式。这意味着即使浏览器样式定义了一个超级特定的选择器,例如#button.btn.super-specific,并且您的作者样式定义了一个超级通用的选择器,例如按钮,您的作者样式仍然会覆盖浏览器样式。

这实际上已经是您可能一直在使用而没有意识到的东西。

* {
  box-sizing: border-box;
}

上面的选择器没有权重,因为 * 符号对权重没有贡献。 这意味着例如使用 p 作为选择器的 p 标签的浏览器样式在技术上比 * 选择器更具体,权重更高。 但是,这一切并不重要,因为作者样式位于比浏览器样式层晚的层中,因此您的代码将始终覆盖浏览器样式。

理解这一点至关重要,因为使用这个新的图层 API,您可以在作者图层中创建自己的图层,从而更轻松地处理特定性。

如何创建你自己的层

下面来看个例子:

很明显,这是我们正常理解的CSS, ID设置的颜色权重更高,所以按钮显示为红色。让我们使用@layer给它们加上两个层,看看是什么效果:

按钮变成蓝色。为什么会这样?

我们给两条CSS分别建立了base和utilities层,很明显,后面创建的层的样式覆盖了前面层的样式,尽管前面层的样式有更高的权重。这就是层的默认工作原理。当然层的顺序是可以指定的,

@layer utilities, base;

@layer utilities, base;

您需要做的就是编写@layer 关键字,后跟以逗号分隔的层列表。 这将按从左到右的顺序定义所有层,其中列出的第一层到最后一层的权重是依次增加的。 然后,您可以稍后使用普通的@layer 语法向每个层添加代码,而不必担心定义层的顺序,因为它们都在这一行中定义。 需要注意的是,这行代码必须在定义任何层之前出现,所以我通常将它作为我的 CSS 文件中的第一行。如上图,通过指定层的顺序,我们让base层应用在utilities层之后,所以按钮又显示为红色。

导入层

上面这两种方式都是导入bootstrap框架的CSS,并且把他们放在framework层中,这样你如果想要覆盖它已有的样式,只需要新建一个自己的层,放置在framework层后面就行。像下面这样。

匿名层

匿名层不常用,但它写在后面可以覆盖其他层的样式,像下面可以把按钮设为橙色。

不在层里的样式

不在层里的样式会有更高的权重,下面这个列表会让你看得更清楚覆盖是怎么发生的

层还可以重叠设置,不过很少用。具体的用法可以查阅相关文档。

浏览器支持

自从IE死了以后,所有主流浏览器都已支持这一特性。大家请放心使用。

.内存管理

前置知识

绝大多数的程序语言,他们的内存生命周期基本一致:

  1. 分配所需使用的内存 ——(分配内存)
  2. 使用分配到的内存(读、写) ——(使用内存)
  3. 不需要时将其释放\归还 ——(释放内存)

对于所有的编程语言,第二部分都是明确的。而第一和第三部分在底层语言中是明确的。

但在像JavaScript这些高级语言中,大部分都是隐含的,因为JavaScript具有自动垃圾回收机制(Garbage collected)。

因此在做JavaScript开发时,不需要关心内存的使用问题,所需内存分配和无用内存回收,都完全实现自动管理。

1.概述

像C语言这样的高级语言一般都有底层的内存管理接口,比如 malloc()和free()。另一方面,JavaScript创建变量(对象,字符串等)时分配内存,并且在不再使用它们时“自动”释放。 后一个过程称为垃圾回收。这个“自动”是混乱的根源,并让JavaScript(和其他高级语言)开发者感觉他们可以不关心内存管理。 这是错误的。 ——《MDN JavaScript 内存管理》

MDN中的介绍告诉我们,作为JavaScript开发者,还是需要去了解内存管理,虽然JavaScript已经给我们做好自动管理。

2.JavaScript内存生命周期

2.1 分配内存

在做JavaScript开发时,我们定义变量的时候,JavaScript便为我们完成了内存分配:

var num = 100; // 为数值变量分配内存
var str = 'pingan'; // 为字符串变量分配内存
var obj = {
 name : 'pingan'
}; // 为对象变量及其包含的值分配内存
var arr = [1, null, 'hi']; // 为数组变量及其包含的值分配内存
function fun(num){
 return num + 2;
}; // 为函数(可调用的对象)分配内存
// 函数表达式也能分配一个对象
someElement.addEventListener('click', function(){
 someElement.style.backgroundColor = 'blue';
}, false);

另外,通过调用函数,也会分配内存:

// 类型1. 分配对象内存
var date = new Date(); // 分配一个Date对象
var elem = document.createElement('div'); // 分配一个DOM元素
// 类型2. 分配新变量或者新对象
var str1 = "pingan";
var str2 = str1.substr(0, 3); // str2 是一个新的字符串
var arr1 = ["hi", "pingan"];
var arr2 = ["hi", "leo"];
var arr3 = arr1.concat(arr2); // arr3 是一个新的数组(arr1和arr2连接的结果)

2.2 使用内存

使用内存的过程实际上是对分配的内存进行读取与写入的操作。

通常表现就是使用定义的值。

读取与写入可能是写入一个变量或者一个对象的属性值,甚至传递函数的参数。

var num = 1;
num ++; // 使用已经定义的变量,做递增操作

2.3 释放内存

当我们前面定义好的变量或函数(分配的内存)已经不需要使用的时候,便需要释放掉这些内存。这也是内存管理中最难的任务,因为我们不知道什么时候这些内存不使用。

很好的是,在高级语言解释器中,已经嵌入“垃圾回收器”,用来跟踪内存的分配和使用,以便在内存不使用时自动释放(这并不是百分百跟踪到,只是个近似过程)。

3.垃圾回收机制

就像前面提到的,“垃圾回收器”只能解决一般情况,接下来我们需要了解主要的垃圾回收算法和它们局限性。

3.1 引用

垃圾回收算法主要依赖于引用的概念。

即在内存管理环境中,一个对象如果有权限访问另一个对象,不论显式还是隐式,称为一个对象引用另一个对象。

例如:一个JS对象具有对它原型的引用(隐式引用)和对它属性的引用(显式引用)。 注意:

这里的对象,不仅包含JS对象,也包含函数作用域(或全局词法作用域)。

3.2 引用计数垃圾收集

这个算法,把“对象是否不再需要”定义为:当一个对象没有被其他对象所引用的时候,回收该对象。这是最初级的垃圾收集算法。

var obj = {
 leo : {
 age : 18
 };
};

这里创建2个对象,一个作为leo的属性被引用,另一个被分配给变量obj。

// 省略上面的代码
/*
我们将前面的
 {
 leo : {
 age : 18
 };
 };
称为“这个对象”
*/
var obj2 = obj; // obj2变量是第二个对“这个对象”的引用
obj = 'pingan'; // 将“这个对象”的原始是引用obj换成obj2
var leo2 = obj2.leo; // 引用“这个对象”的leo属性

可以看出,现在的“这个对象”已经有2个引用,一个是obj2,另一个是leo2。

obj2 = 'hi'; 
// 将obj2变成零引用,因此,obj2可以被垃圾回收
// 但是它的属性leo还在被leo2对象引用,所以还不能回收
leo2 = null;
// 将leo变成零引用,这样obj2和leo2都可以被垃圾回收

这个算法有个限制:

无法处理循环引用。即两个对象创建时相互引用形成一个循环。

function fun(){
 var obj1 = {}, obj2 = {};
 obj1.leo = obj2; // obj1引用obj2
 obj2.leo = obj1; // obj2引用obj1
 return 'hi pingan';
}
fun();

可以看出,它们被调用之后,会离开函数作用域,已经没有用了可以被回收,然而引用计数算法考虑到它们之间相互至少引用一次,所以它们不会被回收。

实际案例:

在IE6,7中,使用引用计数方式对DOM对象进行垃圾回收,常常造成对象被循环引用导致内存泄露:

var obj;
window.onload = function(){
 obj = document.getElementById('myId');
 obj.leo = obj;
 obj.data = new Array(100000).join('');
};

可以看出,DOM元素obj中的leo属性引用了自己obj,造成循环引用,若该属性(leo)没有移除或设置为null,垃圾回收器总是且至少有一个引用,并一直占用内存,即使从DOM树删除,如果这个DOM元素含大量数据(如data属性)则会导致占用内存永远无法释放,出现内存泄露。

3.3 标记清除算法

这个算法,将“对象是否不再需要”定义为:对象是否可以获得。

标记清除算法,是假定设置一个根对象(root),在JS中是全局对象。垃圾回收器定时找所有从根开始引用的对象,然后再找这些对象引用的对象...直到找到所有可以获得的对象和搜集所有不能获得的对象。

它比引用计数垃圾收集更好,因为“有零引用的对象”总是不可获得的,但是相反却不一定,参考“循环引用”。

循环引用不再是问题:

function fun(){
 var obj1 = {}, obj2 = {};
 obj1.leo = obj2; // obj1引用obj2
 obj2.leo = obj1; // obj2引用obj1
 return 'hi pingan';
}
fun();

还是这个代码,可以看出,使用标记清除算法来看,函数调用之后,两个对象无法从全局对象获取,因此将被回收。相同的,下面案例,一旦 obj 和其事件处理无法从根获取到,他们将会被垃圾回收器回收。

var obj;
window.onload = function(){
 obj = document.getElementById('myId');
 obj.leo = obj;
 obj.data = new Array(100000).join('');
};

注意: 那些无法从根对象查询到的对象都将被清除。

3.4 个人小结

在日常开发中,应该注意及时切断需要回收对象与根的联系,虽然标记清除算法已经足够强壮,就像下面代码:

var obj,ele=document.getElementById('myId');
obj.div = document.createElement('div');
ele.appendChild(obj.div);
// 删除DOM元素
ele.removeChild(obj.div);

如果我们只是做小型项目开发,JS用的比较少的话,内存管理可以不用太在意,但是如果是大项目(SPA,服务器或桌面应用),那就需要考虑好内存管理问题了。

#

4.内存泄露(Memory Leak)

#

4.1 内存泄露概念

在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。 ——维基百科

其实简单理解:一些不再使用的内存无法被释放。

当内存占用越来越多,不仅影响系统性能,严重的还会导致进程奔溃。

4.2 内存泄露案例

  1. 全局变量

未定义的变量,会被定义到全局,当页面关闭才会销毁,这样就造成内存泄露。如下:

function fun(){
 name = 'pingan';
};
  1. 未销毁的定时器和回调函数
  2. 如果这里举一个定时器的案例,如果定时器没有回收,则不仅整个定时器无法被内存回收,定时器函数的依赖也无法回收:
var data = {};
setInterval(function(){
 var render = document.getElementById('myId');
 if(render){
 render.innderHTML = JSON.stringify(data);
 }
}, 1000);
  1. 闭包
var str = null;
var fun = function(){
 var str2 = str;
 var unused = function(){
 if(str2) console.log('is unused');
 };
 str = {
 my_str = new Array(100000).join('--');
 my_fun = function(){
 console.log('is my_fun');
 };
 };
};
setInterval(fun, 1000);

定时器中每次调用fun,str都会获得一个包含巨大的数组和一个对于新闭包my_fun的对象,并且unused是一个引用了str2的闭包。

整个案例中,闭包之间共享作用域,尽管unused可能一直没有调用,但my_fun可能被调用,就会导致内存无法回收,内存增长导致泄露。

  1. DOM引用 当我们把DOM的引用保存在一个数组或Map中,即使移除了元素,但仍然有引用,导致无法回收内存。例如:
var ele = {
 img : document.getElementById('my_img')
};
function fun(){
 ele.img.src = "http://www.baidu.com/1.png";
};
function foo(){
 document.body.removeChild(document.getElementById('my_img'));
};

即使foo方法将my_img元素移除,但fun仍有引用,无法回收。

4.3 内存泄露识别方法

  1. 浏览器

通过Chrome浏览器查看内存占用:

步骤如下:

  • 打开开发者工具,选择 Timeline 面板
  • 在顶部的Capture字段里面勾选 Memory
  • 点击左上角的录制按钮
  • 在页面上进行各种操作,模拟用户的使用情况
  • 一段时间后,点击对话框的 **stop **按钮,面板上就会显示这段时间的内存占用情况

如果内存占用基本平稳,接近水平,就说明不存在内存泄漏。

反之,就是内存泄漏了。

  1. 命令行

命令行可以使用 Node 提供的process.memoryUsage方法。

console.log(process.memoryUsage());
// { rss: 27709440,
// heapTotal: 5685248,
// heapUsed: 3449392,
// external: 8772 }

process.memoryUsage返回一个对象,包含了 Node 进程的内存占用信息。该对象包含四个字段,单位是字节,含义如下。

  • rss(resident set size):所有内存占用,包括指令区和堆栈。
  • heapTotal:"堆"占用的内存,包括用到的和没用到的。
  • heapUsed:用到的堆的部分。
  • external: V8 引擎内部的 C++ 对象占用的内存。

判断内存泄漏,以heapUsed字段为准。

公众号:前端自习课

all-admin-web

前言

该项目为前后端分离项目的前端部分,后端项目mall地址:传送门。

项目介绍

mall-admin-web是一个电商后台管理系统的前端项目,基于Vue+Element实现。 主要包括商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等功能。

项目演示

项目在线演示地址:http://39.98.190.128/index.html

项目布局

src -- 源码目录
├── api -- axios网络请求定义
├── assets -- 静态图片资源文件
├── components -- 通用组件封装
├── icons -- svg矢量图片文件
├── router -- vue-router路由配置
├── store -- vuex的状态管理
├── styles -- 全局css样式
├── utils -- 工具类
└── views -- 前端页面
 ├── home -- 首页
 ├── layout -- 通用页面加载框架
 ├── login -- 登录页
 ├── oms -- 订单模块页面
 ├── pms -- 商品模块页面
 └── sms -- 营销模块页面

搭建步骤

  • 下载node并安装:https://nodejs.org/dist/v8.9.4/node-v8.9.4-x64.msi;
  • 该项目为前后端分离项目,访问本地访问接口需搭建后台环境,搭建请参考后端项目传送门;
  • 访问在线接口无需搭建后台环境,只需将config/dev.env.js文件中的BASE_API改为http://39.98.190.128:8080即可;
  • 克隆源代码到本地,使用IDEA打开,并完成编译;
  • 在IDEA命令行中运行命令:npm install,下载相关依赖;
  • 在IDEA命令行中运行命令:npm run dev,运行项目;
  • 访问地址:http://localhost:8090 即可打开后台管理系统页面;
  • 如果遇到无法运行该命令,需要配置npm的环境变量,如在path变量中添加:C:\Users\zhenghong\AppData\Roaming\npm。

https://github.com/macrozheng/mall-admin-web