你应该知道的 Nacos 接入和避坑指南

thbcm阅读(204)

本文给大家分享一个强大的组件Nacos,它是微服务环境下必须要用到的组件。用了Nacos后,再也不用担心服务注册和发现以及配置管理混乱的问题了。

背景

Nacos 致力于帮助开发人员发现、配置和管理微服务,Nacos 提供了一组简单易用的特性集,快速实现动态服务发现、服务配置、服务元数据及流量管理。

目前主流的互联网服务都是基于微服务架构的,那服务与服务之间的交互是必不可少的,而且各个服务的上下线都是相互独立的,而且服务的配置信息也是会动态调整的,这就需要我们的服务更加灵活。Nacos 的出现就是帮助我们实现这些繁琐的功能。

详细的 Nacos 介绍和部署可以参考官方网站 Nacos.io。这里只介绍一下在 SpringBoot 项目中如何快速接入以及接入和使用过程中可能会遇到的坑。

接入

加入依赖

  1. 第一步在 pom 配置文件中加入下面的依赖,用于实现服务注册发现和配置中心功能。
<!-- nacos 配置中心 -->
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency>
  <!-- nacos 注册发现 -->
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency>

增加配置

  1. 第二步在 SpringBoot 项目的启动类上增加如下注解 @EnableDiscoveryClient 用于启动服务注册发现功能。
  1. 增加配置文件,对应的配置信息需要修改成适合自己的,为了方便管理,应用的分组名称,命名空间以及相关的配置都需要合理的设置。多个业务使用同一个 nacos 集群的时候,需要根据各个的业务设定各自的命名空间。所有的配置文件都需要在对应的命名空间下设置,避免多个业务混用,另外业务需要根据用到的组件或者配置,设定独立的配置文件,例如数据库的配置,Redis 的配置等都需要单独设定,这样是为了同一个应用其他的其他服务也可以使用,而且再有地址变更的时候可以只修改一个文件就好,不会忘记。
# 应用服务名称
spring.application.name=application-name
# 应用分组名称
spring.cloud.nacos.config.group=GROUP-NAME
# 配置文件的后缀名
spring.cloud.nacos.config.file-extension=properties
# nacos 对应的命名空间,在后台创建好命名空间后会自动生成
spring.cloud.nacos.config.namespace=xxxxxxxxxxxxxxxxxxxxxxxxx
# 对应的配置文件
# MySQL 相关配置
spring.cloud.nacos.config.ext-config[0].data-id=mysql.properties
spring.cloud.nacos.config.ext-config[0].group=GROUP-NAME
# Redis 相关配置
spring.cloud.nacos.config.ext-config[1].data-id=redis.properties
spring.cloud.nacos.config.ext-config[1].group=GROUP-NAME
# 其他配置等
spring.cloud.nacos.config.ext-config[2].data-id=other.properties
spring.cloud.nacos.config.ext-config[2].group=GROUP-NAME
# 配置中心地址,多个逗号分隔
spring.cloud.nacos.config.server-addr=xxx.xx.xx.xx:xxxx
# 服务注册发现地址,多个逗号分隔
spring.cloud.nacos.discovery.server-addr=xxx.xx.xx.xx:xxxx
# 集群名称
spring.cloud.nacos.discovery.cluster-name=CLUSTER-NAME

  1. 代码中可以使用注解 @Value() 来直接读取 Nacos 配置中的属性参数,也可以使用 @ConfigurationProperties(prefix = "spring.datasource") 读取批量参数。
  1. spring.cloud.nacos.config.ext-config[0].refresh=true 该参数表示是否开启自动更新,根据是否需要自动更新觉得是否配置,如果需要自动更新,加上这个配置后还需要在需要自动更新配置的 Bean 上面增加@RefreshScop 注解。然后对应的 Bean 内部的属性就可以实现自动更新了。增加了spring.cloud.nacos.config.ext-config[0].refresh=true 配置后在修改了 Nacos 中的配置过后日志会出现下面信息,会重新加载配置,并且输出变更的 key 信息。

服务调用

当所有的服务都接入 Nacos 过后,我们在 Nacos 的后台就可以看到每个服务的情况,如下图,可以看到服务状态。

然后我们在服务 A 里面如果要调用服务 B 的时候,就可以直接在 FeginClient 中配置服务 B 的名称,不需要填写 URL 了。这样我们就不用考虑服务 B 是否地址和端口会不会变。服务 B 的实例增加还是减少,端口是否变了,对服务 A 来说都不关心,只要有个服务名称就可以了。

避坑

命名空间

Nacos 有一个默认的名为 public 的命名空间,这个命名空间是无法删除的,所有未指定命名空间的配置都会放在该命名空间下;同样的 Nacos 有一个默认的名为 DEFAULT_GROUP 的分组,在没有指定分组名称的时候默认的配置都是在该分组下。

对于我们应用程序来说,由于很多情况下一个 Nacos 集群是多个团队共同使用的,所以为了方便管理,我们需要根据自己的业务设置自己的命名空间,用于存放本业务的配置文件。本命名空间下的配置文件,根据各个的模块决定是否需要重新分组。

要知道在没有清晰的命名空间划分的时候,要想修改一个配置的内容,是很难受的一件事情。线上的配置调整,一个不小心就是事故。如果还是自动更新配置的话,那连后悔的机会都没有。

精细配置

配置文件应该专一,一个配置文件就设置一个内容,比如 MySQL 的数据源单独一个配置,Redis 的数据源单独一个配置,如果多个 Redis 服务,根据功能建议分开配置,因为并不是所有的服务都需要每个 Redis 的链接配置。各自的服务根据需要单独引用对应的配置文件即可。

将所有的配置独立成一个配置文件方便后续修改配置,只要修改一个配置文件就好,不用担心其他还有未修改的地方。

合理的规划配置文件的内容,往往很多时候可以事半功倍,极大的节约时间和减少出错的概率。

自动刷新

前面介绍了如何设置配置自动刷新,不过服务是否需要自动更新配置,这个根据自身的业务去决定。

我这里一般不建议设置自动更新,因为现在都是微服务部署,有时候我们上线一个新功能的时候都是灰度发布,如果配置自动更新,再调整配置过后,全部实例都会生效,这样会有风险。不设置自动更新的话,我们可以单独重启个别实例,观察线上情况,等稳定了再发布所有服务,这样会安全很多。

当然对于没有那么多服务,不需要灰度,影响不大的场景下,配置自动更新会方便很多,再修改配置后不需要重启服务。

(推荐教程:Spring Boot 那些事)

总结

Nacos 作为服务的注册发现和配置的统一管理确实十分出色,除了能快速接入 SpringBoot 项目之外,其他的框架都能快速的接入,更多使用可以参考官网。

文章来源:公众号–Java极客技术

以上就是W3Cschool编程狮关于你应该知道的 Nacos 接入和避坑指南的相关介绍了,希望对大家有所帮助。

无需代码,使用API操作数据库

thbcm阅读(164)

前言

对于编程人员来说,数据库是必须要接触的,但操作起来却一点都不简单,要用到好几种管理工具,好几种连接方式。假如有一个工具能适用各种数据库,让我们节省大量工作,那该多好。历尽千辛万苦,我总算找到了这么个工具,不仅支持多种数据库,更厉害的是,不用为适配写一行代码,来了解下吧。

神器出场

今天的主角是 sandman2

可以基于已存在的数据库,自动生成一个 RESTful API 服务器,而不需要写任何代码,用作者的话说,简单地就像给食物加点盐

更厉害的是,从简单地 SQLite 数据库,到大型的商业数据 PostgreSQL, 都能完美支持,且不用写一行代码

目前支持的数据库:

  • MySQL
  • PostgreSQL
  • Oracle
  • Microsoft SQL Server
  • SQLite
  • Sybase
  • Drizzle
  • Firebird

这让我想起了曾经因为找不到合适的数据库框架手忙脚乱的日子,如果早点知道 sandman2 就好了

之所以叫 sandman2,是因为它的前辈是 sandman,sandman 已经有了很强的数据库支持能力,不过在 SQLAlchemy 0.9 版本中,增加了 automap功能,可以进一步使 sandman 得到简化,于是重写了 sandman,就有了 sandman2,并且 sandman2 的功能远超 sandman

使用 pip 安装 pip install sandman2

安装成功后,就可以得到一个 sandman2ctl 命令行工具,用它来启动一个 RESTful API 服务器

不用写一行代码,直接启动:

sandman2ctl sqlite+pysqlite:///data.db

注意:如果用的 python 版本是 3.8 及以上,且在 Windows 上,执行时可能会遇到,AttributeError: module 'time' has no attribute 'clock' 的错误 这是因为 3.8 以后 time 模块的 clock 属性换成了perf_counter() 方法,所以需要修改下 lib\site-packages\sqlalchemy\util\compat.py 的 331 行,将 time_func = time.clock 换成 time_func = time.perf_counter() 保存即可

启动之后,默认端口是 5000,访问地址是 http://localhost:5000/admin就能看到服务器控制台了

数据库连接

前面已经看到连接 SQLite 数据的方法

sandman2 是基于 SQLAlchemy 的,所以使用连接 Url 来连接数据库

格式为

dialect+driver://username:password@host:port/database

  • dialect 为数据库类型,如 mysql、SQLite 等
  • driver 为数据库驱动模块名,例如 pymysql、psycopg2、mysqldb 等,如果忽略,表示使用默认驱动

以 mysql 数据库为例:

sandman2ctl 'mysql+pymysql://bob:bobpasswd@localhost:3306/testdb'

如果环境中没有安装 pymysql 模块,必须先安装,才能正常启动

其他数据库的连接方式可参考 SQLAlchemy 的 引擎配置 章节, 在这里查看 docs.sqlalchemy.org/en/13/core/engines.html

控制台

需要快速预览数据,对数据进行简单调整的话,控制台很有用

左侧菜单除了 Home 外,其他的都是库表名称

点击相应库表名称,会在右侧显示表内数据,并且可以做增删改操作

点击新增,打开新增页面:

用过 Django 的同学会感觉很熟悉,不过字段并没有类型支持,只能以字符串输入,自行确保数据类型正确,否则保存时会收到错误信息

点击记录前面的笔状图标,会进入编辑页面

点击记录前的删除图标,来删除记录

另外多选数据后,可以通过 With selected 菜单下的 Delete 按钮来批量删除

控制台方便易用,适合一些简单的、数据量少的操作

注意:由于控制台不能登录即可访问,建议将服务器创建在本地或内网环境中

API

以 RESTful 的角度来看,库表相当于资源(resource),一组资源相当于集合(collection)

以下测验,均采用 curl 工具进行,具体用法可参考 阮一峰的 《curl 的用法指南》(www.ruanyifeng.com/blog/2019/09/curl-reference.html)

查询

通过 Http GET 方法,以 JSON 格式将数据返回,例如返回 学生表 student的所有记录:

$ curl http://localhost:5000/student/


{"resources":[{"age":18,"class":"1","id":1,"name":"\u5f20\u4e09","profile":"\u64c5\u957f\u5b66\u4e60"},...

注意:资源要以 / 结尾

通过参数 page 来分页,例如返回 学生表 student 的第一页数据

$ curl http://localhost:5000/student/?page=1
{"resources":[{"age":18,"class":"1"...

通过参数 limit 显示返回行数

如果要获取具体记录,可以用主键值作为节段,例如获取 id 为 3 的学生记录

$ curl http://localhost:5000/student/3
{"age":18,"class":"2","id":3,"name":"\u738b\u4e94","profile":"\u7231\u7f16\u7a0b"}

以字段名做参数,相当于查询条件,例如,查询 name 为 Tom 的学生记录:

$ curl http://localhost:5000/student/?name=Tom{"resources":
[{"age":19,"class":"1","id":7,"name":"Tom","profile":"Handsome"}]}

查询条件可以被组合,例如,查询班级为 1 年龄为 18 的学生:

$ curl http://localhost:5000/student/?class=1&age=19{"resources":
[{"age":19,"class":"1","id":2,"name":"\u674e\u56db","profile":"\u559c\u6b22\u7bee\u7403"},{"age":19,"class":"1","id":7,"name":"Tom","profile":"Handsome"}]}

修改

POST 方法用于新增,新增内容,由请求的数据部分提供,例如增加一个学生信息:

$ curl -X POST -d '{"name": "Lily", "age": 17, "class":1, "profile":"Likely"}' -H "Content-Type:
application/json" http://127.0.0.1:5000/student/{"age":17,"class":"1","id":8,"name":"Lily","profile":"Likely"}

注意:库表主键是自增长的,可以忽略主键字段,否则必须提供

PATCH 方法用于更新,更新内容,由请求的数据部分提供,例如将 id 为 1 的学生班级更改为 3

注意: 更新时主键信息通过 url 的主键值节段提供,而不在数据部分中

$ curl -X PATCH -d '{"class":3}' -H "Content-Type:
application/json" http://127.0.0.1:5000/student/1{"age":18,"class":"3","id":1,"name":"\u5f20\u4e09","profile":"\u64c5\u957f\u5b66\u4e60"}

DELETE 方法由于删除,例如删除 id 为 8 的学生记录:

$ curl -X DELETE -H "Content-Type: application/json" http://127.0.0.1:5000/student/8

其他接口

获取表的字段定义信息,通过 meta 节段获取,例如获取 学生表 student的字段定义:

$ curl http://127.0.0.1:5000/student/meta
{"age":"INTEGER(11)","class":"VARCHAR(255)","id":"INTEGER(11) (required)","name":"VARCHAR(255)","profile":"VARCHAR(500)"}

导出数据,通过查询字段 export 获取,数据格式为 csv,例如导出学生数据,存放到 student.csv 文件中:

$ curl -o student.csv http://127.0.0.1:5000/student/?export
% Total    % Received % Xferd  Average Speed
Time    Time     Time  Current                                 Dload  Upload   Total   Spent    Left  Speed100   202  100   202    0     0   2525      0 --:--:-- --:--:-- --:--:--  2525

还有更多的接口有待你的探索

部署服务

sandman2 的服务器是基于 Flask 的,具体可以去官网参考,在此就不赘述了。

总结

sandman2 之所以简单易用,是因组合了很多应用和技术,SQLAlchemyORM 层,FlaskRESTful 服务器,Bootstrap 做前台框架等

给我们提供便利的同时,展示了技术组合的强大,使得我们对一些细小知识点的学习不会再感到枯燥无味

文章来源:公众号–Python技术 作者:派森酱

以上就是W3Cschool编程狮关于无需代码,使用API操作数据库的相关介绍了,希望对大家有所帮助。

某公司的初级前端岗位面试小结,我差点怀疑自己是不是高级程序员了

thbcm阅读(172)

本文给大家分享一份来自上海某互联网公司的初级前端岗位面试小结,希望对你有所帮助。

面试过程

1.)自我介绍以及平时是怎么学习前端的?

答:正常回答即可。

2.)问:请举例说明一下Vue 和 React 二者的差异?

答:组件:React组件即函数,在函数内部是JS部分,return 的是HTML部分;Vue我使用的是单文件组件,其由HTML模板、JSCSS构成。

PropsReact子组件通过作为函数参数传递进来的 props对象 拿到父组件传递的外部属性 ,Vue 的子组件通过构造选项 prop 拿到父组件传递的外部属性。

stateReact 通过 useState 声明和初始化数据并拿到 statesetState,通过setState更新状态到视图,Vue 通过 data选项声明数据,修改数据直接更新到视图上。

3.)问:讲讲为什么 Vue 修改数据可以直接更新到视图上?

答:Vue 会对在data 选项里声明的属性通过 defineProperty 方法将属性修改为 gettersetter

Vue 通过 gettersetter 类型监听数据的读取与写入,每当数据变动时Vue就会重新渲染组件。

4.)问:现在如果有一个data数据 { a:123 } ,现在这个 a 被异步的更改为 456 ,现在要求当 a 变更为 456 执行一段代码请问怎么实现?

答:可以通过Watch 选项监听数据 a 的变动,然后执行对应的代码。问:这确实可行,如果不用 Watch 呢?

答:。。。

5.)问:你想一想 Vue 的异步API

答:哦,可以用 nextTick() 方法去获取数据,然后判断 a 是否等于 456 再去执行对应代码。

6.)问:Vuex用过吗?聊一聊你的理解。

答:VuexVue项目的全局状态管理工具,它有几个核心概念分别是:

  • store:存储状态的容器;
  • state:状态;
  • mutation:同步改变状态的方法;
  • getter:store的计算属性;
  • action:异步改变状态的方法;
  • 模块:将 store 进行模块化管理。

7.)问:你对 ES6 有了解吗?聊聊 let 和 var 的区别。

答:

  • let 声明的变量在块级作用域内,而var声明的变量在函数作用域内。
  • let 不可以重复声明,而var可以重复声明。
  • let 不存在变量提升现象,而var存在变量提升。

8.)问:有如下代码,请写出控制台结果。

{
   var a = 1;
   let b = 2;
}
console.log(a)
console.log(b)

答:分别输出:1、报错

9.)问:那吧console提上去呢?

console.log(a)
console.log(b)
{
   var a = 1;
   let b = 2;
}

答:undefined、报错

10.)问:ES6 还有其他了解的吗?Promise了解吗?

答:了解的。

11.)问:阐述一下你对Promise的了解

答:这里本人将Promise是什么、三种状态、回调函数里的resolvereject的作用、以及三个实例方法.then .catch .finally 和两个静态方法.all .race进行了阐述

12.)问:try catch 和 Promise 的 .catch 有什么区别?

答:try catch 也是用来捕获错误的,Promise拥有错误冒泡功能,虽然.then返回的是一个新的对象,但是在.then后面.catch仍然可以捕获到最开始的那个Promise对象的错误。(我不确定这样回答是否正确,面试官也没说什么)

13.)问:宏任务,微任务了解吗?

答:了解的(然后我画了张图,蓝色块表示宏任务队列,橙色表示微任务队列,执行顺序是先执行当前所在横行的同步任务,再执行所在横行的微任务,再执行下一横行的宏任务)

宏任务与微任务

14.)问:Promise 返回的是宏任务还是微任务呢?

答:Promise 返回的是微任务

1.)问:Promise 为什么返回微任务。

答:Promise 表示异步操作的返回结果 -> 当浏览器拿到结果肯定想第一时间处理 -> 加入微任务队列比宏任务处理速度快 -> 所以加入微任务队列。

15.)问:当前有一个 父div 和 子div,在 子div 上有一个 button 绑定一个点击事件,当button 被点击请问发生了什么?

答:button被点击 -> 子组件事件触发 -> 父组件事件触发 这是事件的冒泡机制

16.)问:事件除了冒泡还有其他机制吗?

答:还有捕获机制,addEventListener默认是冒泡机制,可以通过将第三个参数改为true 更改为捕获。

17.)问:你在Vue 项目和 React项目分别踩过的坑有哪些?(这个问题最好提前准备,我是现场想了一会再说的)

  • Vue 项目在 TS 开发中由于Vue2.0的模板内JS代码没有类型声明 -> 导致拿到的数据是 any 类型 -> 结论:Vue2.0目前对TS支持不好。
  • React 是函数式编程,当你的state是某个对象并且要更改其某个属性时,setState需要将就对象先用…展开,在更改对应属性值。

18.)问:接着开始问项目相关的问题,你的UI组件项目popover组件的具体怎么实现的?简单的讲一下。

答:(这个回答根据具体项目而异,具体回答内容这里就不赘述了)

19.)问:你项目中遇到的最棘手的问题是什么?

答:(这个问题最好提前准备,答案根据具体项目而异,也不赘述)

经验总结:

1.如果面试问题比较复杂,你可以先说:让我稍微想一想,不用急着回答。

2.有些问题最后提前准备一下,比如说:项目中最难的问题以及解决方案,遇到哪些坑等等。

3.基础知识还是十分重要的,面试官很多问题会基于你提到的概念再进行拓展,你最好对你说的每一个技术名词都有一定了解。

文章来源:公众号–前端人 作者:鬼哥

以上就是W3Cschool编程狮关于初级前端岗位面试小结的相关介绍了,希望对大家有所帮助。

分享14条能缩减SCSS 50%样式代码的实战经验

thbcm阅读(170)

前言

SassCSS3语言的扩展,它能帮你更省事地写出更好的样式表,使你摆脱重复劳动,使工作更有创造性。因为你能更快地拥抱变化,你也将敢于在设计上创新。你写出的样式表能够自如地应对修改颜色或修改HTML标签,并编译出标准的CSS代码用于各种生产环境。Sass语法比较简单,难点在于如何将Sass运用到实际项目中,解决CSS存在的痛点,从而提高我们效率。经过实际项目的摸索,总结了以下14条实践经验进行分享,希望能帮助大家扩宽思维,更好地将Sass运用到实际项目中。在项目中,我们使用支持传统的类CSS语法—— Scss,所以以下项目经验总结分享以Scss为例。

1、变量 $ 使用

我们可以通过变量来复用属性值,比如颜色、边框大小、图片路径等,这样可以做到更改一处,从而进行全局更改,从而实现“换肤”的功能。

实例1:我们的组件库,利用变量配置,进行统一更改组件的颜色、字体大小等(换肤):

$color-primary: #3ecacb;
$color-success: #4fc48d;
$color-warning: #f3d93f;
$color-danger: #f6588e;
$color-info: #27c6fa;

实例2:图片的配置及全局引入

Scss中图片的使用,可能存在以下2个问题:

(1)如果样式文件和使用该样式文件的vue文件不在同一目录会出现图片找不到

(2)如果将图片路径配置变量写在vue文件的style中,但是该写法导致图片和样式分离

我们可以采用将图片路径写成配置文件,然后进行全局引入,这样可以统一更改图片路径(并且该方法只会在使用相应图片时进行加载,不会导致额外性能问题):

$common-path: './primary/assets/img/';
$icon-see: $common-path+'icon-see.png';
$icon-play: $common-path+'icon-play.png';
$icon-comment: $common-path+'icon-comment.png';
$icon-checkbox: $common-path+'icon-checkbox.png';

2、@import 导入Scss文件

(1)Css中的@import规则,它允许在一个css文件中导入其他css文件。然而,后果是只有执行到@import时,浏览器才会去下载其他css文件,这导致页面加载起来特别慢。

(2)Scss中的@import规则,不同的是,scss@import规则在生成css文件时就把相关文件导入进来。这意味着所有相关的样式被归纳到了同一个css文件中,而无需发起额外的下载请求。

实例1:组件库中统一将组件的样式文件importindex.sccs,然后如果项目中有使用组件库的地方只需要在项目的入口处,引入index.scss文件,如下所示在index.scss文件中引入各组件的样式文件:

@import "./base.scss";
@import "./webupload.scss";
@import "./message-hint.scss";

3、局部文件命名的使用

scss局部文件的文件名以下划线开头。这样,scss就不会在编译时单独编译这个文件输出css,而只把这个文件用作导入。在使用scss时,混合器mixins是最适合的使用场景,因为混合器不需要单独编译输出css文件。

实例1:将混合器的名称写成局部文件命名的方式,如下图所示

4、Scss的嵌套功能和父选择器标识符

我们可以使用嵌套功能和父选择器标识符 & 来缩减重复的代码,特别如果你CSS类采用BEM命名规范,样式类命名存在冗长的问题。使用此功能,能解决BEM命名冗长的问题,且样式可读性更高。

实例1:嵌套功能和父选择器标识符 & 解决BEM冗长问题:

.tea-assignhw {
 &__top {
  margin: 0;
}
 &__content {
   padding-left: 45px;
 }
&__gradeselect {
   width: 158px;
 }
}

实例2:嵌套中使用子选择器、兄弟选择器和伪类选择器

(1)子选择器

&__hint {
  margin: 20px;
  font-size: 14px;
  > p:first-child {
     font-weight: bold;
 }
}

(2)兄弟选择器

&__input {
 width: 220px;
 & + span { 
   margin-left: 10px;
 }
}

(3)伪类选择器

&__browse {
  background: url($btn-search) no-repeat;
&:hover {
  background: url($btn-search) -80px 0 no-repeat;
}
&:visited {
  background: url($btn-search) -160px 0 no-repeat;
 }
}

5、@mixin 混合器和 @extend 指令的使用

变量使你能够复用属性值,但如果想要复用一大段规则呢?传统的做法是,如果在样式表

中发现重复,就会把公共的规则抽离出来放到新的CSS类中。

Scss中可以使用混合器@mixin@extend继承指令来解决以上提到的复用一大段规则的场景。但两者的使用场景又有啥区别呢?

(1)@mixin主要的优势就是它能够接受参数。如果想传递参数,你会很自然地选择@mixin而不是@extend,因为@extend不能够接受参数

(2)因为混合器规则都混入到其他类中,所以在输出的样式表中不能完全避免重复。选择器继承的意思就是让一个选择器能够复用另一个选择器的所有样式,但又不重复输出这些样式属性;即使用@extend产生 DRY CSS风格的代码(Don’t repeat yourself)

综上所述,如果你需要传参数,只能使用@mixin混合器,否则用@extend继承来实现更优。

实例1:@mixin混合器的使用

@mixin paneactive($image, $level, $vertical) {
  background: url($image) no-repeat $level $vertical;
  height: 100px;
  width: 30px;
  position: relative;
  top: 50%;
}
&--left-active {
  @include paneactive($btn-flip, 0, 0);
}
&--right-active {
  @include paneactive($btn-flip, 0, -105px);
}

实例2:@extend继承的使用

.common-mod {
  height: 250px;
  width: 50%;
  background-color: #fff;
  text-align: center;
}
&-mod {
  @extend .common-mod;
  float: right;
}
&-mod2 {
  @extend .common-mod;
}

(推荐教程:CSS教程

6、@mixin 混合器默认参数值的使用

@include混合器时不必传入所有的参数,我们可以给参数指定一个默认值,如果所需要传的参数是 默认值,则@include时可以省略该参数;如果所需要传的参数不是默认值,则@include时则传入新的参数。

实例1:@mixin混合器默认参数值的使用

@mixin pane($dir: left) {
  width: 35px;
  display: block;
  float: $dir;
  background-color: #f1f1f1;
}
&__paneleft {
  @include pane;
}
&__paneright {
  @include pane(right);
}

7、#{} 插值的使用

通过 #{} 插值语句可以在选择器或属性名中使用变量。当有两个页面的样式类似时,我们会将类似的样式抽取成页面混合器,但两个不同的页面样式的命名名称根据BEM命名规范不能一样,这时我们可使用插值进行动态命名。

实例1:页面级混合器中的类名利用#{}插值进行动态设置

@mixin home-content($class) {
 .#{$class} {
   position: relative;
   background-color: #fff;
   overflow-x: hidden;
   overflow-y: hidden;
 &--left {
    margin-left: 160px;
 }
 &--noleft {
    margin-left: 0;
 }
 }
}

8、运算的使用

SassScript 支持数字的加减乘除、取整等运算 (+, -, *, /, %)

实例1:input组件根据输入框的高度设置左右内边距,如下所示:

.ps-input {
   display: block;
   &__inner {
    -webkit-appearance: none;
     padding-left: #{$--input-height + 10
   };
     padding-right: #{$--input-height + 10
   };
  }
}

9、相关scss自带函数的应用

scss自带一些函数,例如hslmix函数等。

实例1:button组件的点击后颜色是将几种颜色根据一定的比例混合在一起,生成另一种颜色。如下所示:

&:focus {
  color: mix($--color-white, $--color-primary, $--button-hover-tint-percent);
  border-color: transparent;
  background-color: transparent;}
&:active {
  color: mix($--color-black, $--color-primary, $--button-active-shade-percent);
  border-color: transparent;  background-color: transparent;
}

10、相关scss自带函数的应用

@for指令可以在限制的范围内重复输出样式,每次按变量的值对输出结果进行变动。

实例1:例如项目中需要设置hwicon类底下第2到8个div子节点需设置样式,如下所示:

@for $i from 2 through 8 {
.com-hwicon {
 > div:nth-child(#{$i}) {
   position: relative;
   float: right;
  }
 }
}

11、each遍历、map数据类型、@mixin/@include混合器、#{}插值 结合使用

可通过结合each遍历、map数据类型、@mixin/@include混合器、#{}插值,从而生成不同的选择器类,并且每个选择器类中的背景图片不同,如下所示:

$img-list: (
 (accessimg, $papers-access),
 (folderimg, $papers-folder),
 (bmpimg, $papers-bmp),
 (xlsimg, $papers-excel),
 (xlsximg, $papers-excel),
 (gifimg, $papers-gif),
 (jpgimg, $papers-jpg),
 (unknownimg, $papers-unknown)
);


@each $label, $value in $img-list {
 .com-hwicon__#{$label} {
    @include commonImg($value);
 }
}

12、样式代码检查校验 —— stylelint 插件

CSS不能算是严格意义的编程语言,但是在前端体系中却不能小觑。CSS 是以描述为主的样式表,如果描述得混乱、没有规则,对于其他开发者一定是一个定时炸弹,特别是有强迫症的人群。CSS 看似简单,想要写出漂亮的 CSS 还是相当困难。所以校验 CSS 规则的行动迫在眉睫。stylelint是一个强大的现代 CSS 检测器,可以让开发者在样式表中遵循一致的约定和避免错误。

(1)需要安装gulp、stylelint、gulp-postscss 、 postcss-reporter、stylelint-config-standard,安装命令为:

npm install gulp stylelint gulp-postscss  postcss-reporter 
stylelint-config-standard--save-dev

(2)安装完成后会在项目根目录下创建gulpfile.js文件,文件gulpfile.js配置为:

var reporter = require('postcss-reporter');
var stylelint = require('stylelint');
var stylelintConfig = {
  'extends': 'stylelint-config-standard',
  'rules': {
  'at-rule-no-unknown': [
     true, {
     'ignoreAtRules': [
     'extend',
     'include',
     'mixin',
     'for'
     ]
    }
   ]
  }
};
gulp.task('scss-lint', function() {
   var processors = [
   stylelint(stylelintConfig),
   reporter({
     clearMessages: true,
     throwError: true
   })
   ];
 return gulp.src(
   ['src/style/*.scss']// 需要工具检查的scss文件 
  ).pipe(postcss(processors));});
 gulp.task('default', ['scss-lint']);

(3) stylelint-config-standard 检验规则

stylelint-config-standardstylelint官方推荐的标准校验规则,具体校验规则有哪些内容,可参照官网。

(4)运行命令进行样式检查

13、样式自动修复插件 —— stylefmt 插件

stylefmt 是一个基于 stylelint 的代码修正工具,它可以基于stylelint的代码规范约定配置,对可修正的地方作格式化输出。

(1)gulp.js配置文件如下:

var stylefmt = require('gulp-stylefmt'); // css格式自动调整工具
gulp.task('stylefmt', function() {
  return gulp.src(
  ['src/style/student/index.scss' // 需要工具检查的scss文件
  ]).pipe(stylefmt(stylelintConfig))
    .pipe(gulp.dest('src/style/dest/student'));});
 gulp.task('fix', ['stylefmt']);

(2)运行命令进行样式修复,如下图所示

14、将scss语法编译成css语法——gulp-sass 插件

初写scss代码时,由于对语法不熟悉等,写出来的scss代码所得到的页面效果,并不是我们想要的。这时,我们可以使用gulp-sass插件来监听scss代码,实时生成css代码,从而可以通过查看css代码,来判断所写的scss代码是否正确。

(1)gulp.js配置文件如下:

var gulpsass = require('gulp-sass');
gulp.task('gulpsass', function() { 
  return gulp.src('src/style/components/hwIcon.scss')    
  .pipe(gulpsass().on('error', gulpsass.logError))   
  .pipe(gulp.dest('src/style/dest'));});
  gulp.task('watch', function() {  
  gulp.watch('src/style/components/hwIcon.scss', ['gulpsass']);
});复制代码复制代码

(2)运行命令从而监听scss文件,动态编译scss代码生成css代码文件,如下图所示

(推荐微课:CSS微课

文章来源:github.com/fengshi123/blog/issues/8

以上就是W3Cschool编程狮关于14条能缩减SCSS 50%样式代码的实战经验的相关介绍了,希望对大家有所帮助。

2021年最火的编程语言将会是哪个?

thbcm阅读(152)

2020年已经过去四分之三了,很快就要到2021年了。作为一名程序员,需要关注一下现在流行的编程语言,以及未来会火的语言。回顾编程语言历史,CJava 是最古老的两个,并且在业界仍然存在,但是你会发现它们的热度一直在下降。

作为开发人员,我爱上了 JavaScript,并且已经使用 JavaScript 已有一段时间了。但是,如果我们睁开眼睛看看开发人员世界,情况可能会稍有改变。让我们从 JavaScriptPythonGo 中选择一门在 2021 年使用的语言。在讨论每种语言的优点之前,让我向您展示一些在线调查及其结果。

StackOverflow 调查

StackOverflow 的年度开发人员调查是对全球开人员的最大、最全面的调查。超过 90,000 个开发人员参加了此调查。我们可以看到Python 领先,而 JavaScriptGo分别位于第二和第三位。

下图在 2019 年进行的调查。作为一种快速增长的语言,Go 的受欢迎程度正在疯狂增加。

GitHub-GitHut 排名

但是通过观察每种编程语言的排名,我们可以看到 Go 的流行度正在显着增加,而 JavaScriptPython 的流行度却在下降。

为什么选择 Python?

Python 是一种高级的动态类型编程语言,在 IT 行业中已经流行了很多年。根据许多调查,Python 被认为是过去十年中最受欢迎的语言。

(推荐教程:python教程

Python 的优点

  • 使用 Python,您可以用更少的代码在更短的时间内解决复杂的问题。
  • Python 通过简单而强大的语法使许多复杂的事情变得非常容易。
  • 这是一种高级语言,因此您不必像使用 C++ 那样担心诸如内存管理之类的复杂任务。
  • 它是跨平台的,这意味着您可以在 Windows,Mac 和 Linux 上构建和运行 Python 应用程序。
  • 它有一个庞大的社区。每当您遇到困难时,都会有人帮助您。
  • 它具有大量的库,框架和工具。这意味着无论您想做什么,很可能其他人以前都做过,因为 Python 已经问世 20 多年了。

简而言之,Python 是一种多用途语言,具有简单,干净且对初学者友好的语法。所有这些都意味着 Python 很棒。从技术上讲,您使用 Python 所做的一切,也可以使用其他编程语言。但是 Python 的简单性和优雅性使其比其他编程语言有了更大的发展。这就是为什么它是雇主寻找的第一语言。无论您是程序员还是绝对的初学者,学习 Python 都会在 2021 年为您带来很多就业机会。实际上,一个 Python 开发人员的平均年薪高达 116,000 美元。(国内是这样吗?感觉悬)

为什么选择 JavaScript(Node.js)?

我个人很喜欢 Node.js,因为 Node.js 是我开始进行后端开发的语言。到目前为止,我已经谈论编程语言。但是在本节中,我想进一步谈谈 Node.js,它是最流行的 JavaScript 运行时环境之一。众所周知,Node.jsIT 行业中很受后端开发的欢迎。主要原因之一是 Node.js基于 JavaScript。如果您是 JavaScript 开发人员,则可以轻松地成为全栈开发人员,这是了解 JavaScript 的另一项优势。

(推荐教程:JavaScript教程

JavaScript 和 Node.js 的优点

  • 这是非阻塞的。因此,这意味着 Node.js 确实非常快。(非阻塞文件读写是服务器需要做的事情之一。但这是 Node.js 要做的事情。)
  • 前端和后端使用一种语言编写。
  • Uber,LinkedIn,Netflix 和 Facebook 等主要公司都在使用它。
  • 它是跨平台的。这意味着您可以在 Windows,Mac 和 Linux 上构建和运行 Node.js 应用程序。
  • 它有一个庞大的社区。(Gitter[2] 是我目前所在的社区之一。此外,您可以找到 Slack 频道和 StackOverflow 讨论。)
  • 它不是一个臃肿的框架。这意味着它是一种 Web 服务器技术,可以与 Node.js 所使用的不同框架一起使用,例如 Express,它是流行的 Node.js 框架之一。

再一次,我们可以看到 Node.js 还将成为在后端开发中使用的绝佳 JavaScript 环境。通过观察下图,我们可以看到在 2019–2020 年期间,JavaScript 用于后端和前端开发的比例相似。如果我们看一下 Node.js 在美国的开发人员的平均年薪,我们发现它的年薪在 48,000 美元到 130,000 美元之间。

为什么选择 Go?

Go 是一种开放源代码编程语言,可轻松构建简单,可靠和高效的软件。它最初是在 2009 年发布的,但自那时以来,其流行度一直低于 PythonJavaScript。然而 Go两次被评为年度编程语言。此外,Go 已成为 67.9% 的开发人员最喜欢的编程语言。显然,有一些原因使开发人员开始喜欢 Go

Go 的优点

  • 高效。由于 Go 是一种类似于 C 的语言,因此您无需了解太多新的语法。
  • Go 也是一门跨平台语言。
  • 这是一门简单的语言。为什么?答案是它只有 25 个关键字和较少数量的数据结构(没有类,只有函数和类型)。
  • 这是一门可靠的语言。这是一门快速的语言。默认情况下,它支持并发,具有漂亮的模型并默认情况下处理可用于生产环境的服务器。
  • Go 不会经常更新。如果更新,则也不会特别重大更改,并且 Go 与旧版本 100% 兼容。
  • Go 现在是一门成熟的语言。(Go 具有内置的包管理器测试框架和并发模型。)

基于以上优点,我们可以看到 Go 也是一门很棒的语言。您可能想知道为什么要花这么多时间才能弄清这一点,为什么还没有开始使用 Go。在我这边,我也这么认为。如我们所见,大多数程序员都将 C 语言作为他们的第一门编程语言来学习。如果您以这种方式开始学习和使用 Go,那么对您来说将毫不费力。

如果我们看一下 Go 开发者在美国的平均薪水,则为每年 45,000 美元 至 130,000 美元。

(推荐课程:Go教程

结论

如上所见,Go 在过去几年中做得很好。此外,根据调查,与 JavaScriptPython 相比,我们可以看到 Go 吸引了大量用户。当然,在接下来的几个月中,人气和资源将会增加。即使我开始使用 JavaScript,我还是选择 Go 作为 2021 年将要使用的最火编程语言。

文章来源:Go语言中文网

以上就是W3Cschool编程狮关于2021年最火的编程语言将会是哪个?的相关介绍了,希望对大家有所帮助。

完美解决JavaScript的深浅拷贝

thbcm阅读(165)

前言

“拷贝”一直都是面试的热门考题。看似简单,实则难住不少面试者,回答的马马虎虎,模棱两可。抽出时间好好分析总结一下”拷贝”,让这个难题彻底消失。

正文

从一则故事讲起,昨天因为医院开不出药,我拿上药取小区药店去买药,进门之后我问老板有没有这个药,老板转身进去一个小屋子拿了一盒药,果不其然确实有,药的名字和毫克一摸一样,但是盒子的样子和厂商不一样,我问老板:“这两个药是一种药吗,盒子不一样啊,药的成分是一样的吗?”老板说当然一样啊,这个就和你去买猪肉一样,同样是猪身上的肉,只不过是你去这个超市和去其他超市买场地一样而已。最后为了安全起见,我还是没有买那个药。

“拷贝”分为浅拷贝和深拷贝。它是针对对象来说的,如果不是对象一切免谈。这里的对象可以理解为我拿的那盒药,浅拷贝可以理解为老板拿出来的那盒药,虽然药的名字和毫克一样,然后里面的我们不知道是否真的一样,可能一样可能不一样。深拷贝可以理解为我买到了一摸一样的药,一层一层的药名,毫克,厂商,成分都一样。

总结:

  • 浅拷贝就是针对对象的属性依次进行复制,只复制一层,不会递归到个属性复制,会产生引用问题即内存地址是指的同一地址。简单来说就是拷贝之后和原对象有关
  • 深拷贝就是针对对象的各属性递归复制到新的对象上,内存地址不会指向同一地址。简单来说就是拷贝之后和元对象无关

下面看一个浅拷贝的例子:

let school={'name':"W3Cschool"};
let my = {age:{count:18},name:"W3Cschool编程狮"};
let all = {...school,...my};
my.age.count=100;
console.log(all);
console.log(my);

结果:

{ age: { count: 100 }, name: 'W3Cschool编程狮' }
{ age: { count: 100 }, name: 'W3Cschool编程狮' }

结论是:浅拷贝修改拷贝之后的对象上的属性会把原对象身上的属性同时修改掉。

下面再看一个深拷贝的例子:

const _ = require("loadsh")
let my = {age:{count:18},name:"W3Cschool编程狮"};
let all = _.cloneDeep(my);
all.age.count =100;
console.log(my);
console.log(all);

结果:

{ age: { count: 18 }, name: 'W3Cschool编程狮' }
{ age: { count: 100 }, name: 'W3Cschool编程狮' }

结论是:深拷贝修改拷贝之后的对象上的属性不会把原对象身上的属性同时修改掉。

拷贝的方法

1.数组方法:slice和concat

  • slice
let arr = [1,2,3,4];
let arr2 = arr.slice(0)
arr2[2]=5;


console.log(arr);  //[ 1, 2, 3, 4 ]
console.log(arr2); //[ 1, 2, 5, 4 ]

当数组里是不是对象的时候从结果上看是深拷贝,在看下面例子

let arr = [{1:1,2:2}];
let arr2 = arr.slice(0)
arr2[2]=5;


console.log(arr);  //[ { '1': 1 }, { '2': 5 } ]
console.log(arr2); //[ { '1': 1 }, { '2': 5 } ]

当数组里是对象的时候就变成了浅拷贝

  • concat
let arr = [1,2,3,4];
let arr2 = [].concat(arr);
arr2[2]=5;
console.log(arr); //[ 1, 2, 3, 4 ]  
console.log(arr2); //[ 1, 2, 5, 4 ]

当数组里不是对象的时候从结果上看是深拷贝,在看下面例子

let arr = [{1:1},{2:2}];
let arr2 = arr.cancat(0)
arr2[1][2]=5;


console.log(arr);  //[ { '1': 1 }, { '2': 5 } ]  变成了引用
console.log(arr2); //[ { '1': 1 }, { '2': 5 } ]

当数组里是对象的时候就变成了浅拷贝

总结:只有当数组是一维数组而且不包含对象的时候才是深拷贝

(推荐教程:JavaScript教程

2.Object.assgin()

let a= {a:1,b:2};
let b= Object.assign({},a);
a.a=3;
console.log(a)  //{a: 3, b: 2}
console.log(b)  //{a: 1, b: 2}  
let a= {a:1,b:{c:2}};
let b= Object.assign({},a);
a.b.c=3;
console.log(a)  //{a: 1, b: {c:3}}
console.log(b)  //{a: 1, b: {c:3}}   变成了引用

总结:Object.assgin如果涉及到嵌套多个对象的话就变成了引用 解决方法:使用JSON.stringify()先转化成字符串,再通过JSON.parse()转化成对象

  • 3.JSON.parse(JSON.stringify())
let a= {a:1,b:{c:2}};
let b= JSON.parse(JSON.stringify(a))
a.b.c=3;
console.log(a)  //{a: 1, b: {c:3}}
console.log(b)  //{a: 1, b: {c:2}}   
let school={'name':"W3Cschool编程狮",fn:function(){}};
let my = {age:{count:18},name:"W3Cschool编程狮"};
let all=JSON.parse(JSON.stringify({...school,...my}))
console.log(all);  //{'name':"W3Cschool编程狮",age:{count:18}}; //把fn给丢了

总结: JSON.parse(JSON.stringify()) 这个方法有一定的局限性,会丢失 fn 。

  • 4.手写深拷贝
let deepClone=(obj)=>{
    if(obj==undefined) return obj;  //undefined == null
    if(obj instanceof RegExp) return new RegExp(obj);
    if(obj instanceof Date) return new Date(obj);
    if(typeof obj!=="object") return obj;
    let newObj = new obj.constructor;
    for(let key in obj){
        if(obj.hasOwnProperty(key)){
            newObj[key] = deepClone(obj[key])
        }
    }
    return newObj;
}
let obj1 = {name:{age:"10"}}
let n = deepClone(obj1)
obj1.name.age = "231"
console.log(n);  //{name:{age:"10"}}  
let obj = { name:"W3Cschool编程狮" }
obj.aaa=obj
let n = deepClone(obj1)
console.log(n);  //死循环了  

解决这个问题可以使用WeakMap

let deepClone=(obj,hash=new WeakMap())=>{
    if(obj==undefined) return obj;  //undefined == null
    if(obj instanceof RegExp) return new RegExp(obj);
    if(obj instanceof Date) return new Date(obj);
    if(typeof obj!=="object") return obj;
    if(hash.has(obj)) return hash.get(obj);
    let newObj = new obj.constructor;
    hash.set(obj,newObj);


    for(let key in obj){
        if(obj.hasOwnProperty(key)){
            newObj[key] = deepClone(obj[key],hash)
        }
    }
    return newObj;
}

  • 5.lodash的cloneDeep

<br> 源码地址:https://github.com/lodash/lodash/blob/86a852fe763935bb64c12589df5391fd7d3bb14d/.internal/baseClone.js
<br> ```
  • 6.vue-router源码中的克隆方法
function clone (value) {
  if (Array.isArray(value)) {
    return value.map(clone)
  } else if (value && typeof value === 'object') {
    const res = {}
    for (const key in value) {
      res[key] = clone(value[key])
    }
    return res
  } else {
    return value
  }
}
let arr = [{1:1},{2:2},function(){}];
let arr2 = clone(arr)
arr2[1][2]=5;
console.log(arr)  //[ { '1': 1 }, { '2': 2 }, [Function (anonymous)] ]    深拷贝
console.log(arr2); //[ { '1': 1 }, { '2': 5 }, [Function (anonymous)] ]
function extend (a, b) {
  for (const key in b) {
    a[key] = b[key]
  }
  return a
}
let b={a:1,b:{c:2}};
let a= extend({},b);
a.b.c=5;
console.log(a);  //{ a: 1, b: { c: 5 } }
console.log(b);  //{ a: 1, b: { c: 5 } }   浅拷贝

(推荐微课:JavaScript微课

文章来源:公众号–小丑的小屋 作者:小丑

以上就是W3Cschool编程狮关于 完美解决JavaScript的深浅拷贝 的相关介绍了,希望对大家有所帮助。

Vue新特性:在CSS 中可以使用 JS 变量

thbcm阅读(156)

前言

小编之前在看《Vue 3:2020年中状态更新》的时候,记得文中尤雨溪希望在7月中旬发布RC版(候选版本),然后8月初发布3.0正式版。

不过现在已经八月初了怎么还是没发布呢?这个月初到底几号才算是”初”呢?于是我赶紧去github上看看现在到底有没有什么风吹草动,看着看着突然发现一个非常好玩的特性,这个特性我以前就经常这么想:要是我在data里面定义的变量也能在 CSS 里面用那该多好啊!(大家有没有也这么想过)

幻想

以前做项目的时候经常会这么想:

<template>
  <h1>{{ color }}</h1>
</template>


<script>
export default {
  data () {
    return {
        color: 'red'
    }
  }
}
</script>


<style>
h1 {
  color: this.color;
}
</style>

当然,想想也知道不可能,JSCSS隶属不同上下文,CSS哪来的this呢?

那么怎么才能在CSS中使用JS变量呢?那就只能用JS操作DOM然后把变量塞进style里了,比如用ref获取到DOM元素,然后dom.style.color = this.color

或者在模板里:

<template>
  <h1 :style="{ color }">Vue</h1>
</template>


<script>
export default {
  data () {
    return {
        color: 'red'
    }
  }
}
</script>

不过这种方式还是有缺陷的,比如本来就不推荐把样式写在style属性里,还有就是变量复用会很麻烦,比如一组DOM元素都想用这个变量,那就不得不给这一组起个类名,然后再在mounted里面document.getElementsByClassName(),获取到DOM集合之后还要循环遍历每个元素,为其加上dom.style.color = this.color,浪费了很多的性能。

其实CSS本身有很多缺陷,并不图灵完备,所以才导致了各种预处理器的出现:SassLessStylus等……

它们为CSS提供了很多特性:循环、条件语句、变量、函数等……

其中有个特性非常有用,那就是变量!于是CSS也引入了变量的这个概念,自从有了CSS变量,很多事情真的方便了许多,通过JS操作CSS变量,然后再在需要的地方使用CSS变量,这种方法比之前的高效得多。

什么是CSS变量

JS里(不止JS,所有语言都差不多),变量有如下几个特性:

  • 声明
  • 使用
  • 作用域

声明

为了方便理解,咱们通过用JS的方式来类比:

var color = ‘red’;

在CSS中等同于:

–color: red;

当然这点跟JS不太一样,但是如果你学PHP这类语言或者Sass的话应该就很好理解了,在PHPSass中,声明变量的时候没有一个关键字,而是在变量名的第一位加上一个美元符号$,这就代表声明变量了。

PHP:

$color = 'red';

Sass:

$color: color;

但是$符号被Sass占用了,@符号被less占了,所以CSS只能想出别的符号了,CSS的符号就是两个减号--

使用

光声明一个变量是没有什么太大意义的,只有使用了它,这个变量才算有价值:

JS:

console.log(color)

可以看到var只是个声明变量的关键字,color才是变量名。

PHP:

echo $color;

Scss:

h1 {
    color: $color;
}

但是在PHPSass中,声明变量的时候带着,用的时候也得带着。

这就令许多开发者感到困惑,所以CSS在使用变量的时候用到了一个函数叫var()

CSS:

h1 {
    color: var(--color);
}

虽然和PHPSass一样,调用时要带着前缀(因为那就是变量名的一部分),但是不一样的是需要用一个var()来把变量包裹起来。

作用域

这个很好理解,不仅JS里有作用域,CSS里也有作用域,比如:

JS:

var color = 'red';


function h1 () {
    console.log(color);
}


function div () {
    var color = 'blue';
    console.log(color);
}


h1(); // red
div(); // blue

类似于CSS里的:

body {
    --color: red;
}


h1 {
    color: var(--color); /** 这里获取到的是全局声明的变量,值为red **/
}


div {
    --color: blue;
    color: var(--color); /** 这里获取到的是局部声明的变量,值为blue **/
}

也就是说,变量的作用域就是它所在的选择器的有效范围。

中文CSS变量

有一次我看到了两个脑洞大开的库,才发现CSS变量还可以这么玩:

  • chinese-gradient
  • chinese-layout

从他俩的名字就可以看出,都是用chinese开头的,那么大概率就是用中文做的CSS变量,点进去一看果不其然。

也就是说CSS变量的包容性很强,不像以往编程的时候都必须是英文命名,中文这次居然也可以完美运行,不信咱们来试一下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <!-- 在这里用link标签引入中文布局 -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/chinese-layout">


  <!-- 在这里用link标签引入中文渐变色 -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/chinese-gradient">
  <style>
    /* 清除默认样式 */
    * { padding: 0; margin: 0 }
    ul { list-style: none }


    /* 全屏显示 */
    html, body, ul { height: 100% }


    /* 在父元素上写入九宫格 */
    ul {
      display: grid;
      grid: var(--九宫格);
      gap: 5px
    }


    /* 给子元素上色 */
    li {
      background: var(--极光绿)
    }
  </style>
</head>
<body>
  <ul>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
  </ul>
</body>
</html>

运行结果:

也就是说,CSS变量可以这样定义:

body {
    --蓝绿色: aquamarine;
}

然后调用的时候:

h1 {
    color: var(--蓝绿色);
}

在vue中的变量

那么怎样才能在 vue3<style> 中使用**<script>**里声明的变量呢?

首先我们先创建个支持vue3vite项目:

npm init vite-app vars

然后进入到该文件夹安装依赖:

cd vars

npm i

然后创建一个组件,组件型式长这样:

<template>
  <h1>{{ color }}</h1>
</template>


<script>
export default {
  data () {
    return {
      color: 'red'
    }
  }
}
</script>


<style vars="{ color }">
h1 {
  color: var(--color);
}
</style>

还记得文章一开始写的幻想中的组件是什么样吗:

<style>
h1 {
  color: this.color;
}
</style>

但是就算vue再牛它也不可能给CSS安个this啊,除非再做一个什么预处理器,不过这次利用CSS变量已经可以很接近咱们幻想中的组件啦:

<style vars="{ color }">
h1 {
  color: var(--color);
}
</style>

首先要在<style>标签中写个vars=”{}”,再在大括号里写上你在data中声明过的值。

再来试一下这个变量是不是响应式的,动态改变<script>标签中的this.color值会不会引起视图的变化呢?来试一下:

<template>
  <h1>Vue</h1>
</template>


<script>
export default {
  data () {
    return {
      opacity: 0
    }
  },
  mounted () {
    setInterval(_ => {
      this.opacity >= 1 && (this.opacity = 0)
      this.opacity += 0.2
    }, 300)
  }
}
</script>


<style vars="{ opacity }">
h1 {
  color: rgb(65, 184, 131);
  opacity: var(--opacity);
}
</style>

运行结果:

可以看到每 300 毫秒我们就改变一下this.opacity的值,它会映射到CSS变量上去,this.opacity变了,--opacity的值就会随之变化,视图也会随着数据的更新而相应的更新,这个特性简直太棒了!

多个变量之间使用逗号进行分隔:

<template>
  <h1>Vue</h1>
</template>


<script>
export default {
  data () {
    return {
      border: '1px solid black',
      color: 'red'
    }
  }
}
</script>


<style vars="{ border, color }" scoped>
h1 {
  color: var(--color);
  border: var(--border);
}
</style>

脑洞大开

既然chinese-gradientchinese-layout这两个CSS库验证了CSS中文变量的可行性,而且我记得对象的属性也是可以写中文的,那么咱们就来试一下在vue中能不能用这种黑魔法来写中文:

<template>
  <h1>Vue</h1>
</template>


<script>
export default {
  data () {
    return {
      '透明度': 0
    }
  },
  mounted () {
    setInterval(_ => {
      this['透明度'] >= 1 && (this['透明度'] = 0)
      this['透明度'] += 0.2
    }, 300)
  }
}
</script>


<style vars="{ 透明度 }">
h1 {
  color: rgb(65, 184, 131);
  opacity: var(--透明度);
}
</style>

运行结果:

居!然!管!用!了!

以后大家不会命名的话也别用汉语拼音了,直接写中文吧哈哈!后续维护的时候一看变量名就能一目了然(不过还是推荐用英文)。

原理

猜也能猜到,大概率是用到了类似于dom.style.setProperty(‘–opacity’, this.opacity)之类的方法,按下 f12 打开控制台一看,果不其然,它控制的是组件元素的style属性:

不过我们刚才在<style>标签中只用到了varscoped其实也很常用,那么如果他们两个碰到一起去会编译成什么样呢?

<style vars="{ 透明度 }" scoped>
h1 {
  color: var(--透明度);
}
</style>

运行结果:

可以看到Vue把CSS变量也编译了一个和data-v-后面的那串随机字符一样的:

那么问题来了,假如我要是在全局样式里定义了一个–color属性,我在带有scoped属性的组件里想用这个全局的CSS变量,可是一旦在scoped中使用CSS变量就会被编译成:–62a9ebed-color,可是全局定义的不是–62a9ebed-color而是–color,这样就会出现找不到全局属性的局面,这个问题要怎么解决呢?其实也很简单,只需要在的后面加上一个global:就可以了:

<style vars="{ color }" scoped>
h1 {
  color: var(--global:color);
}
</style>

这样编译出来的CSS就会变成:var(–color)啦!

结语

怎么样是不是很好玩?Vue这次更新诚意满满,不过大家都把关注点放在了Composition-API上了,没有注意到这些不起眼的边边角角,但就是这些边边角角却可以极大的提高我们的开发体验。

对了,CSS变量也是有兼容性的:

从 caniuse (www.caniuse.com/#search=CSS Variables)网站上可以看到,它是不兼容IE的,使用的时候记得确认一下自己项目需要兼容的范围。

原文链接:segmentfault.com/a/1190000023479851

作者:手撕红黑树

以上就是W3Cschool编程狮关于Vue新特性:在CSS 中可以使用 JS 变量的相关介绍了,希望对大家有所帮助。

【重磅更新】飞冰(ICE)现已支持无线开发

thbcm阅读(147)

前言

从我们正式发布飞冰(ICE)项目截止到目前,在过去的这段时间里,仅仅淘宝内部已经有约 2000+ 项目使用它进行开发;除了内部之外同时也持续得到了业内广大的关注,并被社区众多个人与公司在其中后台项目中采用,飞冰致力于提供简单而友好的前端研发体系。

然而在实际中除了中后台业务场景外,也被部分用户经常问到能否使用飞冰开发无线应用,何时能支持无线开发等问题,这意味着之前的产品定位和架构设计已不能满足业务场景的需要。

经过持续的高速迭代,ice.js 终于迎来了重大的升级(完全向下兼容),正式支持无线 Web App 和小程序的开发,目前支持的小程序平台包括阿里小程序和微信小程序。这意味着如果你想开发多端投放的小程序、Web App、中后台管理系统,那么你只需使用 ice.js 就够了,这在一定程度上能极大的提升开发效率。更重要的是,无需学习各个平台的小程序语法,只要你会使用 React 即可开发。

多端一致的开发体验

毫无疑问,小程序原生语法的开发体验是为人所诟病的,从微信开始到后来的众多追随者如支付宝、百度、头条等,都选择了与微信类似的架构设计,但也存在着一定的差异性,导致开发人员需要适配的小程序平台越来越多,而每个端开发一套代码又不现实,进一步导致研发成本上升,代码维护困难。

社区基于小程序的上层开发框架也层出不穷,这类框架主要的区别和重心在于 DSL多端适配。核心解决的是能复用现有 Web 的生态,以及能提供不同平台一致性的开发体验。如能使用类 React 或者类 Vue 去开发小程序,能复用 NPM 上数以万计的工具包等。

这是很重要但也很基础的能力,但在飞冰体系中却有所不同,除了支持阿里、支付宝等不同端开发的一致性之外,我们致力于为 ICE 开发者提供更简单更平滑的开发小程序的解决方案, 这意味着如果你使用 ice.js 开发过中后台应用,那么几乎可以无任何成本的上手小程序开发。同时将中后台领域沉淀出的标准化的 React 应用开发模式和最佳实践,完全应用到了小程序开发上,包括但不限于状态管理、数据请求、生命周期、样式、Hooks 等能力,

提供从中后台到小程序开发的体验一致性是至关重要的,这其中包括:

  • 开发心智:提供完整的 React 支持,无需额外的学习成本;
  • 开发体验:提供基于 VS Code 的辅助插件,默认支持 TypeScript;
  • 多端适配:一次编码多端投放,已支持阿里小程序,支付宝小程序,Web;
  • 社区生态:与现有社区生态完全兼容。

不受限制地使用 React 所有特性

ice.js 的小程序机制基于 Rax 小程序的运行时方案,采用模拟 DOM/BOM API 的方式适配,因此可做到 DSL 无关。目前这套方案已经在阿里内部落地 100+ 小程序项目,经过了大量线上项目的验证,值得信赖。让你可以不受限制地使用 React 所有特性。

  • 几乎没有任何语法约束
  • 支持使用 Hooks
  • 支持操作 DOM (不推荐)
  • 丰富的 React 社区生态

逻辑层做的事情其实比较复杂。首先要做的是,去处理节点间的关系,去模拟appendChild/ removeChild/updateNode 等各个行为来操作 VDOM 树。其次是去模拟事件,在逻辑层每一个节点类会继承自 EventTarget 基类,这个和 W3C 是一样的,然后通过 nodeId 作为标识去收集需要监听的事件,当视图层通过 action 触发了某个节点的事件之后,再通过原生小程序事件中的 event.currentTarget.dataset.nodeId获取到目标节点的 id,最终触发目标回调。

完备的应用开发实践

ice.js 小程序在设计上继承了Web应用开发的最佳实践,如果你使用过ice.js 开发过应用,那么使用它开发小程序几乎是零成本的,遵循同一套开发规范和最佳实践。

应用入口 ** 通过默认生成的代码运行应用而无需任何配置,只需要调用 runApp 即可启动小程序。

import { runApp } from 'ice';


runApp();

生命周期

生命周期指的是应用自身的一些函数,这些函数在特殊的时间点或一些特殊的框架事件时被自动触发。框架提供了完整的应用生命周期能力。

import { runApp } from 'ice';


const appConfig = {
  app: {
    // 应用启动的时候触发
    onLaunch() {},


    // 应用唤起时触发
    // 应用从后台切到前台的时候触发
    onShow() {},


    // 应用从前台切到后台的时候触发
    onHide() {},


    // 监听全局错误
    onError(error) {}
  }
};


runApp(appConfig);

全局配置

用于对应用进行全局的静态配置,如设置路由、窗口表现等。

  • routes 用于指定应用的页面,每一项代表对应页面的路径及文件信息。
{
  "routes": [
    {
      "path": "/",
      "source": "pages/Home/index"
    }
  ]
}

  • window 用于配置应用的窗口表现,同时也支持针对每个页面设置窗口表现。目前已经支持的参数的有:
{
  "window": {
    "titleBarColor": "",
    "pullRefresh": true,
    "title": "Home"
  }
}

样式方案

默认推荐使用 CSS Modules 方案,这能很好的解决样式开发中的全局污染和命名混乱的两个痛点问题。但同时也支持 SassLess 等编写样式。

数据请求

大多数情况下都需要通过 HTTP 协议与后端服务器通讯,开发小程序时我们提供了 universal-request 用于网络请求,该模块已支持多端发送请求。

Hooks 方案

开发小程序时除了使用基础的 Hooks 之外,我们还提供了一套完备的自定义 Hooks 的工具库 ahooks,覆盖绝大部分的 Hooks 场景,助力您的应用开发。

更多其他细节如状态管理,工程配置,事件等能力详见官网文档。

三分钟上手

初始化项目

基于 npm init ice 命令选择初始化模板进行下载:

$ npm init ice <projectName>


# 或者使用 yarn
$ yarn create ice <projectName>

选择模板

可选择小程序 TypeScript 或者 JavaScript 模板:

? Please select a template (Use arrow keys)
 Lightweight JavaScript template with Mini Program
  Lightweight TypeScript template with Mini Program

启动项目

使用小程序开发者工具打开项目,执行以下命令即可看到如下界面:

$ cd <projectName>
$ npm install
$ npm start

  • 支付宝小程序效果图:

  • 微信小程序效果图:

最后

随着业务场景的不断复杂和需要,飞冰(ICE)从专注中后台领域升级为面向多终端的研发框架,但初心未变我们始终致力于提供简单而友好的前端研发体系,提升开发效率和幸福感。

文章来源:公众号–前端人

以上就是W3Cschool编程狮关于 【重磅更新】飞冰(ICE)现已支持无线开发 的相关介绍了,希望对大家有所帮助。

当你使用print时,Python是怎么运行的

thbcm阅读(155)

学编程这么久了,大家不知道有没有想过一个问题,当我们执行Python时,它是怎么实现的呢?

众所周知,Python 是一门解释型的语言

——所谓“解释型”,当然是区别于以 C语言 为代表的编译型语言。编译型语言需要将整个程序文件全部转换为可以直接由机器执行的二进制文件;而解释型语言则是由相应的解释器一行一行“解释”并执行代码描述的行为。

正是因此,对于新接触的人来说,Python这样的解释性语言很多时候需要执行到相应的语句,才会发现一些显然的错误。

话说回来,Python的解释器是怎么样来“解释”Python代码的呢?

实际上,类似于Java的执行机制,Python也拥有自己的虚拟机。而这个虚拟机实际上执行的也是一种“字节码”。

Python程序的执行中依然存在一个“编译”的过程:将Python代码编译为字节码。

并且,Python也提供了一个名为dis模块,用于查看、分析Python的字节码。

1. dis模块

举例来说,dis模块中有一个同名函数dis,可以用于将当前命名空间中的对象反汇编为字节码。

import dis


def add(add_1, add_2):
    sum_value = add_1 + add_2


dis.dis(add)

执行结果为:

  4           0 LOAD_FAST                0 (add_1)
              2 LOAD_FAST                1 (add_2)
              4 BINARY_ADD
              6 STORE_FAST               2 (sum_value)
              8 LOAD_CONST               0 (None)
             10 RETURN_VALUE

其中,开头的数字“4”表示字节码的内容对应于脚本中第 4 行的内容。

随后的一列数字则表示对应指令所在的地址。纵向观察可以发现一个规律:下一条指令的地址总比上一条指令的地址大 2 。这是巧合吗?

显然不是的。官方文档《dis — Python 字节码反汇编器》中记录的更改显示,从Python 3.6版本开始,”每条指令使用2个字节“。所以每条指令的地址会在上一条指令地址的基础上加2。

再往后,是一列表示指令含义的单词组合,实际上就是人类可读的对应指令名称。顾名思义,LOAD_FAST就是加载某个内容/对象到某处,”FAST“很可能意味着这是一个便捷快速的命令实现。

最右边,则是对应于当前命令的操作数,即操作对象。数字同样是一个类似于地址的表示,括号中的字符串则表示相应对象在Python代码中的具体名称。

这样我们就可以大概地阅读生成的字节码了:

首先Python将函数add的第一个参数add_1加载到某处,紧跟着将第二个参数add_2加载到第一个参数之后。然后调用了一个名为BINARY_ADD的指令,即对之前加载的两个参数做加法。再然后则是将加法所得的和sum_value存储在了另一个位置。最后,加载了一个常量None并返回。

其实读完上面这个执行过程,我们很容易想到一种常用的数据结构——栈。

像下面这样:

当然这并不是本文的重点——真要探讨Python的实现机制,还得另外写几篇长文才能说得一二。

使用dis.dis函数除了可以查看当前脚本中各个对象对应的字节码,还可以直接传入一段代码对应的字符串进行反汇编:

# test_dis.py
import dis




s = """
def add(add_1, add_2):
    sum_value = add_1 + add_2


print("Hello World!")


import sys
"""


dis.dis(s)

汇编结果:

  2           0 LOAD_CONST               0 (<code object add at 0x0000019FF66DFDB0, file "<dis>", line 2>)
              2 LOAD_CONST               1 ('add')
              4 MAKE_FUNCTION            0
              6 STORE_NAME               0 (add)


  5           8 LOAD_NAME                1 (print)
             10 LOAD_CONST               2 ('Hello World!')
             12 CALL_FUNCTION            1
             14 POP_TOP


  7          16 LOAD_CONST               3 (0)
             18 LOAD_CONST               4 (None)
             20 IMPORT_NAME              2 (sys)
             22 STORE_NAME               2 (sys)
             24 LOAD_CONST               4 (None)
             26 RETURN_VALUE

2. compile函数

除了在程序中直接给出要反汇编的程序形成的字符串,我们还可以通过使用内置函数compile来形成相应脚本的编译对象,再使用dis.dis查看其字节码内容。

# test_compile.py
import dis


with open("test_dis.py", "r", encoding="utf-8") as f:
    s = f.read()


compile_obj = compile(s, "test_dis.py","exec")


dis.dis(compile_obj)

字节码输出结果:

  1           0 LOAD_CONST               0 (0)
              2 LOAD_CONST               1 (None)
              4 IMPORT_NAME              0 (dis)
              6 STORE_NAME               0 (dis)


 11           8 LOAD_CONST               2 ('\ndef add(add_1, add_2):\n    sum_value = add_1 + add_2\n\nprint("Hello World!")\n\nimport sys\n')
             10 STORE_NAME               1 (s)


 13          12 LOAD_NAME                0 (dis)
             14 LOAD_METHOD              0 (dis)
             16 LOAD_NAME                1 (s)
             18 CALL_METHOD              1
             20 POP_TOP
             22 LOAD_CONST               1 (None)
             24 RETURN_VALUE

总结

dis模块为我们提供了一个观察Python内部机制的手段,恰当地使用dis模块,并结合其他方法,可以快速有效弄懂一些Python令人迷惑的地方。

(推荐微课:python3基础微课

希望大家善于利用这样一些有用的工具。

文章来源:公众号–Python技术 作者:轩辕御龙

以上就是W3Cschool编程狮关于 当你使用print时,Python是怎么运行的 的相关介绍了,希望对大家有所帮助。

网页链接用了target=”_blank”,结果出乎意料

thbcm阅读(149)

本文给大家分享一个关于 Web 的知识点。假如你做过一段时间Web开发,那你可能已经知道了。但是对于新手还是可以了解一下的。

我们知道,网页里的a标签默认在当前窗口跳转链接地址,如果需要在新窗口打开,需要给 a 标签添加一个target="_blank"属性。

<a href="https://www.w3cschool.cn/" target="_blank">W3Cschool编程狮</a>

顺便提下一个有意思的现象,很早之前我就发现,国外网站倾向于在当前页跳转,而国内网站喜欢打开新窗口。不信你们可以去验证下。我不知道这是交互设计上的文化差异,还是技术上的开发习惯。

当然,这两种方式各有优缺点。当前页跳转显得操作比较有连贯性,不会贸然打断用户的注意力,也会减少浏览器的窗口(tab 页)数量。但是对于需要反复回到初始页面的场景来说,就很麻烦了。比如搜索结果页面,通常需要查看对比几个目标地址,保留在多个窗口还是比较方便。

今天要说的不只是用户体验上的差别,而是涉及安全性能

安全隐患

如果只是加上target="_blank",打开新窗口后,新页面能通过window.opener获取到来源页面的window对象,即使跨域也一样。虽然跨域的页面对于这个对象的属性访问有所限制,但还是有漏网之鱼。

这是某网页打开新窗口的页面控制台输出结果。可以看到window.opener的一些属性,某些属性的访问被拦截,是因为跨域安全策略的限制。

即便如此,还是给一些操作留下可乘之机。比如修改window.opener.location的值,指向另外一个地址。你想想看,刚刚还是在某个网站浏览,随后打开了新窗口,结果这个新窗口神不知鬼不觉地把原来的网页地址改了。这个可以用来做什么?钓鱼啊!等你回到那个钓鱼页面,已经伪装成登录页,你可能就稀里糊涂把账号密码输进去了。

还有一种玩法,如果你处于登录状态,有些操作可能只是发送一个GET请求就完事了。通过修改地址,就执行了非你本意的操作,其实就是 CSRF 攻击。

性能问题

除了安全隐患外,还有可能造成性能问题。通过target="_blank"打开的新窗口,跟原来的页面窗口共用一个进程。如果这个新页面执行了一大堆性能不好的 JavaScript 代码,占用了大量系统资源,那你原来的页面也会受到池鱼之殃。

解决方案

尽量不使用target="_blank",如果一定要用,需要加上rel="noopener"或者rel="noreferrer"。这样新窗口的window.openner就是null了,而且会让新窗口运行在独立的进程里,不会拖累原来页面的进程。不过,有些浏览器对性能做了优化,即使不加这个属性,新窗口也会在独立进程打开。不过为了安全考虑,还是加上吧。

我找了个博客网站试了一下,点击里面的外链打开新页面,window.openner都null。查看页面元素发现,a标签都加上了 rel=”noreferrer”。博客是用 Hexo 生成的,看来这种设置已经成了基本常识了。

另外,对于通过window.open的方式打开的新页面,可以这样做:

var yourWindow = window.open();
yourWindow.opener = null;
yourWindow.location = "http://someurl.here";
yourWindow.target = "_blank";

希望这个小技巧对你有用。

以上就是W3Cschool编程狮关于 网页链接用了target=”_blank”,结果出乎意料 的相关介绍了,希望对大家有所帮助。

联系我们