共计 12467 个字符,预计需要花费 32 分钟才能阅读完成。
为什么 0.1 + 0.2 != 0.3,请详述理由
因为 JS 采纳 IEEE 754 双精度版本(64 位),并且只有采纳 IEEE 754 的语言都有该问题。
咱们都晓得计算机示意十进制是采纳二进制示意的,所以 0.1
在二进制示意为
// (0011) 示意循环
0.1 = 2^-4 * 1.10011(0011)
那么如何失去这个二进制的呢,咱们能够来演算下
小数算二进制和整数不同。乘法计算时,只计算小数位,整数位用作每一位的二进制,并且失去的第一位为最高位。所以咱们得出 0.1 = 2^-4 * 1.10011(0011)
,那么 0.2
的演算也根本如上所示,只须要去掉第一步乘法,所以得出 0.2 = 2^-3 * 1.10011(0011)
。
回来持续说 IEEE 754 双精度。六十四位中符号位占一位,整数位占十一位,其余五十二位都为小数位。因为 0.1
和 0.2
都是有限循环的二进制了,所以在小数位开端处须要判断是否进位(就和十进制的四舍五入一样)。
所以 2^-4 * 1.10011...001
进位后就变成了 2^-4 * 1.10011(0011 * 12 次)010
。那么把这两个二进制加起来会得出 2^-2 * 1.0011(0011 * 11 次)0100
, 这个值算成十进制就是 0.30000000000000004
上面说一下原生解决办法,如下代码所示
parseFloat((0.1 + 0.2).toFixed(10))
介绍一下 babel 原理
babel
的编译过程分为三个阶段:parsing、transforming、generating,以 ES6 编译为 ES5 作为例子:
ES6
代码输出;babylon
进行解析失去 AST;plugin
用babel-traverse
对AST
树进行遍历编译,失去新的AST
树;- 用
babel-generator
通过AST
树生成ES5
代码。
说一下 slice splice split 的区别?
// slice(start,[end])
// slice(start,[end])办法:该办法是对数组进行局部截取,该办法返回一个新数组
// 参数 start 是截取的开始数组索引,end 参数等于你要取的最初一个字符的地位值加上 1(可选)。// 蕴含了源函数从 start 到 end 所指定的元素,然而不包含 end 元素,比方 a.slice(0,3);// 如果呈现正数就把正数与长度相加后再划分。// slice 中的正数的绝对值若大于数组长度就会显示所有数组
// 若参数只有一个,并且参数大于 length,则为空。// 如果完结地位小于起始地位,则返回空数组
// 返回的个数是 end-start 的个数
// 不会扭转原数组
var arr = [1,2,3,4,5,6]
/*console.log(arr.slice(3))//[4,5,6] 从下标为 0 的到 3,截取 3 之后的数 console.log(arr.slice(0,3))//[1,2,3] 从下标为 0 的中央截取到下标为 3 之前的数 console.log(arr.slice(0,-2))//[1,2,3,4]console.log(arr.slice(-4,4))//[3,4]console.log(arr.slice(-7))//[1,2,3,4,5,6]console.log(arr.slice(-3,-3))// []console.log(arr.slice(8))//[]*/
// 集体总结:slice 的参数如果是负数就从左往右数,如果是正数的话就从右往左边数,// 截取的数组与数的方向统一,如果是 2 个参数则截取的是数的交加,没有交加则返回空数组
// ps:slice 也能够切割字符串,用法和数组一样,但要留神空格也算字符
// splice(start,deletecount,item)
// start:起始地位
// deletecount:删除位数
// item:替换的 item
// 返回值为被删除的字符串
// 如果有额定的参数,那么 item 会插入到被移除元素的地位上。// splice: 移除,splice 办法从 array 中移除一个或多个数组,并用新的 item 替换它们。// 举一个简略的例子
var a=['a','b','c'];
var b=a.splice(1,1,'e','f');
console.log(a) //['a', 'e', 'f', 'c']
console.log(b) //['b']
var a = [1, 2, 3, 4, 5, 6];
//console.log("被删除的为:",a.splice(1, 1, 8, 9)); // 被删除的为:2
// console.log("a 数组元素:",a); //1,8,9,3,4,5,6
// console.log("被删除的为:", a.splice(0, 2)); // 被删除的为:1,2
// console.log("a 数组元素:", a) //3,4,5,6
console.log("被删除的为:", a.splice(1, 0, 2, 2)) // 插入 第二个数为 0,示意删除 0 个
console.log("a 数组元素:", a) //1,2,2,2,3,4,5,6
// split(字符串)
// string.split(separator,limit):split 办法把这个 string 宰割成片段来创立一个字符串数组。// 可选参数 limit 能够限度被宰割的片段数量。// separator 参数能够是一个字符串或一个正则表达式。// 如果 separator 是一个空字符,会返回一个单字符的数组,不会扭转原数组。var a="0123456";
var b=a.split("",3);
console.log(b);//b=["0","1","2"]
// 留神:String.split() 执行的操作与 Array.join 执行的操作是相同的。
函数防抖
触发高频事件 N 秒后只会执行一次,如果 N 秒内事件再次触发,则会从新计时。
简略版:函数外部反对应用 this 和 event 对象;
function debounce(func, wait) {
var timeout;
return function () {
var context = this;
var args = arguments;
clearTimeout(timeout)
timeout = setTimeout(function(){func.apply(context, args)
}, wait);
}
}
应用:
var node = document.getElementById('layout')
function getUserAction(e) {console.log(this, e) // 别离打印:node 这个节点 和 MouseEvent
node.innerHTML = count++;
};
node.onmousemove = debounce(getUserAction, 1000)
最终版:除了反对 this 和 event 外,还反对以下性能:
- 反对立刻执行;
- 函数可能有返回值;
- 反对勾销性能;
function debounce(func, wait, immediate) {
var timeout, result;
var debounced = function () {
var context = this;
var args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
// 如果曾经执行过,不再执行
var callNow = !timeout;
timeout = setTimeout(function(){timeout = null;}, wait)
if (callNow) result = func.apply(context, args)
} else {timeout = setTimeout(function(){func.apply(context, args)
}, wait);
}
return result;
};
debounced.cancel = function() {clearTimeout(timeout);
timeout = null;
};
return debounced;
}
应用:
var setUseAction = debounce(getUserAction, 10000, true);
// 应用防抖
node.onmousemove = setUseAction
// 勾销防抖
setUseAction.cancel()
事件是如何实现的?
基于公布订阅模式,就是在浏览器加载的时候会读取事件相干的代码,然而只有理论等到具体的事件触发的时候才会执行。
比方点击按钮,这是个事件(Event),而负责处理事件的代码段通常被称为事件处理程序(Event Handler),也就是「启动对话框的显示」这个动作。
在 Web 端,咱们常见的就是 DOM 事件:
- DOM0 级事件,间接在 html 元素上绑定 on-event,比方 onclick,勾销的话,dom.onclick = null,同一个事件只能有一个处理程序,前面的会笼罩后面的。
- DOM2 级事件,通过 addEventListener 注册事件,通过 removeEventListener 来删除事件,一个事件能够有多个事件处理程序,按程序执行,捕捉事件和冒泡事件
- DOM3 级事件,减少了事件类型,比方 UI 事件,焦点事件,鼠标事件
代码输入后果
var a=3;
function c(){alert(a);
}
(function(){
var a=4;
c();})();
js 中变量的作用域链与定义时的环境无关,与执行时无关。执行环境只会扭转 this、传递的参数、全局变量等
参考 前端进阶面试题具体解答
代码输入后果
var myObject = {
foo: "bar",
func: function() {
var self = this;
console.log(this.foo);
console.log(self.foo);
(function() {console.log(this.foo);
console.log(self.foo);
}());
}
};
myObject.func();
输入后果:bar bar undefined bar
解析:
- 首先 func 是由 myObject 调用的,this 指向 myObject。又因为 var self = this; 所以 self 指向 myObject。
- 这个立刻执行匿名函数表达式是由 window 调用的,this 指向 window。立刻执行匿名函数的作用域处于 myObject.func 的作用域中,在这个作用域找不到 self 变量,沿着作用域链向上查找 self 变量,找到了指向 myObject 对象的 self。
Nginx 的概念及其工作原理
Nginx 是一款轻量级的 Web 服务器,也能够用于反向代理、负载平衡和 HTTP 缓存等。Nginx 应用异步事件驱动的办法来解决申请,是一款面向性能设计的 HTTP 服务器。
传统的 Web 服务器如 Apache 是 process-based 模型的,而 Nginx 是基于 event-driven 模型的。正是这个次要的区别带给了 Nginx 在性能上的劣势。
Nginx 架构的最顶层是一个 master process,这个 master process 用于产生其余的 worker process,这一点和 Apache 十分像,然而 Nginx 的 worker process 能够同时解决大量的 HTTP 申请,而每个 Apache process 只能解决一个。
对 HTML 语义化的了解
语义化是指依据内容的结构化(内容语义化),抉择适合的标签(代码语义化)。艰深来讲就是用正确的标签做正确的事件。
语义化的长处如下:
- 对机器敌对,带有语义的文字表现力丰盛,更适宜搜索引擎的爬虫爬取无效信息,有利于 SEO。除此之外,语义类还反对读屏软件,依据文章能够主动生成目录;
- 对开发者敌对,应用语义类标签加强了可读性,构造更加清晰,开发者能清晰的看出网页的构造,便于团队的开发与保护。
常见的语义化标签:
<header></header> 头部
<nav></nav> 导航栏
<section></section> 区块(有语义化的 div)<main></main> 次要区域
<article></article> 次要内容
<aside></aside> 侧边栏
<footer></footer> 底部
什么是文档的预解析?
Webkit 和 Firefox 都做了这个优化,当执行 JavaScript 脚本时,另一个线程解析剩下的文档,并加载前面须要通过网络加载的资源。这种形式能够使资源并行加载从而使整体速度更快。须要留神的是,预解析并不扭转 DOM 树,它将这个工作留给主解析过程,本人只解析内部资源的援用,比方内部脚本、样式表及图片。
call apply bind
题目形容: 手写 call apply bind 实现
实现代码如下:
Function.prototype.myCall = function (context, ...args) {if (!context || context === null) {context = window;}
// 发明惟一的 key 值 作为咱们结构的 context 外部办法名
let fn = Symbol();
context[fn] = this; //this 指向调用 call 的函数
// 执行函数并返回后果 相当于把本身作为传入的 context 的办法进行调用了
return context[fn](...args);
};
// apply 原理统一 只是第二个参数是传入的数组
Function.prototype.myApply = function (context, args) {if (!context || context === null) {context = window;}
// 发明惟一的 key 值 作为咱们结构的 context 外部办法名
let fn = Symbol();
context[fn] = this;
// 执行函数并返回后果
return context[fn](...args);
};
//bind 实现要简单一点 因为他思考的状况比拟多 还要波及到参数合并(相似函数柯里化)
Function.prototype.myBind = function (context, ...args) {if (!context || context === null) {context = window;}
// 发明惟一的 key 值 作为咱们结构的 context 外部办法名
let fn = Symbol();
context[fn] = this;
let _this = this;
// bind 状况要简单一点
const result = function (...innerArgs) {
// 第一种状况 : 若是将 bind 绑定之后的函数当作构造函数,通过 new 操作符应用,则不绑定传入的 this,而是将 this 指向实例化进去的对象
// 此时因为 new 操作符作用 this 指向 result 实例对象 而 result 又继承自传入的_this 依据原型链常识可得出以下论断
// this.__proto__ === result.prototype //this instanceof result =>true
// this.__proto__.__proto__ === result.prototype.__proto__ === _this.prototype; //this instanceof _this =>true
if (this instanceof _this === true) {
// 此时 this 指向指向 result 的实例 这时候不须要扭转 this 指向
this[fn] = _this;
this[fn](...[...args, ...innerArgs]); // 这里应用 es6 的办法让 bind 反对参数合并
} else {
// 如果只是作为一般函数调用 那就很简略了 间接扭转 this 指向为传入的 context
context[fn](...[...args, ...innerArgs]);
}
};
// 如果绑定的是构造函数 那么须要继承构造函数原型属性和办法
// 实现继承的形式: 应用 Object.create
result.prototype = Object.create(this.prototype);
return result;
};
// 用法如下
// function Person(name, age) {// console.log(name); //'我是参数传进来的 name'
// console.log(age); //'我是参数传进来的 age'
// console.log(this); // 构造函数 this 指向实例对象
// }
// // 构造函数原型的办法
// Person.prototype.say = function() {// console.log(123);
// }
// let obj = {
// objName: '我是 obj 传进来的 name',
// objAge: '我是 obj 传进来的 age'
// }
// // 一般函数
// function normalFun(name, age) {// console.log(name); //'我是参数传进来的 name'
// console.log(age); //'我是参数传进来的 age'
// console.log(this); // 一般函数 this 指向绑定 bind 的第一个参数 也就是例子中的 obj
// console.log(this.objName); //'我是 obj 传进来的 name'
// console.log(this.objAge); //'我是 obj 传进来的 age'
// }
// 先测试作为结构函数调用
// let bindFun = Person.myBind(obj, '我是参数传进来的 name')
// let a = new bindFun('我是参数传进来的 age')
// a.say() //123
// 再测试作为一般函数调用
// let bindFun = normalFun.myBind(obj, '我是参数传进来的 name')
// bindFun('我是参数传进来的 age')
前端贮存的⽅式有哪些?
- cookies:在 HTML5 规范前本地贮存的次要⽅式,长处是兼容性好,申请头⾃带 cookie ⽅便,毛病是⼤⼩只有 4k,⾃动申请头加⼊ cookie 节约流量,每个 domain 限度 20 个 cookie,使⽤起来麻烦,须要⾃⾏封装;
- localStorage:HTML5 加⼊的以键值对 (Key-Value) 为规范的⽅式,长处是操作⽅便,永久性贮存(除⾮⼿动删除),⼤⼩为 5M,兼容 IE8+;
- sessionStorage:与 localStorage 根本相似,区别是 sessionStorage 当⻚⾯敞开后会被清理,⽽且与 cookie、localStorage 不同,他不能在所有同源窗⼝中共享,是会话级别的贮存⽅式;
- Web SQL:2010 年被 W3C 废除的本地数据库数据存储⽅案,然而支流浏览器(⽕狐除外)都曾经有了相干的实现,web sql 相似于 SQLite,是真正意义上的关系型数据库,⽤ sql 进⾏操作,当咱们⽤ JavaScript 时要进⾏转换,较为繁琐;
- IndexedDB:是被正式纳⼊ HTML5 规范的数据库贮存⽅案,它是 NoSQL 数据库,⽤键值对进⾏贮存,能够进⾏疾速读取操作,⾮常适宜 web 场景,同时⽤ JavaScript 进⾏操作会⾮常便。
动静布局求解硬币找零问题
题目形容: 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算能够凑成总金额所需的起码的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1
示例 1:输出: coins = [1, 2, 5], amount = 11
输入: 3
解释: 11 = 5 + 5 + 1
示例 2:输出: coins = [2], amount = 3
输入: -1
实现代码如下:
const coinChange = function (coins, amount) {
// 用于保留每个指标总额对应的最小硬币个数
const f = [];
// 提前定义已知状况
f[0] = 0;
// 遍历 [1, amount] 这个区间的硬币总额
for (let i = 1; i <= amount; i++) {
// 求的是最小值,因而咱们预设为无穷大,确保它肯定会被更小的数更新
f[i] = Infinity;
// 循环遍历每个可用硬币的面额
for (let j = 0; j < coins.length; j++) {
// 若硬币面额小于指标总额,则问题成立
if (i - coins[j] >= 0) {
// 状态转移方程
f[i] = Math.min(f[i], f[i - coins[j]] + 1);
}
}
}
// 若指标总额对应的解为无穷大,则意味着没有一个符合条件的硬币总数来更新它,本题无解,返回 -1
if (f[amount] === Infinity) {return -1;}
// 若有解,间接返回解的内容
return f[amount];
};
代码输入后果
function runAsync (x) {const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
return p
}
function runReject (x) {const p = new Promise((res, rej) => setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x))
return p
}
Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)])
.then(res => console.log(res))
.catch(err => console.log(err))
输入后果如下:
// 1s 后输入
1
3
// 2s 后输入
2
Error: 2
// 4s 后输入
4
能够看到。catch 捕捉到了第一个谬误,在这道题目中最先的谬误就是 runReject(2)
的后果。如果一组异步操作中有一个异样都不会进入 .then()
的第一个回调函数参数中。会被 .then()
的第二个回调函数捕捉。
计算属性和 watch 有什么区别? 以及它们的使用场景?
// 区别
computed 计算属性:依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值产生扭转,下一次获取 computed 的值时才会从新计算 computed 的值。watch 侦听器:更多的是察看的作用, 无缓存性, 相似与某些数据的监听回调, 每当监听的数据变动时都会执行回调进行后续操作
// 使用场景
当须要进行数值计算, 并且依赖与其它数据时, 应该应用 computed, 因为能够利用 computed 的缓存属性, 防止每次获取值时都要从新计算。当须要在数据变动时执行异步或开销较大的操作时, 应该应用 watch, 应用 watch 选项容许执行异步操作(拜访一个 API), 限度执行该操作的频率,并在失去最终后果前,设置中间状态。这些都是计算属性无奈做到的。
代码输入后果
var a = 10;
var obt = {
a: 20,
fn: function(){
var a = 30;
console.log(this.a)
}
}
obt.fn(); // 20
obt.fn.call(); // 10
(obt.fn)(); // 20
输入后果:20 10 20
解析:
- obt.fn(),fn 是由 obt 调用的,所以其 this 指向 obt 对象,会打印出 20;
- obt.fn.call(),这里 call 的参数啥都没写,就示意 null,咱们晓得如果 call 的参数为 undefined 或 null,那么 this 就会指向全局对象 this,所以会打印出 10;
- (obt.fn)(),这里给表达式加了括号,而括号的作用是扭转表达式的运算程序,而在这里加与不加括号并无影响;相当于 obt.fn(),所以会打印出 20;
AJAX
实现:利用 XMLHttpRequest
// get
const getJSON = (url) => {return new Promise((resolve, reject) => {let xhr = new XMLHttpRequest();
// open 办法用于指定 HTTP 申请的参数: method, url, async(是否异步,默认 true)xhr.open("GET", url, false);
xhr.setRequestHeader('Content-Type', 'application/json');
// onreadystatechange 属性指向一个监听函数。// readystatechange 事件产生时(实例的 readyState 属性变动),就会执行这个属性。xhr.onreadystatechange = function(){
// 4 示意服务器返回的数据曾经齐全接管,或者本次接管曾经失败
if(xhr.readyState !== 4) return;
// 申请胜利,基本上只有 2xx 和 304 的状态码,示意服务器返回是失常状态
if(xhr.status === 200 || xhr.status === 304) {
// responseText 属性返回从服务器接管到的字符串
resolve(xhr.responseText);
}
// 申请失败
else {reject(new Error(xhr.responseText));
}
}
xhr.send();});
}
// post
const postJSON = (url, data) => {return new Promise((resolve, reject) => {let xhr = new XMLHttpRequest();
xhr.open("POST", url);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function(){if(xhr.readyState !== 4) return;
if(xhr.status === 200 || xhr.status === 304) {resolve(xhr.responseText);
}
else {reject(new Error(xhr.responseText));
}
}
xhr.send(data);
});
}
如何进攻 XSS 攻打?
能够看到 XSS 危害如此之大,那么在开发网站时就要做好进攻措施,具体措施如下:
- 能够从浏览器的执行来进行预防,一种是应用纯前端的形式,不必服务器端拼接后返回(不应用服务端渲染)。另一种是对须要插入到 HTML 中的代码做好充沛的本义。对于 DOM 型的攻打,次要是前端脚本的不牢靠而造成的,对于数据获取渲染和字符串拼接的时候应该对可能呈现的恶意代码状况进行判断。
- 应用 CSP,CSP 的实质是建设一个白名单,通知浏览器哪些内部资源能够加载和执行,从而避免恶意代码的注入攻打。
- CSP 指的是内容安全策略,它的实质是建设一个白名单,通知浏览器哪些内部资源能够加载和执行。咱们只须要配置规定,如何拦挡由浏览器本人来实现。
- 通常有两种形式来开启 CSP,一种是设置 HTTP 首部中的 Content-Security-Policy,一种是设置 meta 标签的形式
- 对一些敏感信息进行爱护,比方 cookie 应用 http-only,使得脚本无奈获取。也能够应用验证码,防止脚本伪装成用户执行一些操作。
display 的属性值及其作用
属性值 | 作用 |
---|---|
none | 元素不显示,并且会从文档流中移除。 |
block | 块类型。默认宽度为父元素宽度,可设置宽高,换行显示。 |
inline | 行内元素类型。默认宽度为内容宽度,不可设置宽高,同行显示。 |
inline-block | 默认宽度为内容宽度,能够设置宽高,同行显示。 |
list-item | 像块类型元素一样显示,并增加款式列表标记。 |
table | 此元素会作为块级表格来显示。 |
inherit | 规定应该从父元素继承 display 属性的值。 |
TLS/SSL 的工作原理
TLS/SSL全称 平安传输层协定(Transport Layer Security), 是介于 TCP 和 HTTP 之间的一层平安协定,不影响原有的 TCP 协定和 HTTP 协定,所以应用 HTTPS 基本上不须要对 HTTP 页面进行太多的革新。
TLS/SSL 的性能实现次要依赖三类根本算法:散列函数 hash、对称加密 、 非对称加密。这三类算法的作用如下:
- 基于散列函数验证信息的完整性
- 对称加密算法采纳协商的秘钥对数据加密
- 非对称加密实现身份认证和秘钥协商
(1)散列函数 hash
常见的散列函数有 MD5、SHA1、SHA256。该函数的特点是单向不可逆,对输出数据十分敏感,输入的长度固定,任何数据的批改都会扭转散列函数的后果,能够用于避免信息篡改并验证数据的完整性。
特点: 在信息传输过程中,散列函数不能三都实现信息防篡改,因为传输是明文传输,中间人能够批改信息后从新计算信息的摘要,所以须要对传输的信息和信息摘要进行加密。
(2)对称加密
对称加密的办法是,单方应用同一个秘钥对数据进行加密和解密。然而对称加密的存在一个问题,就是如何保障秘钥传输的安全性,因为秘钥还是会通过网络传输的,一旦秘钥被其他人获取到,那么整个加密过程就毫无作用了。这就要用到非对称加密的办法。
常见的对称加密算法有 AES-CBC、DES、3DES、AES-GCM 等。雷同的秘钥能够用于信息的加密和解密。把握秘钥能力获取信息,避免信息窃听,其通信形式是一对一。
特点: 对称加密的劣势就是信息传输应用一对一,须要共享雷同的明码,明码的平安是保障信息安全的根底,服务器和 N 个客户端通信,须要维持 N 个明码记录且不能批改明码。
(3)非对称加密
非对称加密的办法是,咱们领有两个秘钥,一个是公钥,一个是私钥。公钥是公开的,私钥是窃密的。用私钥加密的数据,只有对应的公钥能力解密,用公钥加密的数据,只有对应的私钥能力解密。咱们能够将公钥颁布进来,任何想和咱们通信的客户,都能够应用咱们提供的公钥对数据进行加密,这样咱们就能够应用私钥进行解密,这样就能保证数据的平安了。然而非对称加密有一个毛病就是加密的过程很慢,因而如果每次通信都应用非对称加密的形式的话,反而会造成等待时间过长的问题。
常见的非对称加密算法有 RSA、ECC、DH 等。秘钥成对呈现,个别称为公钥(公开)和私钥(窃密)。公钥加密的信息只有私钥能够解开,私钥加密的信息只能公钥解开,因而把握公钥的不同客户端之间不能互相解密信息,只能和服务器进行加密通信,服务器能够实现一对多的的通信,客户端也能够用来验证把握私钥的服务器的身份。
特点: 非对称加密的特点就是信息一对多,服务器只须要维持一个私钥就能够和多个客户端进行通信,但服务器收回的信息可能被所有的客户端解密,且该算法的计算简单,加密的速度慢。
综合上述算法特点,TLS/SSL 的工作形式就是客户端应用非对称加密与服务器进行通信,实现身份的验证并协商对称加密应用的秘钥。对称加密算法采纳协商秘钥对信息以及信息摘要进行加密通信,不同节点之间采纳的对称秘钥不同,从而保障信息只能通信单方获取。这样就解决了两个办法各自存在的问题。