2020前端面试都会问啥?

thbcm阅读(540)

文章转载自公众号:前端工匠(微信号:frontJS)

2020年注定是不平凡的一年,找工作的竞争压力可想而知,如何从众多面试者中脱颖而出呢,总结了一波前端常见面试题,希望对大家有所帮助!

1. javascript 作用域与预解析

什么是预解析?

分两步执行:

第一步:(代码还没有执行。预览页面之前,写完之后)

找程序中var关键字,如果找到了提前给var定义的变量赋值undefined

找程序中的普通函数,如果找到了,函数提升,将整个函数赋值给函数名。

如果找的var的名字和函数名字相同,函数优先。

第二步: 逐行解析代码。按照上下顺序。如果碰到函数定义,忽略。

重点:函数内部同样适用于js预解析。

我们通过几道面试题,来了解下作用域和和预解析的原理

function fun(){ console.log(n); var n = 456; console.log(n); } var n = 123; fun(n);

猜一猜此题中输出的结果是?可能并不是你想的结果,why?

代码分析如下:

1-5行定义函数fun

6行定义变量 n

7行执行函数并传入变量 n

注意:fun函数内部有预解析。

预解析及执行步骤:

  1. Fun函数开始执行前,将var n提前执行,初始化为undefined
  1. 由于函数传入参数 n 并没有使用,忽略。
  1. 开始执行第2行,输出为undefined
  1. 执行第3行,此时即n = 456,即将n值重置为456。
  1. 执行第4行,输出改变后的n。

通过以上步骤分析,即可得知预解析的原理了(针对有var的变量提前赋初始值)

下面再看一题,看看函数预解析

猜一猜此题中输出的结果是?可能并不是你想的结果,why?

代码分析如下:

29行定义一个全局变量

30-32行定义一个函数 f1

33-36行定义一个函数 f2

37行执行函数 f2

38行输出结果 n

预解析及执行步骤:

1.代码执行前,预解析先初始化变量 n, f1, f2,将它们都置为 undefined.

2.接着执行第29行,为变量n赋值

3.接着执行第30-32行,为函数变量 f1 赋值,即 f1 为函数了

4.接着执行第33-36行,为函数变量 f2 赋值

5.执行第37行,即执行 f2 函数。

  1. f2 函数执行前,同样预解析,先将 n 初始化为undefined,接着把 n 赋值为456,接着调用f1函数执行。
  1. f1 在 f2 中执行,那 f1 的作用域应该是 f2 ,应该输出456?

8.35行执行 f1 函数时无调用者,即 f1 函数为全局作用域,输出全局 n 为 123

9.第38行直接输出全局变量 n ,即123

继续深入,再来一题:

猜一猜此题中输出的结果是?可能并不是你想的结果,why?

代码分析如下:

预解析只针对varfunction定义的变量

预解析及执行步骤:

1.预解析先初始化变量length, obj, f1并赋值为undefined

2.接着为变量length赋值为 100

3.接着为函数变量 f1 赋值为函数

4.接着为变量`obj 赋值为对象

5.第52行,调用对象obj的 f2 函数执行,传入形参 f1 和1

6.第47行,f2 函数接收实参为 f1 , 接着执行 f1 函数

7.同上,f1 函数执行无调用者,作用域为全局,this指向window,输出全局变量length,即100

8.第47行,f2 函数接收实参 f1 ,若要取到所有实参则需要arguments对象,第一个参数arguments[0]==f1,第二个arguments[1]==1,依此类推。

9.第49行,arguments[0]()看上去是f1(),那也应该输出100?

10.注意arguments[0]作用域与f1的作用域并不相同,第48行直接执行f1,无调用者,作用域为全局作用域,但arguments[0]作用域为arguments对象,即thisarguments,则应输出 2,因为arguments对象的属性length为2。

2. 前端如何处理跨域

1、为什么会出现跨域问题

同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说 Web 是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的 javascript 脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)。

2、什么是跨域

当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域。

当前页面url 被请求页面url 是否跨域 原因
http://www.w3cschool.cn/ http://www.w3cschool.cn/index.html 同源
http://www.w3cschool.cn/ https://www.w3cschool.cn/index.html 跨域 协议不同(http/https
http://www.w3cschool.cn http://www.baidu.cn/ 跨域 主域名不同(w3cschool/baidu
http://www.w3cschool.cn/ http://123.w3cschool.cn 跨域 子域名不同(www/123
www.test.com:8080/ www.test.com:7001 跨域 端口号不同

3、跨域解决方法

【1】设置document.domain解决无法读取非同源网页的 Cookie问题

【2】跨文档通信 API:window.postMessage()

【3】JSONP

【4】CORS

【5】Proxy

作为开发人员,最关心的跨域一般是2种交互的跨域,即ProxyCORS,很多开发只图一时方便,使用了Proxy,在打包后就发现又有跨域了,不知道怎么解决,下面我们通过实例一点点给大家解析。

跨域出现

首先,需要重现跨域,先用node写一个简单的接口,如下

使用命令node启动这个服务,则搭建了一个最简单的后端服务接口,然后使用前端ajax来请求这个接口,如下

创建一个简单的html页面,再加上上面的简单ajax请求,在浏览器控制台就看到了跨域error

从日志上看出现了“Access-Control-Allow-Origin”,表示是访问源未被许可,即跨域了。

跨域解决之Proxy

现在项目一般都使用脚手架,即使用webpack,那可以使用webpack自带的proxy特性来处理跨域,下面我们来配置一个简单的webpack项目,如下

1.创建配置文件webpack.config.js

配置文件说明项目入口文件在srcindex.js,打包输出目录为dist,使用proxy处理跨域,即前端所有请求会自动跳转到target指定的url

注意这里有一个前缀,若没有可以不写。

2.创建src目录及index.js

3.创建工程依赖文件package.json

依赖文件中配置了webpack启动命令

Npm run dev 启动服务

Npm run start 启动服务

Npm run build 打包命令

当启动服务后,打开浏览器输入 http://localhost:8080 ,即可看到一个空白页面,打开控制台可以看到ajax请求

拿到交互的数据了。

这种方式是开发最常用的,但是打包后就有问题了,因为打包后就不存在proxy了,跨域还是会存在,那应该怎么解决?

跨域解决之CORS

这种方式是在后端配置,配置CORS后,前端无需任何处理即可访问后端的接口,无论是在开发时还是部署时都是OK的。

下面,我们把proxy注释掉,使用CORS方式处理,如下:

配置了cors后,接口就可以随便访问了。

此时,还需要把前端请求地址改一下,改为直接请求后端接口,如下

刷新页面,打开控制台可以看到请求地址为

通过此种方式,在开发阶段或部署都没有问题,这也是开发中最常用的2种方式。

3. 什么是闭包?如何理解

闭包(closure)是javascript的一大难点,也是它的特色。很多高级应用都要依靠闭包来实现。

要理解闭包,首先要理解javascript的全局变量和局部变量。

javascript语言的特别之处就在于:函数内部可以直接读取全局变量,但是在函数外部无法读取函数内部的局部变量。

如何从外部读取函数内部的局部变量?

我们有时候需要获取到函数内部的局部变量,正常情况下,这是办不到的!只有通过变通的方法才能实现。那就是在函数内部,再定义一个函数。

1、闭包的概念

上面代码中的 f2 函数,就是闭包。

各种专业文献的闭包定义都非常抽象,我的理解是: 闭包就是能够读取其他函数内部变量的函数。

由于在javascript中,只有函数内部的子函数才能读取局部变量,所以说,闭包可以简单理解成“定义在一个函数内部的函数“。

所以,在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

2、闭包的用途

闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中,不会在 f1 调用后被自动清除。

为什么会这样呢?原因就在于 f1 是 f2 的父函数,而 f2 被赋给了一个全局变量,这导致 f2 始终在内存中,而 f2 的存在依赖于 f1 ,因此 f1 也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

在我们平时的代码中经常会用到闭包,比如在构造函数中

//另一种写法

3、常见闭包的写法

另一种调用方法

//定义函数并立即调用

4、闭包的实际应用

使用闭包,我们可以做很多事情。比如模拟面向对象的代码风格;更优雅,更简洁的表达出代码;在某些方面提升代码的执行效率。

封装

通过person.name是无法获取到name的值,如果要获取到name的值可以通过

Console.log(person.getName()); //直接获取到 张三 person.setName(“李四”); //重新设置新的名字 print(person.getName()); //获取 李四

继承

总结:闭包就是一个函数引用另外一个函数的变量,因为变量被引用着所以不会被回收,因此可以用来封装一个私有变量。这是优点也是缺点,不必要的闭包只会徒增内存消耗!

以上就是W3Cschool编程狮关于2020前端面试都会问啥?的相关介绍了,希望对大家有所帮助。

JavaScript错误的10大经典问题

thbcm阅读(165)

本文转载自公众号:前端工匠(微信号:frontJS)

本文给大家带来了10个经典的JavaScript错误,为了便于阅读,每个错误都被缩短了,让我们更深入地研究每一个问题,以确定是什么导致了这些问题,以及如何避免产生这些问题。

1. Uncaught TypeError: Cannot read property

如果你是一个 JavaScript 开发人员,你可能已经看到过这个错误。当你读取属性或在未定义对象上调用方法时,Chrome 中就会发生这种情况。你可以在 Chrome 开发者控制台中轻松进行测试。

发生这种情况的原因有很多,但常见的原因是渲染 UI 组件时状态初始化不当。让我们来看一个在现实应用中如何发生这种情况的示例。我们将选择React,但是不正确初始化的相同原理也适用于AngularVue或任何其他框架。

class Quiz extends Component {
  componentWillMount() {
    axios.get('/thedata').then(res => {
      this.setState({items: res.data});
    });
  }
  render() {
    return (
      <ul>
        {this.state.items.map(item =>
          <li key={item.id}>{item.name}</li>
        )}
      </ul>
    );
  }
}

这里有两件重要的事情要意识到:

  • 组件的状态(例如 this.state)以 undefined 状态开始使用。
  • 当你异步获取数据时,无论数据是在构造函数 componentWillMount 还是 componentDidMount 中获取,组件都将在数据加载之前至少渲染一次。当Quiz第一次渲染时,this.state.itemsundefined。这反过来又意味着ItemList会得到未定义的items,你会在控制台中得到一个错误——”UncaughtTypeError: Cannot read property ‘map’ of undefined “的错误。

这很容易解决,最简单的方法:在构造函数中使用合理的默认值初始化状态。

class Quiz extends Component {
  // 添加了这个:
  constructor(props) {
    super(props);
    this.state = {
      items: [] // 默认值
    };
  }
  componentWillMount() {
    axios.get('/thedata').then(res => {
      this.setState({items: res.data});
    });
  }
  render() {
    return (
      <ul>
        {this.state.items.map(item =>
          <li key={item.id}>{item.name}</li>
        )}
      </ul>
    );
  }
}

你的应用程序中的实际代码可能会有不同,但我希望已经给了你足够的线索,让你在你的应用程序中修复或避免这个问题。如果没有,请继续阅读,因为我将在下面介绍有关相关错误的更多示例。

2. TypeError: ‘undefined’ is not an object (evaluating

这是在 Safari 中读取属性或调用undefined对象上的方法时发生的错误,你可以在Safari开发者控制台中非常轻松地进行测试。这基本上与上述针对 Chrome 的错误相同,但 Safari 使用了不同的错误消息。

3. TypeError: null is not an object (evaluating

这是在 Safari 中读取属性或调用null对象上的方法时发生的错误,你可以在 Safari 开发者控制台中非常轻松地进行测试。

有趣的是,在JavaScript中,nullundefined不相同,这就是为什么我们看到两个不同的错误消息的原因。undefined通常是尚未分配的变量,而null表示该值为空白。 要验证它们是否相等,请尝试使用严格相等运算符。

在实际示例中可能发生这种错误的一种方式是,在加载元素之前尝试在JavaScript中使用DOM元素,这是因为DOM API对于空白的对象引用返回null

任何执行和处理DOM元素的JS代码都应在创建DOM元素后执行。JS代码按照HTML格式从上到下进行解释,所以,如果在DOM元素之前有一个标签,那么在浏览器解析HTML页面的时候,脚本标签内的JS代码就会被执行。如果在加载脚本之前尚未创建DOM元素,则会出现此错误。

在此示例中,我们可以通过添加事件侦听器来解决该问题,该事件侦听器将在页面准备就绪时通知我们。一旦触发了 addEventListenerinit() 方法就可以使用DOM元素。

<script>
  function init() {
    var myButton = document.getElementById("myButton");
    var myTextfield = document.getElementById("myTextfield");
    myButton.onclick = function() {
      var userName = myTextfield.value;
    }
  }
  document.addEventListener('readystatechange', function() {
    if (document.readyState === "complete") {
      init();
    }
  });
</script>
<form>
  <input type="text" id="myTextfield" placeholder="Type your name" />
  <input type="button" id="myButton" value="Go" />
</form>

4. (unknown): Script error

当未捕获的JavaScript错误违反跨源策略跨越域边界时,将发生脚本错误。例如,如果你将你的JavaScript代码托管在CDN上,任何未被捕获的错误(冒泡到window.onerror处理程序中的错误,而不是在try-catch中被捕获的错误)都会被报告为 “Script error”,而不是包含有用的信息。这是一种浏览器安全措施,旨在防止跨域传递数据,否则该域将无法通信。

要获取真实的错误消息,请执行以下操作。

发送Access-Control-Allow-Origin标头

Access-Control-Allow-Origin 标头设置为 * 表示可以从任何域正确访问资源。你可以根据需要将 * 替换为您的域:例如,Access-Control-Allow-Origin:www.example.com。但是,处理多个域比较复杂,如果使用 CDN 可能会出现缓存问题,那么可能不值得花费精力。

以下是一些有关如何在各种环境中设置此标头的示例:

Apache

在将提供JavaScript文件的文件夹中,创建一个具有以下内容的 .htaccess 文件:

Header add Access-Control-Allow-Origin "*"

Nginx

add_header指令添加到提供JavaScript文件的location块中:

location ~ ^/assets/ {
    add_header Access-Control-Allow-Origin *;
}

HAProxy

将以下内容添加到提供JavaScript文件的asset后端:

rspadd Access-Control-Allow-Origin:\ *

在脚本标签上设置crossorigin =“ anonymous”

在你的HTML源代码中,对于您设置了 Access-Control-Allow-Origin 标头的每个脚本,在script标记上设置crossorigin="anonymous"。在script标记上添加 crossorigin 属性之前,请确保已验证是否已为脚本文件发送了标头。在Firefox中,如果存在 crossorigin 属性,但没有 Access-Control-Allow-Origin 标头,则不会执行脚本。

5. TypeError: Object doesn’t support property

这是在 IE 中发生的错误,当您调用undefined的方法时,你可以在 IE 开发人员控制台中对此进行测试。

这等效于 Chrome 中的错误“ TypeError:‘undefined’ is not a function”。是的,对于相同的逻辑错误,不同的浏览器可能具有不同的错误消息。

这是 IE 在采用JavaScript命名空间的 Web 应用程序中常见的问题,在这种情况下,99.9%的问题是 IE 无法将当前名称空间中的方法绑定到 this 关键字。

例如,如果你的JS命名空间 Rollbar 使用 isAwesome 方法。通常,如果你在 Rollbar 名称空间中,则可以使用以下语法调用 isAwesome 方法:

this.isAwesome();

Chrome,Firefox 和 Opera 将很乐意接受此语法。另一方面,IE 则不会。因此,在使用 JS命名空间时,最安全的方法是用实际的命名空间作为前缀。

Rollbar.isAwesome();

6. TypeError: ‘undefined’ is not a function

这是在 Chrome 中发生的错误,当你调用undefined的函数时。你可以在 Chrome 开发者控制台和Mozilla Firefox开发者控制台中对此进行测试。

随着这些年来JavaScript的编码技术和设计模式越来越复杂,在回调和闭包中的自引用作用域也相应地增多,这也是相当常见的这种或那种混乱的根源。

考虑以下示例代码片段:

function clearBoard(){
  alert("Cleared");
}
document.addEventListener("click", function(){
  this.clearBoard(); // 这个 "this" 是什么?
});

如果执行上述代码,然后单击该页面,则会导致以下错误“ Uncaught TypeError:this.clearBoard not a function”。原因是正在执行的匿名函数是在文档的上下文中,而 clearBoard 是在 window 中定义的。

传统的、与旧浏览器兼容的解决方案是简单地将对它的引用保存在一个变量中,然后闭包可以继承这个变量。例如:

var self = this;
document.addEventListener("click", function(){
  self.clearBoard();
});

另外,在较新的浏览器中,可以使用 bind() 方法传递正确的引用:

document.addEventListener("click",this.clearBoard.bind(this));

7. Uncaught RangeError: Maximum call stack

这是 Chrome 浏览器在几种情况下出现的错误,一种是调用不终止的递归函数。你可以在 Chrome 开发者控制台中对此进行测试。

如果将值传递给超出范围的函数,也可能会发生这种情况。许多函数的输入值仅接受特定范围的数字,例如,Number.toExponential(digits)Number.toFixed(digits) 接受0到20之间的数字,而Number.toFixed(digits) 接受1到21之间的数字。

var a = newArray(4294967295);  //OK
var b = newArray(-1); //range error


var num = 2.555555;
document.writeln(num.toExponential(4));  //OK
document.writeln(num.toExponential(-2)); //range error!


num = 2.9999;
document.writeln(num.toFixed(2));   //OK
document.writeln(num.toFixed(25));  //range error!


num = 2.3456;
document.writeln(num.toPrecision(1));   //OK
document.writeln(num.toPrecision(22));  //range error!

8. TypeError: Cannot read property ‘length’

这是 Chrome 浏览器中发生的错误,因为读取undefined的变量的length属性,你可以在 Chrome 开发者控制台中进行测试。

通常情况下,你可以在数组上找到定义的长度,但如果数组没有初始化或者变量名被隐藏在其他上下文中,你可能会遇到这个错误。通过以下示例让我们了解此错误。

var testArray= ["Test"];
function testFunction(testArray) {
    for (var i = 0; i < testArray.length; i++) {
      console.log(testArray[i]);
    }
}
testFunction();

当你声明一个带参数的函数时,这些参数就变成了局部参数。这意味着即使你具有名称为 testArray 的变量,函数内具有相同名称的参数仍将被视为局部参数。

你可以通过两种方式解决问题:

删除函数声明语句中的参数(事实证明,你想访问那些在函数外部声明的变量,因此你不需要为函数使用参数)

var testArray = ["Test"];


/* 前置条件:在函数外部定义testArray */
function testFunction(/* No params */) {
   for (var i = 0; i < testArray.length; i++) {
     console.log(testArray[i]);
   }
}


testFunction()

调用函数,将我们声明的数组传递给它。

var testArray = ["Test"];


function testFunction(testArray) {
  for (var i = 0; i < testArray.length; i++) {
     console.log(testArray[i]);
   }
}


testFunction(testArray);

9. Uncaught TypeError: Cannot set property

当我们尝试访问未定义的变量时,它总是返回 undefined,我们无法获取或设置任何 undefined 属性。在这种情况下,应用程序将引发“ Uncaught TypeError:Cannot set property”。

例如,在 Chrome 浏览器中:

如果 test 对象不存在,则错误将引发“ Uncaught TypeError:Cannot set property”。

10. ReferenceError: event is not defined

当您尝试访问undefined 或超出当前范围的变量时,将引发此错误。你可以在Chrome浏览器中非常轻松地对其进行测试。

如果你在使用事件处理系统时收到这个错误,请确保你使用传入的事件对象作为参数。IE 等较旧的浏览器会提供全局变量事件,而 Chrome 会自动将事件变量附加到处理程序。 Firefox 不会自动添加它。jQuery 之类的库试图规范这种行为,尽管如此,最好还是使用传递给事件处理程序函数的方法。

document.addEventListener("mousemove", function (event) {
  console.log(event);
})

总结

事实证明,其中许多都是nullundefined的错误。如果使用严格的编译器选项,像Typescript这样的静态类型检查系统可以帮助你避免使用它们。它可以警告你,如果一个类型是预期的,但还没有被定义。

以上就是W3Cschool编程狮关于JavaScript错误的10大经典问题的相关介绍了,希望对大家有所帮助。

分布式服务注册发现与统一配置管理之 Consul

thbcm阅读(162)

文章转载自公众号:Java极客技术 作者:鸭血粉丝

今天这篇文章给大家介绍一个用于服务注册发现和管理配置的开源组件– Consul 。接下来让我们一起来看一下它的功能吧。

背景

目前分布式系统架构已经基本普及,很多项目都是基于分布式架构的,以往的单机模式基本已经不适应当下互联网行业的发展。随着分布式项目的普及,项目服务实例数目的增加,服务的注册与发现功能就成了一项必不可少的架构。服务的注册与发现的功能,有很多开源方案。包括早期的zookeeper,百度的disconf,阿里的diamond,基于Go语言的ETCDSpring集成的Eureka,以及前文提到的 Nacos 还有本文的主角Consul。这里不对上面提到的进行比较,本文仅介绍Consul,详细的对比,说明网上有很多资料,可以参考,例如:服务发现比较:Consul vs Zookeeper vs Etcd vs Eureka说到服务的注册与发现主要是下面两个主要功能:

  1. 服务注册与发现
  2. 配置中心即分布式项目统一配置管理

Consul 服务端配置使用

  1. 下载相应版本解压,并将可执行文件复制到/usr/local/consul目录下
  1. 创建一个service的配置文件
    silence$ sudo mkdir /etc/consul.d
    silence$ echo '{"service":{"name": "web", "tags": ["rails"], "port": 80}}' | sudo tee /etc/consul.d/web.json
  1. 启动代理
   silence$ /usr/local/consul/consul agent -dev -node consul_01 -config-dir=/etc/consul.d/ -ui

-dev 参数代表本地测试环境启动;-node 参数表示自定义集群名称;-config-drir 参数表示services的注册配置文件目录,即上面创建的文件夹-ui 启动自带的web-ui管理页面

  1. 集群成员查询方式
   silence-pro:~ silence$ /usr/local/consul/consul members

  1. HTTP协议数据查询
silence-pro:~ silence$ curl http://127.0.0.1:8500/v1/catalog/service/web
[
    {
        "ID": "ab1e3577-1b24-d254-f55e-9e8437956009",
        "Node": "consul_01",
        "Address": "127.0.0.1",
        "Datacenter": "dc1",
        "TaggedAddresses": {
            "lan": "127.0.0.1",
            "wan": "127.0.0.1"
        },
        "NodeMeta": {
            "consul-network-segment": ""
        },
        "ServiceID": "web",
        "ServiceName": "web",
        "ServiceTags": [
            "rails"
],
        "ServiceAddress": "",
        "ServicePort": 80,
        "ServiceEnableTagOverride": false,
        "CreateIndex": 6,
        "ModifyIndex": 6
    }
]
silence-pro:~ silence$

  1. web-ui管理 Consul Web UI

Consul的web-ui可以用来进行服务状态的查看,集群节点的检查,访问列表的控制以及KV存储系统的设置,相对于Eureka和ETCD,Consul的web-ui要好用的多。(Eureka和ETCD将在下一篇文章中简单介绍。)

7.KV存储的数据导入和导出

silence-pro:consul silence$ ./consul kv import @temp.json


silence-pro:consul silence$ ./consul kv export redis/

temp.json文件内容格式如下,一般是管理页面配置后先导出保存文件,以后需要再导入该文件

[
    {
        "key": "redis/config/password",
        "flags": 0,
        "value": "MTIzNDU2"
    },
    {
        "key": "redis/config/username",
        "flags": 0,
        "value": "U2lsZW5jZQ=="
    },
    {
        "key": "redis/zk/",
        "flags": 0,
        "value": ""
    },
    {
        "key": "redis/zk/password",
        "flags": 0,
        "value": "NDU0NjU="
    },
    {
        "key": "redis/zk/username",
        "flags": 0,
        "value": "ZGZhZHNm"
    }
]

Consul的KV存储系统是一种类似zk的树形节点结构,用来存储相关key/value键值对信息的,我们可以使用KV存储系统来实现上面提到的配置中心,将统一的配置信息保存在KV存储系统里面,方便各个实例获取并使用同一配置。而且更改配置后各个服务可以自动拉取最新配置,不需要重启服务。

Consul Java 客户端使用

  1. maven pom依赖增加,版本可自由更换
<dependency>
    <groupId>com.orbitz.consul</groupId>
    <artifactId>consul-client</artifactId>
    <version>0.12.3</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

  1. Consul 基本工具类,根据需要相应扩展
package com.coocaa.consul.consul.demo;


import com.google.common.base.Optional;
import com.google.common.net.HostAndPort;
import com.orbitz.consul.*;
import com.orbitz.consul.model.agent.ImmutableRegCheck;
import com.orbitz.consul.model.agent.ImmutableRegistration;
import com.orbitz.consul.model.health.ServiceHealth;


import java.net.MalformedURLException;
import java.net.URI;
import java.util.List;


public class ConsulUtil {


    private static Consul consul = Consul.builder().withHostAndPort(HostAndPort.fromString("127.0.0.1:8500")).build();


    /**


     * 服务注册


     */
    public static void serviceRegister() {
        AgentClient agent = consul.agentClient();


        try {
            /**


             * 注意该注册接口:
             * 需要提供一个健康检查的服务URL,以及每隔多长时间访问一下该服务(这里是3s)


             */
            agent.register(8080, URI.create("http://localhost:8080/health").toURL(), 3, "tomcat", "tomcatID", "dev");


        } catch (MalformedURLException e) {
            e.printStackTrace();
        }


    }


    /**


     * 服务获取


     *


     * @param serviceName


     */
    public static void findHealthyService(String serviceName) {
        HealthClient healthClient = consul.healthClient();
        List<ServiceHealth> serviceHealthList = healthClient.getHealthyServiceInstances(serviceName).getResponse();
        serviceHealthList.forEach((response) -> {
            System.out.println(response);
        });
    }


    /**


     * 存储KV


     */
    public static void storeKV(String key, String value) {
        KeyValueClient kvClient = consul.keyValueClient();
        kvClient.putValue(key, value);
    }


    /**


     * 根据key获取value


     */
    public static String getKV(String key) {
        KeyValueClient kvClient = consul.keyValueClient();
        Optional<String> value = kvClient.getValueAsString(key);
        if (value.isPresent()) {
            return value.get();
        }
        return "";
    }


    /**


     * 找出一致性的节点(应该是同一个DC中的所有server节点)


     */
    public static List<String> findRaftPeers() {
        StatusClient statusClient = consul.statusClient();
        return statusClient.getPeers();
    }


    /**


     * 获取leader


     */
    public static String findRaftLeader() {
        StatusClient statusClient = consul.statusClient();
        return statusClient.getLeader();
    }


    public static void main(String[] args) {
        AgentClient agentClient = consul.agentClient();
        agentClient.deregister("tomcatID");
    }
}

temp.json 和 ConsulUtil.java 文件以及上传到 GitHub 资源库,回复【源码仓库】获取代码地址。

3.通过上面的基本工具类可以实现服务的注册和KV数据的获取与存储功能

Consul集群搭建

  1. 三台主机Consul下载安装,我这里没有物理主机,所以通过三台虚拟机来实现。虚拟机IP分192.168.231.145,192.168.231.146,192.168.231.147
  1. 将145和146两台主机作为Server模式启动,147作为Client模式启动,Server和Client只是针对Consul集群来说的,跟服务没有任何关系!
  1. Server模式启动145,节点名称设为n1,数据中心统一用dc1
[root@centos145 consul]# ./consul agent -server -bootstrap-expect 2 -data-dir /tmp/consul -node=n1 -bind=192.168.231.145 -datacenter=dc1
bootstrap_expect = 2: A cluster with 2 servers will provide no failure tolerance. See https://www.consul.io/docs/internals/consensus.html#deployment-table
bootstrap_expect > 0: expecting 2 servers
==> Starting Consul agent...
==> Consul agent running!
           Version: 'v1.0.1'
           Node ID: '6cc74ff7-7026-cbaa-5451-61f02114cd25'
         Node name: 'n1'
        Datacenter: 'dc1' (Segment: '<all>')
            Server: true (Bootstrap: false)
       Client Addr: [127.0.0.1] (HTTP: 8500, HTTPS: -1, DNS: 8600)
      Cluster Addr: 192.168.231.145 (LAN: 8301, WAN: 8302)
           Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false


==> Log data will now stream in as it occurs:


    2017/12/06 23:26:21 [INFO] raft: Initial configuration (index=0): []
    2017/12/06 23:26:21 [INFO] serf: EventMemberJoin: n1.dc1 192.168.231.145
    2017/12/06 23:26:21 [INFO] serf: EventMemberJoin: n1 192.168.231.145
    2017/12/06 23:26:21 [INFO] agent: Started DNS server 127.0.0.1:8600 (udp)
    2017/12/06 23:26:21 [INFO] raft: Node at 192.168.231.145:8300 [Follower] entering Follower state (Leader: "")
    2017/12/06 23:26:21 [INFO] consul: Adding LAN server n1 (Addr: tcp/192.168.231.145:8300) (DC: dc1)
    2017/12/06 23:26:21 [INFO] consul: Handled member-join event for server "n1.dc1" in area "wan"
    2017/12/06 23:26:21 [INFO] agent: Started DNS server 127.0.0.1:8600 (tcp)
    2017/12/06 23:26:21 [INFO] agent: Started HTTP server on 127.0.0.1:8500 (tcp)
    2017/12/06 23:26:21 [INFO] agent: started state syncer
    2017/12/06 23:26:28 [ERR] agent: failed to sync remote state: No cluster leader
    2017/12/06 23:26:30 [WARN] raft: no known peers, aborting election
    2017/12/06 23:26:49 [ERR] agent: Coordinate update error: No cluster leader
    2017/12/06 23:26:54 [ERR] agent: failed to sync remote state: No cluster leader
    2017/12/06 23:27:24 [ERR] agent: Coordinate update error: No cluster leader
    2017/12/06 23:27:27 [ERR] agent: failed to sync remote state: No cluster leader
    2017/12/06 23:27:56 [ERR] agent: Coordinate update error: No cluster leader
    2017/12/06 23:28:02 [ERR] agent: failed to sync remote state: No cluster leader
    2017/12/06 23:28:27 [ERR] agent: failed to sync remote state: No cluster leader
    2017/12/06 23:28:33 [ERR] agent: Coordinate update error: No cluster leader

目前只启动了145,所以还没有集群

4.Server模式启动146,节点名称用n2,并在n2上启用了web-ui管理页面功能

[root@centos146 consul]# ./consul agent -server -bootstrap-expect 2 -data-dir /tmp/consul -node=n2 -bind=192.168.231.146 -datacenter=dc1 -ui
bootstrap_expect = 2: A cluster with 2 servers will provide no failure tolerance. See https://www.consul.io/docs/internals/consensus.html#deployment-table
bootstrap_expect > 0: expecting 2 servers
==> Starting Consul agent...
==> Consul agent running!
           Version: 'v1.0.1'
           Node ID: 'eb083280-c403-668f-e193-60805c7c856a'
         Node name: 'n2'
        Datacenter: 'dc1' (Segment: '<all>')
            Server: true (Bootstrap: false)
       Client Addr: [127.0.0.1] (HTTP: 8500, HTTPS: -1, DNS: 8600)
      Cluster Addr: 192.168.231.146 (LAN: 8301, WAN: 8302)
           Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false


==> Log data will now stream in as it occurs:


    2017/12/06 23:28:30 [INFO] raft: Initial configuration (index=0): []
    2017/12/06 23:28:30 [INFO] serf: EventMemberJoin: n2.dc1 192.168.231.146
    2017/12/06 23:28:31 [INFO] serf: EventMemberJoin: n2 192.168.231.146
    2017/12/06 23:28:31 [INFO] raft: Node at 192.168.231.146:8300 [Follower] entering Follower state (Leader: "")
    2017/12/06 23:28:31 [INFO] consul: Adding LAN server n2 (Addr: tcp/192.168.231.146:8300) (DC: dc1)
    2017/12/06 23:28:31 [INFO] consul: Handled member-join event for server "n2.dc1" in area "wan"
    2017/12/06 23:28:31 [INFO] agent: Started DNS server 127.0.0.1:8600 (tcp)
    2017/12/06 23:28:31 [INFO] agent: Started DNS server 127.0.0.1:8600 (udp)
    2017/12/06 23:28:31 [INFO] agent: Started HTTP server on 127.0.0.1:8500 (tcp)
    2017/12/06 23:28:31 [INFO] agent: started state syncer
    2017/12/06 23:28:38 [ERR] agent: failed to sync remote state: No cluster leader
    2017/12/06 23:28:39 [WARN] raft: no known peers, aborting election
    2017/12/06 23:28:57 [ERR] agent: Coordinate update error: No cluster leader
    2017/12/06 23:29:11 [ERR] agent: failed to sync remote state: No cluster leader
    2017/12/06 23:29:30 [ERR] agent: Coordinate update error: No cluster leader
    2017/12/06 23:29:38 [ERR] agent: failed to sync remote state: No cluster leader
    2017/12/06 23:29:57 [ERR] agent: Coordinate update error: No cluster leader

同样没有集群发现,此时n1和n2都启动起来,但是互相并不知道集群的存在!

5.将n1节点加入n2

   [silence@centos145 consul]$ ./consul join 192.168.231.146

此时n1和n2都打印发现了集群的日志信息

6.这个时候n1和n2两个节点已经是一个集群里面的Server模式的节点了

7.Client模式启动147

[root@centos147 consul]# ./consul agent -data-dir /tmp/consul -node=n3 -bind=192.168.231.147 -datacenter=dc1
==> Starting Consul agent...
==> Consul agent running!
           Version: 'v1.0.1'
           Node ID: 'be7132c3-643e-e5a2-9c34-cad99063a30e'
         Node name: 'n3'
        Datacenter: 'dc1' (Segment: '')
            Server: false (Bootstrap: false)
       Client Addr: [127.0.0.1] (HTTP: 8500, HTTPS: -1, DNS: 8600)
      Cluster Addr: 192.168.231.147 (LAN: 8301, WAN: 8302)
           Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false


==> Log data will now stream in as it occurs:


    2017/12/06 23:36:46 [INFO] serf: EventMemberJoin: n3 192.168.231.147
    2017/12/06 23:36:46 [INFO] agent: Started DNS server 127.0.0.1:8600 (udp)
    2017/12/06 23:36:46 [INFO] agent: Started DNS server 127.0.0.1:8600 (tcp)
    2017/12/06 23:36:46 [INFO] agent: Started HTTP server on 127.0.0.1:8500 (tcp)
    2017/12/06 23:36:46 [INFO] agent: started state syncer
    2017/12/06 23:36:46 [WARN] manager: No servers available
    2017/12/06 23:36:46 [ERR] agent: failed to sync remote state: No known Consul servers
    2017/12/06 23:37:08 [WARN] manager: No servers available
    2017/12/06 23:37:08 [ERR] agent: failed to sync remote state: No known Consul servers
    2017/12/06 23:37:36 [WARN] manager: No servers available
    2017/12/06 23:37:36 [ERR] agent: failed to sync remote state: No known Consul servers
    2017/12/06 23:38:02 [WARN] manager: No servers available
    2017/12/06 23:38:02 [ERR] agent: failed to sync remote state: No known Consul servers
    2017/12/06 23:38:22 [WARN] manager: No servers available
    2017/12/06 23:38:22 [ERR] agent: failed to sync remote state: No known Consul servers

8.在n3上面将节点n3加入集群

   [silence@centos147 consul]$ ./consul join 192.168.231.145

9.再次查看集群节点信息

10.此时三个节点的Consul集群搭建成功了!其实n1和n2是Server模式启动,n3是Client模式启动。

11.关于Consul的Server模式和Client模式主要的区别是这样的,一个Consul集群通过启动的参数-bootstrap-expect来控制这个集群的Server节点个数,Server模式的节点会维护集群的状态,并且如果某个Server节点退出了集群,则会触发Leader重新选举机制,在会剩余的Server模式节点中重新选举一个Leader;而Client模式的节点的加入和退出很自由。

12.在n2中启动web-ui

以上就是W3Cschool编程狮关于分布式服务注册发现与统一配置管理之 Consul的相关介绍了,希望对大家有所帮助。

JavaScript引擎V8 8.5 带来的三个实用新特性

thbcm阅读(145)

文章转载自公众号:code秘密花园

JavaScript 引擎 V8 发布了 8.5 版本,接下来带大家了解一下8.5版本的三个实用特性。

Promise.any

Promise 新增了一个 any 方法,它接收一个 Promise 数组,当数组中某一个 Promisefulfilled 后,它返回的 Promise 就会被返回。

const promises = [
  fetch('/endpoint-a').then(() => 'a'),
  fetch('/endpoint-b').then(() => 'b'),
  fetch('/endpoint-c').then(() => 'c'),
];
try {
  const first = await Promise.any(promises);
  // 任何一个 Promise 为 fulfilled 状态
  console.log(first);
  // →   'b'
} catch (error) {
  // 所有 Promise 都被 rejected 了
  console.assert(error instanceof AggregateError);
  // reject 结果数组
  console.log(error.errors);
}

如果所有输入的 Promise 都被拒绝,那么 Promise.any 将会返回一个 AggregateError 类型的异常,这个对象的 errors 属性包含所有 Promise被拒绝的属性。

注意不要和 Promise.race 方法弄混, race 方法是数组中有任何一个 Promise 被解决或拒绝就会返回,而 any 方法是必须有一个被解决,如果所有都被拒绝是会抛出异常的。

String.prototype.replaceAll

String.prototype.replaceAll 提供了一种简便的方式来替换子字符串的所有匹配,而不再需要创建全局 RegExp

看下面的例子,以前你要把 queryString 中所有的 + 替换掉,需要创建一个全局的正则:

const queryString = 'q=query+string+parameters';


queryString.replace(/\+/g, ' ');

现在你只需要使用 replaceAll 方法:

queryString.replaceAll('+', ' ');

逻辑赋值运算符

逻辑赋值运算符是一种新的复合赋值运算符,他可以把逻辑操作 &&,||或?? 与赋操作结合成一条命令。

x &&= y;
// 等同于 x && (x = y)
x ||= y;
// 等同于 x || (x = y)
x ??= y;
// 等同于 x ?? (x = y)

以上就是W3Cschool编程狮关于JavaScript引擎V8 8.5 带来的三个实用新特性的相关介绍了,希望对大家有所帮助。

CSS背景知识,一篇文章带你由浅到深全面理解

thbcm阅读(177)

文章转载自公众号:前端人

本文从简单到深入讲解了 CSS 背景相关的知识,请一定要耐心看完,切勿浮躁!

CSS background是最常用的 CSS 属性之一。但是,在所有开发人员中使用多种背景仍然不是很了解。我将重点关注使用多种背景的潜力,并充分利用 CSS 的全部功能。

在本文中,我将background-image详细解释该属性,并提供一个可视化的解释器,说明如何堆叠多个背景及其实际好处。当然,将有一些视觉示例,您可以大饱口福!

如果您不了解CSS background属性,我更喜欢看Mozilla开发人员网络(MDN)的这份参考资料,它解释了CSS的background工作原理。

介绍

CSS background属性是以下属性的简写。

背景剪辑,背景颜色,背景图像,背景来源,背景位置,背景重复,背景大小和背景附件。

在这篇文章中,我将重点放在background-imagebackground-positionbackground-size。你准备好了吗?让我们潜入吧!

考虑以下示例。

.element {
 background: url(cool.jpg) top left/50px 50px no-repeat;
}

背景图像被定位在左上角的元件的拐角,具有尺寸的50px * 50px。了解并记住位置和大小的顺序很重要。

在上图中,background-position后面跟着background-size。反之亦然!换句话说,以下CSS 无效:

.element {
 /* Warning: Invalid CSS */
 background: url(cool.jpg) 50px 50px/top left no-repeat;
}

背景位置

相对于background-origin属性设置的定位层定位元素。我喜欢的灵活性background-position。它具有多种定位元素的方式:

  • 关键字值(top,right,bottom,left,center
  • 百分比值。例如:50%
  • 长度值。例如:20px 2.5rem
  • 边缘偏移值。例如:top 20px left 10px

坐标系从左上角开始,默认值为0% 0%

值得一提的是,该值top left与相同left top。浏览器足够聪明,可以确定其中一个用于x轴,哪个用于y轴。

.element {
 background: url(cool.jpg) top left/50px 50px no-repeat;
 /* is the same as */
 background: url(cool.jpg) left top/50px 50px no-repeat;
}

背景尺寸

该属性的名称不言而喻。大小由widthheight组成。对于该background-size属性,第一个是width,第二个是height

不必使用两个值。您可以使用一个值,它将用于宽度和高度。

免责声明:值得一提的是CSS规范指出:“如果仅给出一个值,则第二个将被认为是自动的”。但是,该功能尚未在浏览器中实现,并且将来会更改。感谢Ilya Streltsyn的来信。

现在,我已经了解了CSS如何background工作的基础知识,让我们探索如何使用多个背景。

多种背景

背景属性可以有一个或更多的层,用逗号分隔。如果多个背景的大小相同,则其中一个将覆盖另一个背景。

.element {
 background: url(cool.jpg) top left/50px 50px no-repeat,
 url(cool.jpg) center/50px 50px no-repeat;
}

在上图中,我们有两个背景层。他们每个人的位置都不同。这是多种背景的基本用法。让我们探索一个更高级的示例。

堆叠顺序

当放置多个背景时,其中一个背景占据其父级的全部宽度和高度时,将进行堆叠顺序。决定何时将背景堆叠在彼此之上可能会有些混乱。考虑以下示例。

.hero {
  min-height: 350px;
  background: url('table.jpg') center/cover no-repeat,
    url('konafa.svg') center/50px no-repeat; 
}

我们有一个盘子和一张桌子。您希望上面的CSS产生什么结果?哪个先到?盘子还是桌子?

答案是桌子。在CSS中,第一个背景可以堆叠在第二个背景上,第二个背景可以堆叠在第三个背景上,依此类推。通过替换背景顺序,结果将达到预期。

.hero {
  background: url('konafa.svg') center/50px no-repeat,
  url('table.jpg') center/cover no-repeat;
}

纯色

假设您要绘制两个具有CSS背景的矩形,您将如何做?幸运的是,使用CSS渐变非常容易。当linear-gradient具有相同颜色的停止时,结果将为纯色。而已!

.hero {
 background: linear-gradient(#3c88Ec, #3c88Ec)
}

通过探索CSS渐变的一个非常非常有用的用例,我们可以进一步采用这种方式。请继续关注用例部分!

用例和范例

叠加

通常,您可能需要在英雄部分的顶部放置一个覆盖层,以便使文本易于阅读。通过堆叠两个背景可以轻松完成此操作。

.hero {
 background: linear-gradient(rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.15)),
    url("landscape.jpg") center/cover;
}

更好的是,我们可以使用与上面相同的方法将淡色应用于元素。考虑以下:

.hero {
 background: linear-gradient(135deg, rgba(177, 234, 77, 0.25), rgba(69, 149, 34, 0.25),
    url("landscape.jpg") center/cover;
}

使用CSS绘图 使用CSS渐变绘制的可能性是无限的。您可以使用linear-gradientradial-gradient更多。对于此基本示例,我将解释如何绘制笔记本电脑。

让我们拆卸笔记本电脑,看看我们需要使用什么梯度。

请注意,当分解便携式计算机项目时,现在更容易考虑如何将其实现为多个CSS背景。如果你注意到了,我创建了两个圈充当圆角的身体,因为有做与圆边的梯度没有直接的方法。

接下来是图纸。首先是将每个渐变定义为CSS变量及其大小。我喜欢使用CSS变量,因为它可以减少代码的复杂性,并使代码更简洁,更易于阅读。完成后,我将转到放置它们的步骤。

:root {
  --case: linear-gradient(#222, #222);
  --case-size: 152px 103px;


  --display: linear-gradient(#fff, #fff);
  --display-size: 137px 87px;


  --reflection: linear-gradient(205deg, #fff, rgba(255, 255, 255, 0));
  --reflection-size: 78px 78px;


  --body: linear-gradient(#888, #888);
  --body-size: 182px 9px;


  --circle: radial-gradient(9px 9px at 5px 5.5px, #888 50%, transparent 50%);
  --circle-size: 10px 10px;
}

现在我们定义了渐变及其大小,下一步是放置它们。考虑下图,以获得更好的视觉解释。

显示反射

如前所述,应该首先定义需要在顶部的元素。在我们的情况下,显示反射应该是第一个渐变。

液晶显示器

显示屏位于 x轴的中心,并且位于6pxy轴的位置。

塑料盒

外壳位于显示屏下方,并且位于x轴的中心,并且位于0pxy轴。

身体

那是图形中最有趣的组件。首先,主体是一个矩形,每个侧面(左侧和右侧)有两个圆圈。

最后结果

:root {
  --case: linear-gradient(#222, #222);
  --case-size: 152px 103px;
  --case-pos: center 0;


  --display: linear-gradient(#fff, #fff);
  --display-size: 137px 87px;
  --display-pos: center 6px;


  --reflection: linear-gradient(205deg, #fff, rgba(255, 255, 255, 0));
  --reflection-size: 78px 78px;
  --reflection-pos: top right;


  --body: linear-gradient(#888, #888);
  --body-size: 182px 9px;
  --body-pos: center bottom;


  --circle: radial-gradient(9px 9px at 5px 5.5px, #888 50%, transparent 50%);
  --circle-size: 10px 10px;
  --circle-left-pos: left bottom;
  --circle-right-pos: right bottom;
}


.cool {
  width: 190px;
  height: 112px;


  background-image: var(--reflection), var(--display), var(--case), var(--circle), var(--circle), var(--body);


  background-size: var(--reflection-size), var(--display-size), var(--case-size), var(--circle-size), var(--circle-size), var(--body-size);


  background-position: var(--reflection-pos), var(--display-pos), var(--case-pos), var(--circle-left-pos), var(--circle-right-pos), var(--body-pos);


  background-repeat: no-repeat;


  /*outline: solid 1px;*/
}

混合多个背景

可以混合使用多个背景时会令人兴奋。我可以解释的最简单的用例是使图像去饱和。考虑一下您background-imageCSS中有一个,并且想要将其转换为黑色和白色的需求。

.hero {
  background: linear-gradient(#000, #000),
  url("landscape.jpg") center/cover;
  background-blend-mode: color;
}

翻译原文:ishadeed.com/article/css-multiple-backgrounds

以上就是W3Cschool编程狮关于CSS背景知识,一篇文章带你由浅到深全面理解的相关介绍了,希望对大家有所帮助。

从”算法”角度回答—输入URL后页面呈现发生了什么?

thbcm阅读(172)

文章转载自公众号:前端三元同学

老生常谈的问题,可以另辟蹊径来回答!本文不讲解 URL 输入后发生的网络部分,只从算法角度讲解拿到页面后的解析部分,主要分为以下几个步骤:

  • 构建 DOM
  • 样式计算
  • 生成布局树(Layout Tree)

构建 DOM 树

由于浏览器无法直接理解 HTML字符串 ,因此将这一系列的字节流转换为一种有意义并且方便操作的数据结构,这种数据结构就是DOM树DOM树本质上是一个以document为根节点的多叉树。

那通过什么样的方式来进行解析呢?

HTML文法的本质

首先,我们应该清楚把握一点: HTML 的文法并不是上下文无关文法

这里,有必要讨论一下什么是上下文无关文法

在计算机科学的编译原理学科中,有非常明确的定义:

若一个形式文法G = (N, Σ, P, S) 的产生式规则都取如下的形式:V-&w,则叫上下文无关语法。其中 V∈N ,w∈(N∪Σ)* 。

其中把 G = (N, Σ, P, S) 中各个参量的意义解释一下:

  1. N 是非终结符(顾名思义,就是说最后一个符号不是它, 下面同理)集合。
  2. Σ 是终结符集合。
  3. P 是开始符,它必须属于 N ,也就是非终结符。
  4. S 就是不同的产生式的集合。如 S -> aSb 等等。

通俗一点讲,上下文无关的文法就是说这个文法中所有产生式的左边都是一个非终结符。

看到这里,如果还有一点懵圈,我举个例子你就明白了。

比如:

A -> B

这个文法中,每个产生式左边都会有一个非终结符,这就是上下文无关的文法。在这种情况下,xBy一定是可以规约出xAy的。

我们下面看看看一个反例:

aA -> B
Aa -> B

这种情况就是不是上下文无关的文法,当遇到B的时候,我们不知道到底能不能规约出A,取决于左边或者右边是否有a存在,也就是说和上下文有关。

关于它为什么是非上下文无关文法,首先需要让大家注意的是,规范的 HTML 语法,是符合上下文无关文法的,能够体现它非上下文无关的是不标准的语法。在此我仅举一个反例即可证明。

比如解析器扫描到form标签的时候,上下文无关文法的处理方式是直接创建对应 form 的 DOM 对象,而真实的 HTML5 场景中却不是这样,解析器会查看 form 的上下文,如果这个 form 标签的父标签也是 form, 那么直接跳过当前的 form 标签,否则才创建 DOM 对象。

常规的编程语言都是上下文无关的,而HTML却相反,也正是它非上下文无关的特性,决定了HTML Parser并不能使用常规编程语言的解析器来完成,需要另辟蹊径。

解析算法

HTML5 规范详细地介绍了解析算法。这个算法分为两个阶段:

  1. 标记化。
  2. 建树。

对应的两个过程就是词法分析语法分析

标记化算法

这个算法输入为 HTML文本 ,输出为HTML标记,也成为标记生成器。其中运用有限自动状态机来完成。即在当当前状态下,接收一个或多个字符,就会更新到下一个状态。

<html>
  <body>
    Hello sanyuan
  </body>
</html>

通过一个简单的例子来演示一下标记化的过程。

遇到<, 状态为标记打开

接收[a-z]的字符,会进入标记名称状态

这个状态一直保持,直到遇到>,表示标记名称记录完成,这时候变为数据状态

接下来遇到body标签做同样的处理。

这个时候htmlbody的标记都记录好了。

现在来到<body>中的>,进入数据状态,之后保持这样状态接收后面的字符hello sanyuan

接着接收 </body> 中的<,回到标记打开, 接收下一个/后,这时候会创建一个end tag的token。

随后进入标记名称状态, 遇到>回到数据状态

接着以同样的样式处理 </body>。

建树算法

之前提到过,DOM 树是一个以document为根节点的多叉树。因此解析器首先会创建一个document对象。标记生成器会把每个标记的信息发送给建树器建树器接收到相应的标记时,会创建对应的 DOM 对象。创建这个DOM对象后会做两件事情:

  1. DOM对象加入 DOM 树中。
  2. 将对应标记压入存放开放(与闭合标签意思对应)元素的栈中。

还是拿下面这个例子说:

<html>
  <body>
    Hello sanyuan
  </body>
</html>

首先,状态为初始化状态

接收到标记生成器传来的html标签,这时候状态变为before html状态。同时创建一个HTMLHtmlElement的 DOM 元素, 将其加到document根对象上,并进行压栈操作。

接着状态自动变为before head, 此时从标记生成器那边传来body,表示并没有head, 这时候建树器会自动创建一个HTMLHeadElement并将其加入到DOM树中。

现在进入到in head状态, 然后直接跳到after head

现在标记生成器传来了body标记,创建HTMLBodyElement, 插入到DOM树中,同时压入开放标记栈。

接着状态变为in body,然后来接收后面一系列的字符: Hello sanyuan。接收到第一个字符的时候,会创建一个Text节点并把字符插入其中,然后把Text节点插入到 DOM 树中body元素的下面。随着不断接收后面的字符,这些字符会附在Text节点上。

现在,标记生成器传过来一个body的结束标记,进入到after body状态。

标记生成器最后传过来一个html的结束标记, 进入到after after body的状态,表示解析过程到此结束。

容错机制

讲到HTML5规范,就不得不说它强大的宽容策略, 容错能力非常强,虽然大家褒贬不一,不过我想作为一名资深的前端工程师,有必要知道HTML Parser在容错方面做了哪些事情。

接下来是 WebKit 中一些经典的容错示例,发现有其他的也欢迎来补充。

1.使用</br>而不是<br>

if (t->isCloseTag(brTag) && m_document->inCompatMode()) {
  reportError(MalformedBRError);
  t->beginTag = true;
}

全部换为<br>的形式。

2.表格离散

<table>
  <table>
    <tr><td>inner table</td></tr>
  </table>
  <tr><td>outer table</td></tr>
</table>

WebKit会自动转换为:

<table>
    <tr><td>outer table</td></tr>
</table>
<table>
    <tr><td>inner table</td></tr>
</table>

3.表单元素嵌套

这时候直接忽略里面的form

样式计算

关于CSS样式,它的来源一般是三种:

  1. link标签引用
  2. style标签中的样式
  3. 元素的内嵌style属性

格式化样式表

首先,浏览器是无法直接识别 CSS 样式文本的,因此渲染引擎接收到 CSS 文本之后第一件事情就是将其转化为一个结构化的对象,即styleSheets。

这个格式化的过程过于复杂,而且对于不同的浏览器会有不同的优化策略,这里就不展开了。

在浏览器控制台能够通过document.styleSheets来查看这个最终的结构。当然,这个结构包含了以上三种CSS来源,为后面的样式操作提供了基础。

标准化样式属性

有一些 CSS 样式的数值并不容易被渲染引擎所理解,因此需要在计算样式之前将它们标准化,如em->px,red->#ff0000,bold->700等等。

计算每个节点的具体样式

样式已经被格式化标准化,接下来就可以计算每个节点的具体样式信息了。

其实计算的方式也并不复杂,主要就是两个规则: 继承层叠

每个子节点都会默认继承父节点的样式属性,如果父节点中没有找到,就会采用浏览器默认样式,也叫UserAgent样式。这就是继承规则,非常容易理解。

然后是层叠规则,CSS 最大的特点在于它的层叠性,也就是最终的样式取决于各个属性共同作用的效果,甚至有很多诡异的层叠现象,看过《CSS世界》的同学应该对此深有体会,具体的层叠规则属于深入 CSS 语言的范畴,这里就不过多介绍了。

不过值得注意的是,在计算完样式之后,所有的样式值会被挂在到window.computedStyle当中,也就是可以通过JS来获取计算后的样式,非常方便。

生成布局树

现在已经生成了DOM树DOM样式,接下来要做的就是通过浏览器的布局系统确定元素的位置,也就是要生成一棵布局树(Layout Tree)。

布局树生成的大致工作如下:

  1. 遍历生成的 DOM 树节点,并把他们添加到布局树中
  2. 计算布局树节点的坐标位置。

值得注意的是,这棵布局树值包含可见元素,对于 head标签和设置了display: none的元素,将不会被放入其中。

有人说首先会生成Render Tree,也就是渲染树,其实这还是 16 年之前的事情,现在 Chrome 团队已经做了大量的重构,已经没有生成Render Tree的过程了。而布局树的信息已经非常完善,完全拥有Render Tree的功能。

之所以不讲布局的细节,是因为它过于复杂,一一介绍会显得文章过于臃肿,不过大部分情况下我们只需要知道它所做的工作是什么即可,如果想深入其中的原理,知道它是如何来做的,我强烈推荐你去读一读人人FED团队的文章从Chrome源码看浏览器如何layout布局。

总结

梳理一下这一节的主要脉络:

以上就是W3Cschool编程狮关于从”算法”角度回答—输入URL后页面呈现发生了什么?的相关介绍了,希望对大家有所帮助。

后端接收long类型参数时精度丢失,如何处理这个天坑

thbcm阅读(153)

文章转载自公众号:Java极客技术 作者:鸭血粉丝

最近几天一直在改造工程,采用雪花算法生成主键ID,突然踩到一个天坑,前端 JavaScript 在取 Long 型参数时,参数值有点不太对!

一、问题描述

最近在改造内部管理系统的时候, 发现了一个巨坑,就是前端 JavaScript 在获取后端 Long 型参数时,出现精度丢失!

起初,用 postman 模拟接口请求,都很正常,但是用浏览器请求的时候,就出现问题了!

  • 问题复现
@RequestMapping("/queryUser")
public List<User> queryUser(){
    List<User> resultList = new ArrayList<>();
    User user = new User();
    //赋予一个long型用户ID
    user.setId(123456789012345678L);
    resultList.add(user);
    return resultList;
}

打开浏览器,请求接口,结果如下!

用 postman 模拟接口请求,结果如下!

刚开始的时候,还真没发现这个坑,结果当进行测试的时候,才发现前端传给后端的 ID ,与数据库中存的 ID 不一致,才发现 JavaScript 还有这个天坑!

由于 JavaScriptNumber 类型的自身原因,并不能完全表示 Long 型的数字,在 Long 长度大于17位时会出现精度丢失的问题。

当我们把上面的用户 ID 改成 19 位的时候,我们再来看看浏览器请求返回的结果。

//设置用户ID,位数为19位
user.setId(1234567890123456789l);

浏览器请求结果!

当返回的结果超过17位的时候,后面的全部变成0!

二、解决办法

遇到这种情况,应该怎么办呢?

  • 第一种办法:在后台把 long型改为String类型,但是代价有点大,只要涉及到的地方都需要改
  • 第二种办法:使用工具进行转化把 long 型改为String类型,这种方法可以实现全局转化(推荐)
  • 第三种办法:前端进行处理(目前没有很好的办法,不推荐)

因为项目涉及到的代码非常多,所以不可能把 long 型改为 String 类型,而且使用 Long 类型的方法非常多,改起来风险非常大,所以不推荐使用!

最理想的方法,就是使用aop代理拦截所有的方法,对返回参数进行统一处理,使用工具进行转化,过程如下!

2.1、Jackson 工具序列化对象

我们可以使用Jackson工具包来实现对象序列化。

  • 首先在 maven 中添加必须的依赖
<!--jackson依赖-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.9.8</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.9.8</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.8</version>
</dependency>

  • 编写一个转化工具类JsonUtil
public class JsonUtil {


    private static final Logger log = LoggerFactory.getLogger(JsonUtil.class);


    private static ObjectMapper objectMapper = new ObjectMapper();
    private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";


    static {
        // 对象的所有字段全部列入
        objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
        // 取消默认转换timestamps形式
        objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        // 忽略空bean转json的错误
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        //设置为东八区
        objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
        // 统一日期格式
        objectMapper.setDateFormat(new SimpleDateFormat(DATE_FORMAT));
        // 反序列化时,忽略在json字符串中存在, 但在java对象中不存在对应属性的情况, 防止错误
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        // 序列换成json时,将所有的long变成string
        objectMapper.registerModule(new SimpleModule().addSerializer(Long.class, ToStringSerializer.instance).addSerializer(Long.TYPE, ToStringSerializer.instance));
    }


    /**
     * 对象序列化成json字符串
     * @param obj
     * @param <T>
     * @return
     */
    public static <T> String objToStr(T obj) {
        if (null == obj) {
            return null;
        }


        try {
            return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj);
        } catch (Exception e) {
            log.warn("objToStr error: ", e);
            return null;
        }
    }


    /**
     * json字符串反序列化成对象
     * @param str
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T strToObj(String str, Class<T> clazz) {
        if (StringUtils.isBlank(str) || null == clazz) {
            return null;
        }


        try {
            return clazz.equals(String.class) ? (T) str : objectMapper.readValue(str, clazz);
        } catch (Exception e) {
            log.warn("strToObj error: ", e);
            return null;
        }
    }


    /**
     * json字符串反序列化成对象(数组)
     * @param str
     * @param typeReference
     * @param <T>
     * @return
     */
    public static <T> T strToObj(String str, TypeReference<T> typeReference) {
        if (StringUtils.isBlank(str) || null == typeReference) {
            return null;
        }


        try {
            return (T) (typeReference.getType().equals(String.class) ? str : objectMapper.readValue(str, typeReference));
        } catch (Exception e) {
            log.warn("strToObj error", e);
            return null;
        }
    }
}

  • 紧接着,编写一个实体类Person,用于测试
@Data
public class Person implements Serializable {


    private static final long serialVersionUID = 1L;


    private Integer id;


    //Long型参数
    private Long uid;
    private String name;
    private String address;
    private String mobile;


    private Date createTime;
}

  • 最后,我们编写一个测试类测试一下效果
public static void main(String[] args) {
    Person person = new Person();
    person.setId(1);
    person.setUid(1111L);
    person.setName("hello");
    person.setAddress("");
    System.out.println(JsonUtil.objToStr(person));
}

输出结果如下:

其中最关键一行代码,是注册了这个转换类,从而实现将所有的 long 变成 string

// 序列换成json时,将所有的long变成string
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);

如果想对某个日期进行格式化,可以全局设置。

//全局统一日期格式
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

也可以,单独对某个属性进行设置,例如对createTime属性格式化为yyyy-MM-dd,只需要加上如下注解即可。

@JsonFormat(pattern="yyyy-MM-dd", timezone="GMT+8")
private Date createTime;

工具转化类写好之后,就非常简单了,只需要对 aop 拦截的方法返回的参数,进行序列化就可以自动实现将所有的 long变成 string

2.2、SpringMVC 配置

如果是 SpringMVC 项目,操作也很简单。

  • 自定义一个实现类,继承自ObjectMapper
package com.example.util;


/**
 * 继承ObjectMapper
 */
public class CustomObjectMapper extends ObjectMapper {


    public CustomObjectMapper() {
        super();
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        registerModule(simpleModule);
    }
}

  • 在 SpringMVC 的配置文件中加上如下配置
<mvc:annotation-driven >
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <constructor-arg index="0" value="utf-8" />
            <property name="supportedMediaTypes">
                <list>
                    <value>application/json;charset=UTF-8</value>
                    <value>text/plain;charset=UTF-8</value>
                </list>
            </property>
        </bean>          
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="objectMapper">
                <bean class="com.example.util.CustomObjectMapper">
                    <property name="dateFormat">
                        <-对日期进行统一转化->
                        <bean class="java.text.SimpleDateFormat">
                            <constructor-arg type="java.lang.String" value="yyyy-MM-dd HH:mm:ss" />
                        </bean>
                    </property>
                </bean>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

2.3、SpringBoot 配置

如果是 SpringBoot 项目,操作也类似。

  • 编写一个WebConfig配置类,并实现自WebMvcConfigurer,重写configureMessageConverters方法
/**
 * WebMvc配置
 */
@Configuration
@Slf4j
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {


    /**
     *添加消息转化类
     * @param list
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> list) {
        MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = jsonConverter.getObjectMapper();
        //序列换成json时,将所有的long变成string
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        objectMapper.registerModule(simpleModule);
        list.add(jsonConverter);
    }
}

三、总结

在实际的项目开发中,很多服务都是纯微服务开发,没有用到SpringMVC,在这种情况下,使用JsonUtil工具类实现对象序列化,可能是一个非常好的选择。

如果有理解不对的地方,欢迎网友批评指出!

以上就是W3Cschool编程狮关于后端接收long类型参数时精度丢失,如何处理这个天坑的相关介绍了,希望对大家有所帮助。

CSS布局–CC中不可忽视的部分

thbcm阅读(151)

文章转载自公众号:小丑的小屋

前言

CSS 布局是一个前端必备的技能, HTML 如果说是结构之美, CSS 就是视觉之美可以给用户不一样的体验的效果和视觉冲击。如果一个大方美观的布局,用户第一眼看到会很舒服,不仅提高了用户的视觉效果也提高了用户的体验效果。随着现在设备种类的增多,各种大小不一,奇形怪状的设备使得前端开发的压力不断增大,越来越多的UI框架层出不群,我们就会忽略了最基本的 CSS, 下面总结了一些经常用到的 CSS 布局,一起学习和进步。

单列布局

单列布局是最常见也是最常用的布局方式,一般设置一个最大或者最小的宽度和居中就可以实现了。

<div id="app"></div>
#app{
    border:1px solid red;
    margin:0 auto;
    height: 500px;
    width: 100%;
    max-width: 960px;
    min-width: 500px;
    box-sizing: border-box;
}

两列布局

两列布局将页面分割成左右宽度不一样的两部分,一般情况下都是左边宽度固定,右边宽度撑满剩余宽度,常用于api网站后台管理系统。这种布局当屏幕缩小的时候,或者宽度不够的时候,右边撑满的部分就变成了单列布局,左边的部分改为垂直方向的显示或者隐藏。

<div id="app">
    <div id="main">main</div>
    <div id="side">side</div>
</div>
#main{
    background:orange;
    flex:1;
}
#side{
    width:200px;
    background:greenyellow;
}
#main,#side{
    height: 200px;
}
#app{
    display: flex;
    flex-direction:row-reverse;
    flex-wrap: wrap;
}
@media only screen and (max-width:1000px){
    #app{
        flex-direction: row;
    }
    #main{
        flex:100%;
    }
}

三列布局

三列布局是将页面分为左中右水平方向的三个部分比如github。也有可能是水平方向上的两列布局中右边撑满的部分嵌套一个两列布局组成。

  • 圣杯布局
<div id="app">
    <div id="main">main</div>
    <div id="side">side1</div>
    <div id="foot">side2</div>
</div>
*{
    margin:0px;
    padding:0px;
}
#app{
    padding:0px 200px 0px 300px;
}
#app::after{
    content: "";
    display: block;
    clear: both;
}
#main,#side,#foot{
    float: left;
}
#main{
    background:orange;
    width:100%;
}
#side{
    background:greenyellow;
    width: 300px;
    position: relative;
    margin-left:-100%;
    left: -300px;
}
#foot{
    background:palevioletred;
    width: 200px;
    position: relative;
    margin-left:-200px;
    right:-200px;


}
@media only screen and (max-width:1000px){
    #app{
        padding:0px;
    }
   #side{
      margin-left:0px;
      left:0px;
   }
   #foot{
    margin-left:0px;
    right:0px;
 }
}

  • 双飞翼布局
<div id="app">
    <main>
        <div id="container">main</div>
    </main>
    <header>header</header>
    <footer>footer</footer>
</div>
*{
    margin:0px;
    padding:0px;
}
#app::after{
    content: "";
    display: block;
    clear: both;
}
main,header,footer{
    float: left;
}
main{
    background:orange;
    width: 100%;
}
header{
    background:greenyellow;
    width: 300px;
    margin-left: -100%;
}
footer{
    background:palevioletred;
    width: 200px;
    margin-left: -200px;
}
#container{
    margin: 0px 200px 0px 300px;
}
@media only screen and (max-width:1000px){
   header{
      margin-left:0px;
   }
   footer{
    margin-left:0px;
 }
}

  • 圣杯和双飞翼的区别见下图

还有一种布局是垂直方向上的分为上中下三个部分,上和下两部分固定高度,中间部分高度不定,当页面高度小于浏览器高度时,下部分应该固定在浏览器的底部,但是当页面的高度超出浏览器高度的时候,下部分应该随中间部分被撑开,显示在页面的最底部即sticky footer

  • 传统布局的方法

headermain放到一个容器中,让容器的高度撑满整个屏幕,下面预留出一定的高度,给footer设置外边距使它插入到容器底部实现功能。

<div id="app">
    <header></header>
    <main>
        <div id="container"></div>
    </main>
</div>
<footer></footer>
*{
    margin:0px;
    padding:0px;
}
#app{
    box-sizing: border-box;
    min-height: 100vh;
    padding-bottom: 100px;
}


header,footer{
    height: 100px;
    background: burlywood;
}
main{
    background: cadetblue;
}
footer{
    margin-top:-100px ;
}

  • flex布局

父级app使用flex布局高度撑满容器,让子容器垂直排列,headerfooter设置固定高度,让main撑满剩余的容器

<div id="app">
    <header></header>
    <main>
        <div id="container"></div>
    </main>
    <footer></footer>
</div>
*{
    margin:0px;
    padding:0px;
}
#app{
    display: flex;
    flex-direction: column;
    height: 100%;
}


header,footer{
    background: burlywood;
    height: 100px;
}
main{
    background: cadetblue;
    flex: 1;
}

这里有的面试会问到flex:1的原理是什么?**flex**是flex-grow, flex-shrinkflex-basis的缩写

flex-grow:1; //放大比例
flex-shrink:1;  //  缩小比例
flex-basis;0%;  // 分配剩余空间

  • grid布局

grid布局就一句话,设置第一行和最后一行高为固定值,中间部分由浏览器自己决定长度

<div id="app">
    <header></header>
    <main>
        <div id="container"></div>
    </main>
    <footer></footer>
</div>
*{
    margin:0px;
    padding:0px;
}
#app{
    display: grid;
    height: 100%;
    grid-template-rows:100px auto 100px;
}


header,footer{
    background: burlywood;
}
main{
    background: cadetblue;
}

总结

经典永远都是经典,框架再多选择再多,基础永远是我们需要掌握的,所谓万变不离其中,有了这些基础知识我们只需要灵活的运用即可

  • 1.首先将我们需要布局的大框架写出来,即页面容器的主层次,一般主容器放到次容器的上面。
  • 2.将布局容器进行水平排列。
  • 3.设置容器的宽度。
  • 4.消除布局的副作用,比如浮动造成的高度塌陷。
  • 5.为了适配不同机型,通过媒体查询进行优化。

以上就是W3Cschool编程狮关于CSS布局–CC中不可忽视的部分的相关介绍了,希望对大家有所帮助。

Java是值传递还是引用传递?有图为证

thbcm阅读(144)

文章转载自公众号:Java中文社群 作者:磊哥

开篇先来曝答案,在 Java 语言中,本质只有值传递,而无引用传递,解释和证明详见正文。

说到值传递和引用传递我们不得不提到两个概念:值类型和引用类型。

1.值类型

通俗意义上来说,所谓的值类型指的就是 Java 中的 8 大基础数据类型:

  • 整数型:byte、int、short、long
  • 浮点型:float、double
  • 字符类型:char
  • 布尔类型:boolean

从 JVM 层面来讲:所谓的值类型指的是在赋值时,直接在栈中(Java 虚拟机栈)生成值的类型,如下图所示:

2.引用类型

引用类型是指除值类型之外的数据类型,比如:

  • 接口
  • 数组
  • 字符串
  • 包装类(Integer、Double…)

从 JVM 的层面来讲,所谓的引用类型是指,在初始化时将引用生成栈上,而值生成在堆上的这些数据类型,如下图所示:

3.值传递

值传递(Pass By Value)指的是方法传参时,传递的是原内容的副本,因此对副本进行如何修改都不会影响原内容。

实现代码如下:

public class PassTest {
    public static void main(String[] args) {
        int age = 18;
        System.out.println("调用方法前:" + age);
        intTest(age);
        System.out.println("调用方法后:" + age);
    }


    private static void intTest(int age) {
        age = 30;
        System.out.println("方法中修改为:" + age);
    }
}

程序的执行结果为:

调用方法前:18

方法中修改为:30

调用方法后:18

从上述结果可以看出,在方法中修改参数并未影响原内容,我们把这种传参方式称之为值传递。

4.引用传递

引用传递(Pass By Reference)指的是方法传参时,传递的是参数本身,因此对参数进行任意修改都会影响原内容。

模拟“引用传递”的实现代码如下:

public class PassTest {
    public static void main(String[] args) {
        char[] name = {'编程', '师'};
        System.out.println("调用方法前:" + new String(name));
        paramTest(name);
        System.out.println("调用方法后:" + new String(name));
    }
    private static void paramTest(char[] n) {
        n[1] = '狮';
        System.out.println("方法中修改为:" + new String(n));
    }
}

程序的执行结果为:

调用方法前:编程师

方法中修改为:编程狮

调用方法后:编程狮

从上述的结果可以看出在 paramTest 方法中修改了参数之后,在 main 方法中再打印参数时,发现参数的值也跟着发生了改变,那么似乎我们可以得出结论,Java 中貌似也有“引用传递”,然而实事并如此,我们接着看。

5.真假“引用传递”

我们给上面的代码添加一行,如下所示:

public class PassByValue {
    public static void main(String[] args) {
        char[] name = {'磊', '哥'};
        System.out.println("调用方法前:" + new String(name));
        paramTest(name);
        System.out.println("调用方法后:" + new String(name));
    }
    private static void paramTest(char[] n) {
        n = new char[2]; // 添加此行代码
        n[1] = '神';
        System.out.println("方法中修改为:" + new String(n));
    }
}

程序的执行结果为:

调用方法前:磊哥

方法中修改为:神

调用方法后:磊哥

从上述结果可以看出,当我们在 paramTest 方法中添加 new char[]之后,“引用传递”就突然变值传递了?为什么?

这是因为,在 Java 语言中本质上只有值传递,也就说 Java 的传参只会传递它的副本,并不会传递参数本身。

前面那个带引号的“引用传递”其实只是传递了它的引用副本,如下图所示:

PS:《Java虚拟机规范》中对 Java 堆的描述是:“所有的对象实例以及数组都应当在堆上分配”。

所以我们在调用 new char[] 之后,可以看出 n 对象有了新地址,而原内容并未被修改,如果按照引用传递的思路来看的话,不管执行任何方式的修改都会改变原内容,因此我们可以更加确认 Java 语言中只有值传递,如下图所示:

总结

通过本文的内容,我们可以得出:在 Java 语言中只有值传递,方法传参时只会传递副本信息而非原内容。我们还知道了基础数据类型会直接生成到栈上,而对象或数组则会在栈和堆上都生成信息,并将栈上生成的引用,直接指向堆中生成的数据,如下图所示:

以上就是W3Cschool编程狮关于Java是值传递还是引用传递?有图为证的相关介绍了,希望对大家有所帮助。

一个CSS属性让你页面渲染速度提升数倍

thbcm阅读(164)

文章转载自公众号:code秘密花园

浏览器在接收到服务端返回的 HTML 之后,需要把这段数据渲染成用户看到的页面,在开始渲染第一个元素之前可能还需要经过很多步骤。这个过程会适用于整个页面,包括当前不可见的内容。

所以在首屏渲染时,是有很大一部分时间花费在用户不可见的内容上,实际上这部分数据我们没必要在首屏就把它们渲染出来。

Chrome 85 新推出的 content-visibility: auto 就是为了解决上面的问题,它可以告诉浏览器暂时跳过该元素的布局和渲染工作,直到这个元素滚动到当前视口,从而可以加快整个页面的初始渲染,并且缩短用户和页面可交互需要花费的时间。

CSS Containment

content-visibility 依赖于 CSS Containement,目前只能在 Chromium 85 中支持 content-visibility 属性,但是大多数浏览器都支持 CSS Containement

CSS Containment 是一种规范,它的主要目的就是在页面渲染的过程中通过忽略文档中的某些子树来提高页面的渲染性能。

上面我们提到,在首屏渲染时,是有很大一部分时间花费在用户不可见的内容上,实际上这部分数据我们没必要在首屏就把它们渲染出来。作为开发者,肯定很清楚当前修改的元素是否独立或者影响其他元素。因此如果开发者能把这个信息通过 CSS 告诉浏览器,那么浏览器就不需要再去考虑其他元素了。CSS Containment 模块中提供的 contain 属性就为我们提供了这种能力。

css contain 一共有四个属性:

  • size: 在计算该元素盒子大小的时候会忽略其子元素
  • layout: 元素的内部布局不受外部影响,同时该元素以及其内容也不会影响到上级
  • style: 声明同时会影响这个元素和其子孙元素的属性,都在这个元素的包含范围内
  • paint: 声明这个元素的子孙节点不会在它边缘外显示。如果一个元素在视窗外或因其他原因导致不可见,则同样保证它的子孙节点不会被显示。

content-visibility

css contain 是一个很棒的属性,但是我们不太容易判断要选取它的哪个值。现在,我们可以直接应用 content-visibility 来达到这样的效果,但是配置却简单的多。

content-visibility 属性也有多个值,但是 auto 这个值是一个可以立刻提高性能的属性:

.my-class {
  content-visibility: auto;
}

如果一个元素具有 auto 这个属性,如果当前元素没有出现在屏幕上,浏览器就不会渲染它和它的子元素;当元素接近用户的可视区域时,浏览器就会解除限制,并开始进行渲染;这可以保证元素会及时被用户看到。

看看上面的例子,在将 content-visibility: auto 设置到一些小的模块之后,渲染时间直接从232ms提升到了30ms,性能提升了7倍。

contain-intrinsic-size

如果我们给可视区域外的元素增加了 content-visibility: auto 属性,那么当滚动条滚动到这个元素之后,如果这个元素很大有一定高度,那么滚动条的长度就会发生变化,页面可能就会发生抖动的现象。

解决这个问题,可以先使用 contains-intrinsic-size 提前给元素设置自然高度的大小,比如 1000px ,这样元素提前占用了一些高度,就不会发生抖动现象。

以上就是W3Cschool编程狮关于一个CSS属性让你页面渲染速度提升数倍的相关介绍了,希望对大家有所帮助。

联系我们