整合营销服务商

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

免费咨询热线:

听说过CSS in JS,那你听说过JS in CSS吗


SS in JS

CSS in JS是一种解决css问题想法的集合,而不是一个指定的库。从CSS in JS的字面意思可以看出,它是将css样式写在JavaScript文件中,而不需要独立出.css.less之类的文件。将css放在js中使我们更方便的使用js的变量模块化tree-shaking。还解决了css中的一些问题,譬如:更方便解决基于状态的样式更容易追溯依赖关系生成唯一的选择器来锁定作用域。尽管CSS in JS不是一个很新的技术,但国内的普及程度并不高。由于Vue和Angular都有属于他们自己的一套定义样式的方案,React本身也没有管用户怎样定义组件的样式[1],所以CSS in JS在React社区的热度比较高。

目前为止实现CSS in JS的第三方库有很多:(http://michelebertoli.github.io/css-in-js/)。像JSS[2]styled-components[3]等。在这里我们就不展开赘述了(相关链接已放在下方),这篇文章的重点是JS in CSS

JS in CSS又是什么

在上面我们提到CSS in JS就是把CSS写在JavaScript中,那么JS in CSS我们可以推断出就是可以在CSS中使用JavaScript脚本,如下所示。可以在CSS中编写Paint API的功能。还可以访问:ctx,geom。甚至我们还可以编写自己的css自定义属性等。这些功能的实现都基于CSS Houdini[4]

.el {
  --color: cyan;
  --multiplier: 0.24;
  --pad: 30;
  --slant: 20;
  --background-canvas: (ctx, geom) => {
    let multiplier = var(--multiplier);
    let c = `var(--color)`;
    let pad = var(--pad);
    let slant = var(--slant);

    ctx.moveTo(0, 0);
    ctx.lineTo(pad + (geom.width - slant - pad) * multiplier, 0);
    ctx.lineTo(pad + (geom.width - slant - pad) * multiplier + slant, geom.height);
    ctx.lineTo(0, geom.height);
    ctx.fillStyle = c;
    ctx.fill();
  };
  background: paint(background-canvas);
  transition: --multiplier .4s;
}
.el:hover {
  --multiplier: 1;
}

Houdini 解决了什么问题

CSS 与 JS的标准制定流程对比

在如今的Web开发中,JavaScript几乎占据了项目代码的大部分。我们可以在项目开发中使用ES 2020、ES2021、甚至提案中的新特性(如:Decorator[5]),即使浏览器尚未支持,也可以编写Polyfill或使用Babel之类的工具进行转译,让我们可以将最新的特性应用到生产环境中(如下图所示)。

JavaScript标准制定流程.png

而CSS就不同了,除了制定CSS标准规范所需的时间外,各家浏览器的版本、实战进度差异更是旷日持久(如下图所示),最多利用PostCSS、Sass等工具來帮我们转译出浏览器能接受的CSS。开发者们能操作的就是通过JS去控制DOMCSSOM来影响页面的变化,但是对于接下來的LayoutPaintComposite就几乎没有控制权了。为了解决上述问题,为了让CSS的魔力不在受到浏览器的限制,Houdini就此诞生。

CSS 标准制定流程.png

CSS Polyfill

我们上文中提到JavaScript中进入提案中的特性我们可以编写Polyfill,只需要很短的时间就可以讲新特性投入到生产环境中。这时,脑海中闪现出的第一个想法就是CSS Polyfill,只要CSS的Polyfill 足够强大,CSS或许也能有JavaScript一样的发展速度,令人可悲的是编写CSS Polyfill异常的困难,并且大多数情况下无法在不破坏性能的情况下进行。这是因为JavaScript是一门动态脚本语言[6]。它带来了极强的扩展性,正是因为这样,我们可以很轻松使用JavaScript做出JavaScript的Polyfill。但是CSS不是动态的,在某些场景下,我们可以在编译时将一种形式的CSS的转换成另一种(如PostCSS[7])。如果你的Polyfill依赖于DOM结构或者某一个元素的布局、定位等,那么我们的Polyfill就无法编译时执行,而需要在浏览器中运行了。不幸的是,在浏览器中实现这种方案非常不容易。

页面渲染流程.png

如上图所示,是从浏览器获取到HTML到渲染在屏幕上的全过程,我们可以看到只有带颜色(粉色、蓝色)的部分是JavaScript可以控制的环节。首先我们根本无法控制浏览器解析HTML与CSS并将其转化为DOMCSSOM的过程,以及Cascade,Layout,Paint,Composite我们也无能为力。整个过程中我们唯一完全可控制的就是DOM,另外CSSOM部分可控。

CSS Houdini草案中提到,这种程度的暴露是不确定的、兼容性不稳定的以及缺乏对关键特性的支持的。比如,在浏览器中的 CSSOM 是不会告诉我们它是如何处理跨域的样式表,而且对于浏览器无法解析的 CSS 语句它的处理方式就是不解析了,也就是说——如果我们要用 CSS polyfill让浏览器去支持它尚且不支持的属性,那就不能在 CSSOM 这个环节做,我们只能遍历一遍DOM,找到 <style><link rel="stylesheet"> 标签,获取其中的 CSS 样式、解析、重写,最后再加回 DOM 树中。令人尴尬的是,这样DOM树全部刷新了,会导致页面的重新渲染(如下如所示)。

即便如此,有的人可能会说:“除了这种方法,我们也别无选择,更何况对网站的性能也不会造成很大的影响”。那么对于部分网站是这样的。但如果我们的Polyfill是需要对可交互的页面呢?例如scrollresizemousemovekeyup等等,这些事件随时会被触发,那么意味着随时都会导致页面的重新渲染,交互不会像原本那样丝滑,甚至导致页面崩溃,对用户的体验也极其不好。

综上所述,如果我们想让浏览器解析它不认识的样式(低版本浏览器使用grid布局),然而渲染流程我们无法介入,我们也只能通过手动更新DOM的方式,这样会带来很多问题,Houdini的出现正是致力于解决他们。

Houdini API

Houdini是一组底层API,它公开了CSS引擎的各个部分,如下图所示展示了每个环节对应的新API(灰色部分各大浏览器还未实现),从而使开发人员能够通过加入浏览器渲染引擎的样式和布局过程来扩展CSS。Houdini是一群来自Mozilla,Apple,Opera,Microsoft,HP,Intel和Google的工程师组成的工作小组设计而成的。它们使开发者可以直接访问CSS对象模型(CSSOM),使开发人员可以编写浏览器可以解析为CSS的代码,从而创建新的CSS功能,而无需等待它们在浏览器中本地实现。

CSS Houdini-API

Properties & Values API

尽管当前已经有了CSS变量,可以让开发者控制属性值,但是无法约束类型或者更严格的定义,CSS Houdini新的API,我们可以扩展css的变量,我们可以定义CSS变量的类型,初始值,继承。它是css变量更强大灵活。

CSS变量现状:

.dom {
  --my-color: green;
  --my-color: url('not-a-color'); // 它并不知道当前的变量类型
  color: var(--my-color);
}

Houdini提供了两种自定义属性的注册方式,分别是在js和css中。

CSS.registerProperty({
  name: '--my-prop', // String 自定义属性名
  syntax: '<color>', // String 如何去解析当前的属性,即属性类型,默认 *
  inherits: false, // Boolean 如果是true,子节点将会继承
  initialValue: '#c0ffee', // String 属性点初始值
});

我们还可以在css中注册,也可以达到上面的效果

@property --my-prop {
  syntax: '<color>';
  inherits: false;
  initial-value: #c0ffee;
}

这个API中最令人振奋人心的功能是自定义属性上添加动画,像这样:transition: --multiplier 0.4s;,这个功能我们在前面介绍什么是js in css那个demo[8]用使用过。我们还可以使用+使syntax属性支持一个或多个类型,也可以使用|来分割。更多syntax属性值:

属性值描述<length>长度值<number>数字<percentage>百分比<length-percentage>长度或百分比,calc将长度和百分比组成的表达式<color>颜色<image>图像<url>网址<integer>整数<angle>角度<time>时间<resolution>分辨率<transform-list>转换函数<custom-ident>ident

Worklets

Worklets是渲染引擎的扩展,从概念上来讲它类似于Web Workers[9],但有几个重要的区别:

  1. 设计为并行,每个Worklets必须始终有两个或更多的实例,它们中的任何一个都可以在被调用时运行
  2. 作用域较小,限制不能访问全局作用域的API(Worklet的函数除外)
  3. 渲染引擎会在需要的时候调用他们,而不是我们手动调用

Worklet是一个JavaScript模块,通过调用worklet的addModule方法(它是个Promise)来添加。比如registerLayout,registerPaint, registerAnimator 我们都需要放在Worklet中

//加载单个
await demoWorklet.addModule('path/to/script.js');

// 一次性加载多个worklet
Promise.all([
  demoWorklet1.addModule('script1.js'),
  demoWorklet2.addModule('script2.js'),
]).then(results => {});

registerDemoWorklet('name', class {

  // 每个Worklet可以定义要使用的不同函数
  // 他们将由渲染引擎在需要时调用
  process(arg) {
    return !arg;
  }
});

Worklets的生命周期

Worklets lifecycle

  1. Worklet的生命周期从渲染引擎内开始
  2. 对于JavaScript,渲染引擎启动JavaScript主线程
  3. 然后他将启动多个worklet进程,并且可以运行。这些进程理想情况下是独立于主线程的线程,这样就不会阻塞主线程(但它们也不需要阻塞)
  4. 然后在主线程中加载我们浏览器的JavaScript
  5. 该JavaScript调用 worklet.addModule 并异步加载一个worklet
  6. 加载后,将worklet加载到两个或多个可用的worklet流程中
  7. 当需要时,渲染引擎将通过从加载的Worklet中调用适当的处理函数来执行Worklet。该调用可以针对任何并行的Worklet实例。

Typed OM

Typed OM是对现有的CSSOM的扩展,并实现 Parsing APIProperties & Values API相关的特性。它将css值转化为有意义类型的JavaScript的对象,而不是像现在的字符串。如果我们尝试将字符串类型的值转化为有意义的类型并返回可能会有很大的性能开销,因此这个API可以让我们更高效的使用CSS的值。

现在读取CSS值增加了新的基类CSSStyleValue,他有许多的子类可以更加精准的描述css值的类型:

子类描述CSSKeywordValueCSS关键字和其他标识符(如inherit或grid)CSSPositionValue位置信息 (x,y)CSSImageValue表示图像的值属性的对象CSSUnitValue表示为具有单个单位的单个值(例如50px),也可以表示为没有单位的单个值或百分比CSSMathValue比较复杂的数值,比如有calc,min和max。这包括子类 CSSMathSum, CSSMathProduct, CSSMathMin,CSSMathMax, CSSMathNegate 和 CSSMathInvertCSSTransformValue由CSS transforms组成的CSSTransformComponent列表,其中包括CSSTranslate, CSSRotate, CSSScale, CSSSkew, CSSSkewX, CSSSkewY, CSSPerspective 和 CSSMatrixComponent

使用Typed OM主要有两种方法:

  1. 通过attributeStyleMap设置和获取有类型的行间样式
  2. 通过computedStyleMap获取元素完整的Typed OM样式

使用attributeStyleMap设置并获取

myElement.attributeStyleMap.set('font-size', CSS.em(2));
myElement.attributeStyleMap.get('font-size'); // CSSUnitValue { value: 2, unit: 'em' }

myElement.attributeStyleMap.set('opacity', CSS.number(.5));
myElement.attributeStyleMap.get('opacity'); // CSSUnitValue { value: 0.5, unit: 'number' };

在线demo[10]

使用computedStyleMap

.foo {
  transform: translateX(1em) rotate(50deg) skewX(10deg);
  vertical-align: baseline;
  width: calc(100% - 3em);
}
const cs = document.querySelector('.foo').computedStyleMap();

cs.get('vertical-align');
// CSSKeywordValue {
//  value: 'baseline',
// }

cs.get('width');
// CSSMathSum {
//   operator: 'sum',
//   length: 2,
//   values: CSSNumericArray {
//     0: CSSUnitValue { value: -90, unit: 'px' },
//     1: CSSUnitValue { value: 100, unit: 'percent' },
//   },
// }

cs.get('transform');
// CSSTransformValue {
//   is2d: true,
//   length: 3,
//   0: CSSTranslate {
//     is2d: true,
//     x: CSSUnitValue { value: 20, unit: 'px' },
//     y: CSSUnitValue { value: 0, unit: 'px' },
//     z: CSSUnitValue { value: 0, unit: 'px' },
//   },
//   1: CSSRotate {...},
//   2: CSSSkewX {...},
// }

Layout API

开发者可以通过这个API实现自己的布局算法,我们可以像原生css一样使用我们自定义的布局(像display:flex, display:table)。在Masonry layout library[11] 上我们可以看到开发者们是有多想实现各种各样的复杂布局,其中一些布局光靠 CSS 是不行的。虽然这些布局会让人耳目一新印象深刻,但是它们的页面性能往往都很差,在一些低端设备上性能问题犹为明显。

CSS Layout API 暴露了一个registerLayout方法给开发者,接收一个布局名(layout name)作为后面在 CSS中使用的属性值,还有一个包含有这个布局逻辑的JavaScript类。

my-div {
  display: layout(my-layout);
}
// layout-worklet.js
registerLayout('my-layout', class {
  static get inputProperties() { return ['--foo']; }
  
  static get childrenInputProperties() { return ['--bar']; }
  
  async intrinsicSizes(children, edges, styleMap) {}

  async layout(children, edges, constraints, styleMap) {}
});
await CSS.layoutWorklet.addModule('layout-worklet.js');

目前浏览器大部分还不支持

Painting API

我们可以在CSS background-image中使用它,我们可以使用Canvas 2d上下文,根据元素的大小控制图像,还可以使用自定义属性。

await CSS.paintWorklet.addModule('paint-worklet.js');
registerPaint('sample-paint', class {
  static get inputProperties() { return ['--foo']; }

  static get inputArguments() { return ['<color>']; }

  static get contextOptions() { return {alpha: true}; }

  paint(ctx, size, props, args) { }
});

Animation API

这个API让我们可以控制基于用户输入的关键帧动画,并且以非阻塞的方式。还能更改一个 DOM 元素的属性,不过是不会引起渲染引擎重新计算布局或者样式的属性,比如 transform、opacity 或者滚动条位置(scroll offset)。Animation API的使用方式与 Paint APILayout API略有不同我们还需要通过new一个WorkletAnimation来注册worklet。

// animation-worklet.js
registerAnimator('sample-animator', class {
  constructor(options) {
  }
  animate(currentTime, effect) {
    effect.localTime = currentTime;
  }
});
await CSS.animationWorklet.addModule('animation-worklet.js');

// 需要添加动画的元素
const elem = document.querySelector('#my-elem');
const scrollSource = document.scrollingElement;
const timeRange = 1000;
const scrollTimeline = new ScrollTimeline({
  scrollSource,
  timeRange,
});

const effectKeyframes = new KeyframeEffect(
  elem,
  // 动画需要绑定的关键帧
  [
    {transform: 'scale(1)'},
    {transform: 'scale(.25)'},
    {transform: 'scale(1)'}
  ],
  {
    duration: timeRange,
  },
);
new WorkletAnimation(
  'sample-animator',
  effectKeyframes,
  scrollTimeline,
  {},
).play();

关于此API的更多内容:(https://github.com/w3c/css-houdini-drafts/tree/main/css-animation-worklet-1)

Parser API

允许开发者自由扩展 CSS 词法分析器。

解析规则:

const background = window.cssParse.rule("background: green");
console.log(background.styleMap.get("background").value) // "green"

const styles = window.cssParse.ruleSet(".foo { background: green; margin: 5px; }");
console.log(styles.length) // 5
console.log(styles[0].styleMap.get("margin-top").value) // 5
console.log(styles[0].styleMap.get("margin-top").type) // "px"

解析CSS:

const style = fetch("style.css")
        .then(response => CSS.parseStylesheet(response.body));
style.then(console.log);

Font Metrics API

它将提供一些方法来测量在屏幕上呈现的文本元素的尺寸,将允许开发者控制文本元素在屏幕上呈现的方式。使用当前功能很难或无法测量这些值,因此该API将使开发者可以更轻松地创建与文本和字体相关的CSS特性。例如:

  • flex布局: align-items baseline特性。需要知道每一个flex盒子中第一个元素的基线位置。
  • 首字母: 需要知道每个字母的基线高度和字母最大的高度,以及换行内容的基线长度。
  • 单个字形的前进和后退。
  • 换行: 需要访问字体数据,文本的所有样式输入以及布局信息(可用的段落长度等)。
  • 元素中的每一个line boxes都需要一个基线。(line boxes代表包含众多inline boxes的这行)

Houdini 目前进展

Is Houdini ready yet

(https://ishoudinireadyyet.com/)

Houdini 的蓝图

了解到这里,部分开发者可能会说:“我不需要这些花里胡哨的技术,并不能带收益。我只想简简单单的写几个页面,做做普通的Web App,并不想试图干预浏览器的渲染过程从而实现一些实验性或炫酷的功能。”如果这样想的话,我们不妨退一步再去思考。回忆下最近做过的项目,用于实现页面效果所使用到的技术,grid布局方式在考虑兼容老版本浏览器时也不得不放弃。我们想控制浏览器渲染页面的过程并不是仅仅为了炫技,更多的是为了帮助开发者们解决以下两个问题:

  1. 统一各大浏览器的行为
  2. JavaScript一样,在推出新的特性时,我们可以通过Polyfill的形式快速的投入生产环境中。

几年过后再回眸,当主流浏览器完全支持Houdini的时候。我们可以在浏览器上随心所欲的使用任何CSS属性,并且他们都能完美支持。像今天的grid布局在旧版本浏览器支持的并不友好的这类问题,那时我们只需要安装对应的Polyfill就能解决类似的问题。

者:HXGNMSL来源:CSDN原文:https://blog.csdn.net/HXGNMSL/article/details/89076476

Javascript的历史来源

94年网景公司 研发出世界上第一款浏览器。

95年 sun公司 java语言诞生

网景公司和sun合作。

Javascript ===> javascript

JavaScript和ECMAScript的关系

简单来说ECMAScript不是一门语言,而是一个标准。符合这个标准的比较常见的有:JavaScript、Action Script(Flash中用的语言)

JavaScript的基本结构:

JavaScript的语法:

JavaScript的基础语法

变量的声明及使用

数据类型

运算符

逻辑控制语句

注释

语法规则

变量的声明语法:

var变量名;

例如:

Var num;

然后进行赋值:num = 10;也可以声明时直接赋值:

Var num =10;

在JavaScript中,提供了常用的基本数据类型:

undefined 未定义;

null 空;

string 字符串类型;

boolean 布尔类型;

number 数值类型;

运算符:

算数运算符:+、-、*、/、%、++、–;

比较运算符:>、<、> =、< =、==、!=;

逻辑运算符:&&、||、!;

赋值运算符:=;

逻辑控制语句:

JavaScript的逻辑控制语句也分为两类:条件结构和循环机构。

条件结构

条件机构分为if结构和switch结构:

If…else

Switch

循环结构

JavaScript的循环结构的执行顺序与Java类似,主要包括以下几种结构:

for循环

while循环

do…while循环

for…inx循环

示例:

for(var i=0;i<10;i++){

Document.write(“*”);

}

输出结果:**********

循环中断:

用于循环中断的语句有以下两种:

break.

continue.

与Java用法一样,break是跳出循环,continue是跳入下一次循环。

函数

函数有两种:一种是系统函数,一种是自定义函数

常用的系统函数包括:

parseInt():转换为整数。

parseFloat():转换为浮点型。

isNaN():判断非数字。

Eval():计算表达式值。

自定义函数:

自定义函数的语法

function 函数名(参数1,参数2,…){

…//语句

Return 返回值;//可选

}

函数的调用:

函数的调用方式有以下两种

事件名=函数名(传递的实参值),例如:

“函数名()”

直接使用函数名(传递的实参值),例如:

var recult = add(2,3);

匿名函数

匿名函数的语法

var sumFun=function(num1,num2){

return(nun1,num2);

} ;

在语法中:

var sunFun=function(num1,num2)表示声明一个变量等于某个函数体。

{…};是把整个函数体放在变量的后面,并把末尾添加一个分号。

匿名函数的调用:

由于匿名函数定义的整个语句,可以像赋值一样赋给一个变量进行保存,所以可以使用如下方式调用语法中的匿名函数:

var sum=sumFun(2,3)

BOM概述

使用BOM可以移动窗口,改变状态栏中的文本,执行其他与页面内容不直接相关的动作。它包含的对象主要有以下几种;

Window对象

Window对象是指整个窗口对象,可以通过操作Window对象的属性和方法控制窗口,例如,打开或关闭一个窗口。

History对象

浏览器访问过的历史页面对应History对象,通过History对象的属性和方法实现浏览器的前进或后退的功能。

Location对象

浏览器的地址栏对应Location对象,通过Location对象的属性和方法控制页面跳转。

Document对象

浏览器内的网页内容对应Document对象,通过Document对象的属性和方法,控制页面元素。

Window常用的属性有:

history:有关客户访问过的URL的信息。

location:有关当前URL的信息。

Screen: 有关客户端的屏幕和显示性能的信息。

Window对象常用的方法:

prompt():显示可提示用户输入的对话框。

alert():显示带有一段消息和一个人“确认”按钮的警告框。

confirm():显示带有一段消息以及“确认”按钮“取消”按钮的对话框。

close():关闭浏览器窗口。

open():打开一个新的浏览器窗口,加载给定URL所指定的文档。

setTimeout():用于在指定(以毫秒计)后调用函数或计算表达式。

setTneerval():按照指定的周期 (以毫秒计)数来调用函数或计算表达式。

Window对象常用窗口特征属性

height、width:窗口文档显示区的高度、宽度,以像素计。

left、top:窗口的x坐标y坐标,以像素计。

toolbar:yes|no|1|0:是否显示浏览器的工具栏,默认是yes。

scrollbars =yes|no|1|0:是否显示滚动条,默认是yes。

locationyes|no|1|0:是否显示地址栏,默认是yes。

status|no|1|0:是否添加地址栏,默认是yes。

menubar|no|1|0:是否显示菜单栏,默认是yes。

resizable|no|1|0:窗口是否可调节尺寸,默认是yes。

Window对象的常用事件:

onload:一个页面或一副图像完成加载。

onmouseover:鼠标指针移到某元素之上。

onclick:单击某个对象。

onkeydown:某个键盘按键被按下。

onchange:域的内容被改变。

History对象的方法:

back():加载History对象列表中的上一个URL。

forward():加载History对象列表中的下一个URL。

go():加载History对象列表中的某个具体URL。

Location对象的属性:

host:设置或返回主机名和当前URL的端口号。

hostname:设置或返回当前URL的主机名。

href:设置或返回完整的URL。

Location对象的方法:

reload():重新加载当前文档。

replace():用新的文档替换当前文档。

Document对象常用的属性:

referrer:返回载入当前文档的URL。

URL:返回当前文档的URL。

Document对象的常用方法:

getElementById():返回对拥有指定id的第一个对象的引用。

getElementsByName():返回带有指定名称的对象的集合。

getElementsByTagName():返回带有指定标签名的对象的集合。

write():向文档写文本、HTML表达式代码。

内置对象

系统的内置对象有Date对象、Array对象、String对象和Math对象等。

Date:用于操作日期和时间。

Array:用于在单独的变量名中储存一系列的值。

String:用于支持对字符串的处理。

Math:用于执行数学任务,包含了若干数字常量和函数。

Date对象:

1:创建日期对象

Date对象包含日期和时间两个信息,创建日期对象的基本语法有两种:

创建日期的基本语法1: var 日期实例化=new Date(参数);

创建日期的基本语法2: var 日期实例化=new Date();

Date对象的常用方法:

getDate():从Date对象返回一个月中的某一天,其值介于1到31之间。

getDay():从Date对象返回星期中的某一天,其值介于0到6之间。

getHours():返回Date对象的小时,其值介于0到23之间。

getMinutes():返回Date对象的分钟,其值介于0到59之间。

getSeconds():返回Date对象的秒数,其值介于0到59之间。

getMonth():返回Date对象的月份,其值介于0到11之间。

getFullYear():返回Date对象的年份,其值为4位数。

getTime():返回自某一时刻(2010年1月1日)以来的毫秒数。

DOM概述

什么是DOM

DOM是文档对象的缩写,和语言无关。它提供了访问、动态修改结构文档的接口,W3C制定了DOM规范,主流浏览器都支持。

使用Core DOM操作节点

访问节点:

使用getElement系列方法访问指定节点。

getElementById():返回对拥有指定id的第一个对象的引用。

getElementsByName():返回带有指定名称的对象的集合。

getElementsByTagName():返回带有指定标签名的对象的集合。

使用层次关系访问节点。

parenNode:返回节点的父节点。

firstChild:返回节点的首个节点。文本和属性节点没有父节点,会返回一个空数组,对于元素节点,若是没有子节点会返回null。

lastChild:返回节点的最后一个子节点,返回值同firstChild。

操作节点属性值

CoreDOM的标准方法包括以下两种:

getAttribute(“属性名”):获取属性值。

getAttribute(“属性名”,“属性值”):设置属性值

创建和增加节点:

创建节点

createElement(tagName):按照给定的标签名称创建一个新的元素节点

appendChild(nodeName):向以存在节点列表的末尾添加新的节点。

inserBefore(newNode,oldNode):向指定的节点之前插入一个新的子节点。

cloneNode(deep):复制某个指定的节点。

删除和替换节点

removeChild(node):删除指定的节点。

replaceChild(newNode,oldNode):用其他的节点替换指定的节点。

Table对象的属性和方法

属性:

rows[]:返回包含表格中所有行的一个数组。

rows[]用于返回表格中所有行的一个数组。

方法:

inserRow():在表格中插入一个新行。

deleteRow():从表格中删除一行。

数组

数组是具有相同数据类型的一个或多个值得集合

创建数组的语法:

var 数组名称=new Array(size);

数组的赋值的两种方式:

先声明在赋值

var province = new Array(4);

province[0]=“河北省”;

province[1]=“河南省”;

索引也可以使用标识(字符串),例如:

var province=new Array(4);

province[‘河北省’]=“河北省”;

province[‘河南省’]=“河南省”;

声明时同时初始化

var province=new Array(“河北省”,“河南省”,“湖北省”,“广东省”);

Array对象的常用属性和方法:

属性:

length:设置或返回数组中元素的数目。

方法:

join():把数组的所有元素放入一个字符串,通过一个分隔符进行分割。

sort():对数组的元素进行排序。


在本教程中,我们有一个使用 HTML、CSS 和 JS 制作的待办事项列表应用程序,我们将把它与 Cerbos 集成以向应用程序添加授权。授权确定用户是否可以执行特定操作或访问某些资源或数据。它使组织能够控制和保护对敏感数据库、私人和个人数据以及公司资源的访问。在我们的 JS 应用程序中,授权将定义用户可以执行的操作(创建待办事项并阅读待办事项)以及管理员可以执行的操作(创建、阅读和删除待办事项)。

RBAC 简介

基于角色的访问控制 (RBAC)是一种访问控制方法,它根据最终用户的组织角色为其分配权限。RBAC 提供细粒度的控制,提供了一种简单、易于管理的访问管理方法,与单独分配权限相比,这种方法不容易出错。

使用 RBAC 的优点是管理授权权限变得更加容易,因为系统管理员可以批量管理用户和权限,而不是逐个管理。
我们已经定义了 RBAC 策略,并将集成 Cerbos 以根据用户身份授权创建、读取和删除待办事项。我们对谁可以做什么的业务要求如下:

  • 管理员可以执行所有操作(创建、读取和删除)
  • 用户可以创建和阅读待办事项,但不能删除待办事项。

什么是 Cerbos 以及为什么选择 Cerbos?

Cerbos 是一个开源授权层,可让您轻松实现应用程序的授权。在Cerbos,我们提供细粒度的访问控制来增强安全性,同时使您的应用程序更快、更具可扩展性。使用 Cerbos 进行授权具有使用严格的 JS 代码无法获得的各种优势

  • 更高的安全性:数字基础设施面临的威胁始终存在,访问管理始终是一个紧迫的问题。当您在 JS 应用程序中集成 Cerbos 授权时,您可以启用细粒度的访问控制,并通过几行代码安全地消除未经授权的访问。\
  • 可扩展性:Cerbos 拥有一个易于扩展的集中式策略管理系统。随着应用程序中用户数量的增长,您的授权解决方案将随应用程序扩展,只需单击几下即可修改策略,而无需更改一行代码。

策略生成的最佳实践

当组织采用 RBAC 访问模型时,必须遵守有关安全和管理的最佳实践,并致力于不断改进其访问协议以防范新出现的威胁。

以下列表包含一些您必须遵循的 RBAC 最佳实践:

  1. 确定需求和角色:了解组织的特定需求并建立清晰的角色层次结构。明确定义职责以指导 RBAC 系统的设计。
  2. 制定和执行政策:创建全面的 RBAC 政策,概述访问规则、范围和目标,并确保所有利益相关者都可以访问它们。
  3. 应用最小特权原则:仅为每个角色分配所需的最小权限,遵守最小特权原则以防止未经授权的访问。
  4. 定期审查和自动化:定期审查角色分配,以确保它们反映组织变化,并使用自动化进行有效的权限分配和删除。
  5. 优先监控和响应:实施日志记录和监控以检测可疑行为并制定事件响应程序以迅速解决未经授权的访问。

建模策略

我们将在示例代码中使用这些角色来演示访问控制如何工作。

使用 Cerbos,访问规则始终面向资源,您编写的策略会映射到系统中的这些资源。资源可以是任何东西,而您建模策略的方式则由您决定 — 您可以通过多种方式实现相同的逻辑结果:以操作为主导、以角色为主导、以属性为主导或以它们的组合为主导。

话虽如此,有些模式更适合特定场景——让我们看看一些不同的方法。考虑这个权限模型:

Actions

Roles


CPO

CTO

Exec-1

Exec-2

Exec-3

View

Allowed

Allowed

Allowed

Allowed

Allowed

Add

Allowed

Allowed

Allowed

Allowed

Allowed

Delete

Allowed

Allowed

Not Allowed

Not Allowed

Not Allowed

我们将描述以行动为主导的政策,因为我们已经为我们的 JS 应用程序实施了以行动为主导的政策。

行动主导

在这里,我们关注一个动作并列出可以执行该动作的所有角色。为了更好地理解这一点,我们文档中列出了一个示例,如下所示:

# Principals in the following three roles can perform the `run` action
  - actions:
      - "run"
    effect: EFFECT_ALLOW
    roles:
      - JR_MANAGER
      - SR_MANAGER
      - CFO

# All principals can perform the `view` action
  - actions:
      - "view"
    effect: EFFECT_ALLOW
    roles:
      - ["*"]

如果您的系统符合以下任一情况,则此方法可能适用:

  • 您的角色在他们能做的事情上是“相似的”,例如 JR_MANAGER 和 SR_MANAGER;JR_MANAGER 可能拥有 SR_MANAGER 权限的子集。当然,在任何一个方向上都会有重复,但从行动角度来推理这一点通常更容易。
  • 您有“高风险”操作 — 您希望能够一目了然地知道哪些角色有权访问特定操作。明确列出每个操作的角色,可以大大减少意外向错误用户授予不需要的权限的可能性。
  • 您拥有的角色数量相对较多,但操作数量较少。

先决条件

  1. WSL(如果 Windows 是你的主要操作系统)
  2. Docker

在 JavaScript 应用程序中集成 Cerbos

  1. 首先,克隆此存储库并继续操作。确保您位于存储库的主分支中。

我们的应用程序的文件结构

构建成功后,您应该会看到网页在浏览器的localhost:5500上加载。

目前,没有任何策略,此应用程序中的 CPO 和 CTO 被授予删除待办事项的权限,而高管(1、2 和 3)只能查看和添加待办事项。但是,由于我们尚未将 Cerbos 与应用程序集成以进行权限管理,因此 CTO 和 CPO 无法删除待办事项。成功集成 Cerbos 后,CTO 和 CPO 将能够删除待办事项。

将 Cerbos 集成为服务

要将 Cerbos 集成到我们的 JavaScript 应用程序中,我们首先要启动 Cerbos Docker 容器。在根目录 (/to-dolist-cerbos) 中使用以下命令运行 Docker 容器:

docker run --rm --name cerbos -d -v $(pwd)/cerbos/policies:/policies -p 3592:3592 -p 3593:3593 ghcr.io/cerbos/cerbos:0.34.0 

使用 Cerbos RBAC 策略生成器生成策略文件

Cerbos Playground是一款用于在线创建和测试策略的实用程序。Cerbos Playground 是了解策略创建甚至生成可用策略的绝佳方式:https://play.cerbos.dev/new ?generator 。

如何使用 RBAC 策略生成器为我们的 javascript 应用程序生成策略?

我们有两个角色:用户和管理员(其中 CTO/CPO 是管理员,而高管是用户)。添加操作并根据我们要授予的权限选择复选框。

根据偏好选择后,我们可以点击**生成**按钮,生成一个名为to-dos.yaml的YAML策略文件。

使用生成器生成策略后,只需将其复制并添加到应用程序的文件结构中即可。策略生成器还会生成一个 to-dos_test.yaml 文件,该文件旨在帮助自动测试访问控制策略。确保测试文件始终以 _test 后缀结尾以下是它通常包含的内容和功能:

现在我们已经将 Cerbos 与 JS 应用程序集成,我们将运行它来检查策略是否按预期工作。

测试我们的策略文件

让我们使用测试文件和 Cerbos RBAC 策略生成器生成的 _testdata _ 来测试策略文件。

您可以使用此 Docker 命令(在 PowerShell 终端中)来运行测试:

docker run -i -t -v "$(Get-Location)/cerbos/policies:/policies" ghcr.io/cerbos/cerbos:latest compile /policies

运行此命令后,测试成功执行。

在应用程序 UI 中生效的策略

如果高管(具有角色:用户)尝试删除待办事项:

随着政策的实施,高管不得删除待办事项;他们会收到一条警告:您无权删除此待办事项。该权限仅授予 CPO 和 CTO 角色。

根据政策,高管可以将待办事项添加到列表中:

将任务添加到文本框并单击添加待办事项按钮将其添加到待办事项列表中。

CPO 已授权,待办事项已成功删除。

这个实际演示成功地描绘了 Cerbos 与我们的 JS 应用程序的集成。

经常问的问题

Q1. 制定 RBAC 政策时我们必须考虑哪些最佳实践?

A1:当组织采用 RBAC 访问模型时,必须遵守有关安全和管理的最佳实践,并致力于不断改进其访问协议,以防范新出现的威胁。

以下列表包含 RBAC 最佳实践的公平样本:

  1. 确定需求:了解组织角色和职责以指导 RBAC 设计。
  2. 生成政策:制定明确的 RBAC 政策,详细说明规则、范围和目标,并与利益相关者分享。
  3. 建立角色层次结构:定义与组织结构相一致的角色和职责。
  4. 应用最小权限:为每个角色授予最小权限以限制访问。

问题 2:在政策生效之前,可以使用 Cerbos 进行测试吗?

A2:是的,可以在策略生效之前使用 Cerbos 对其进行测试。Cerbos 提供强大的策略测试功能,允许您在将访问控制规则部署到生产环境之前对其进行验证。使用 Cerbos Policy Generator 中提供的 Cerbos 测试框架,您可以确保策略强制执行所需的访问控制规则并避免实际场景中的潜在问题。这种部署前测试有助于维护访问控制策略的完整性和安全性,降低未经授权访问的风险并确保遵守组织的安全准则。

结论

您已通过以下关键步骤成功将 Cerbos 集成到演示待办事项列表应用程序中:

  • 我们正在 Docker 容器中部署 Cerbos,以确保一致且隔离的环境。
  • 制定和完善策略文件以定义明确的访问控制规则。
  • 重建应用程序以纳入这些政策。
  • 验证应用程序是否遵守既定的授权规则,确保安全和受控的访问。