关于javascript:前端打工人的面试总结

7次阅读

共计 11595 个字符,预计需要花费 29 分钟才能阅读完成。

v-model 语法糖是怎么实现的

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <!-- v-model 只是语法糖而已 -->
    <!-- v-model 在外部为不同的输出元素应用不同的 property 并抛出不同的事件 -->
    <!-- text 和 textarea 元素应用 value property 和 input 事件 -->
    <!-- checkbox 和 radio 应用 checked  property 和 change 事件 -->
    <!-- select 字段将 value 作为 prop 并将 change 作为事件 -->
    <!-- 留神:对于须要应用输入法 (如中文、日文、韩文等) 的语言,你将会发现 v -model 不会再输入法    组合文字过程中失去更新 -->
    <!-- 再一般标签上 -->
    <input v-model="sth" />  // 这一行等于下一行
    <input v-bind:value="sth" v-on:input="sth = $event.target.value" />
    <!-- 再组件上 -->
    <currency-input v-model="price"></currentcy-input>
        <!-- 上行代码是上行的语法糖         <currency-input :value="price" @input="price = arguments[0]"></currency-input>        --> 
        <!-- 子组件定义 -->
        Vue.component('currency-input', {
         template: `
          <span>
           <input
            ref="input"
            :value="value"
            @input="$emit('input', $event.target.value)"
           >
          </span>
         `,
         props: ['value'],
        })   
</body>
</html>

数字证书是什么?

当初的办法也不肯定是平安的,因为没有方法确定失去的公钥就肯定是平安的公钥。可能存在一个中间人,截取了对方发给咱们的公钥,而后将他本人的公钥发送给咱们,当咱们应用他的公钥加密后发送的信息,就能够被他用本人的私钥解密。而后他伪装成咱们以同样的办法向对方发送信息,这样咱们的信息就被窃取了,然而本人还不晓得。为了解决这样的问题,能够应用数字证书。

首先应用一种 Hash 算法来对公钥和其余信息进行加密,生成一个信息摘要,而后让有公信力的认证核心(简称 CA)用它的私钥对音讯摘要加密,造成签名。最初将原始的信息和签名合在一起,称为数字证书。当接管方收到数字证书的时候,先依据原始信息应用同样的 Hash 算法生成一个摘要,而后应用公证处的公钥来对数字证书中的摘要进行解密,最初将解密的摘要和生成的摘要进行比照,就能发现失去的信息是否被更改了。

这个办法最要的是认证核心的可靠性,个别浏览器里会内置一些顶层的认证核心的证书,相当于咱们主动信赖了他们,只有这样能力保证数据的平安。

其余值到布尔类型的值的转换规则?

以下这些是假值:
• undefined
• null
• false
• +0、-0 和 NaN
• “”

假值的布尔强制类型转换后果为 false。从逻辑上说,假值列表以外的都应该是真值。

intanceof 操作符的实现原理及实现

instanceof 运算符用于判断构造函数的 prototype 属性是否呈现在对象的原型链中的任何地位。

function myInstanceof(left, right) {
  // 获取对象的原型
  let proto = Object.getPrototypeOf(left)
  // 获取构造函数的 prototype 对象
  let prototype = right.prototype; 

  // 判断构造函数的 prototype 对象是否在对象的原型链上
  while (true) {if (!proto) return false;
    if (proto === prototype) return true;
    // 如果没有找到,就持续从其原型上找,Object.getPrototypeOf 办法用来获取指定对象的原型
    proto = Object.getPrototypeOf(proto);
  }
}

参考:前端进阶面试题具体解答

当在浏览器中输出 Google.com 并且按下回车之后产生了什么?

(1)解析 URL: 首先会对 URL 进行解析,剖析所须要应用的传输协定和申请的资源的门路。如果输出的 URL 中的协定或者主机名不非法,将会把地址栏中输出的内容传递给搜索引擎。如果没有问题,浏览器会查看 URL 中是否呈现了非法字符,如果存在非法字符,则对非法字符进行本义后再进行下一过程。

(2)缓存判断: 浏览器会判断所申请的资源是否在缓存里,如果申请的资源在缓存里并且没有生效,那么就间接应用,否则向服务器发动新的申请。

(3)DNS 解析: 下一步首先须要获取的是输出的 URL 中的域名的 IP 地址,首先会判断本地是否有该域名的 IP 地址的缓存,如果有则应用,如果没有则向本地 DNS 服务器发动申请。本地 DNS 服务器也会先查看是否存在缓存,如果没有就会先向根域名服务器发动申请,取得负责的顶级域名服务器的地址后,再向顶级域名服务器申请,而后取得负责的权威域名服务器的地址后,再向权威域名服务器发动申请,最终取得域名的 IP 地址后,本地 DNS 服务器再将这个 IP 地址返回给申请的用户。用户向本地 DNS 服务器发动申请属于递归申请,本地 DNS 服务器向各级域名服务器发动申请属于迭代申请。

(4)获取 MAC 地址: 当浏览器失去 IP 地址后,数据传输还须要晓得目标主机 MAC 地址,因为应用层下发数据给传输层,TCP 协定会指定源端口号和目标端口号,而后下发给网络层。网络层会将本机地址作为源地址,获取的 IP 地址作为目标地址。而后将下发给数据链路层,数据链路层的发送须要退出通信单方的 MAC 地址,本机的 MAC 地址作为源 MAC 地址,目标 MAC 地址须要分状况解决。通过将 IP 地址与本机的子网掩码相与,能够判断是否与申请主机在同一个子网里,如果在同一个子网里,能够应用 APR 协定获取到目标主机的 MAC 地址,如果不在一个子网里,那么申请应该转发给网关,由它代为转发,此时同样能够通过 ARP 协定来获取网关的 MAC 地址,此时目标主机的 MAC 地址应该为网关的地址。

(5)TCP 三次握手: 上面是 TCP 建设连贯的三次握手的过程,首先客户端向服务器发送一个 SYN 连贯申请报文段和一个随机序号,服务端接管到申请后向服务器端发送一个 SYN ACK 报文段,确认连贯申请,并且也向客户端发送一个随机序号。客户端接管服务器的确认应答后,进入连贯建设的状态,同时向服务器也发送一个 ACK 确认报文段,服务器端接管到确认后,也进入连贯建设状态,此时单方的连贯就建设起来了。

(6)HTTPS 握手: 如果应用的是 HTTPS 协定,在通信前还存在 TLS 的一个四次握手的过程。首先由客户端向服务器端发送应用的协定的版本号、一个随机数和能够应用的加密办法。服务器端收到后,确认加密的办法,也向客户端发送一个随机数和本人的数字证书。客户端收到后,首先查看数字证书是否无效,如果无效,则再生成一个随机数,并应用证书中的公钥对随机数加密,而后发送给服务器端,并且还会提供一个后面所有内容的 hash 值供服务器端测验。服务器端接管后,应用本人的私钥对数据解密,同时向客户端发送一个后面所有内容的 hash 值供客户端测验。这个时候单方都有了三个随机数,依照之前所约定的加密办法,应用这三个随机数生成一把秘钥,当前单方通信前,就应用这个秘钥对数据进行加密后再传输。

(7)返回数据: 当页面申请发送到服务器端后,服务器端会返回一个 html 文件作为响应,浏览器接管到响应后,开始对 html 文件进行解析,开始页面的渲染过程。

(8)页面渲染: 浏览器首先会依据 html 文件构建 DOM 树,依据解析到的 css 文件构建 CSSOM 树,如果遇到 script 标签,则判端是否含有 defer 或者 async 属性,要不然 script 的加载和执行会造成页面的渲染的阻塞。当 DOM 树和 CSSOM 树建设好后,依据它们来构建渲染树。渲染树构建好后,会依据渲染树来进行布局。布局实现后,最初应用浏览器的 UI 接口对页面进行绘制。这个时候整个页面就显示进去了。

(9)TCP 四次挥手: 最初一步是 TCP 断开连接的四次挥手过程。若客户端认为数据发送实现,则它须要向服务端发送连贯开释申请。服务端收到连贯开释申请后,会通知应用层要开释 TCP 链接。而后会发送 ACK 包,并进入 CLOSE_WAIT 状态,此时表明客户端到服务端的连贯曾经开释,不再接管客户端发的数据了。然而因为 TCP 连贯是双向的,所以服务端仍旧能够发送数据给客户端。服务端如果此时还有没发完的数据会持续发送,结束后会向客户端发送连贯开释申请,而后服务端便进入 LAST-ACK 状态。客户端收到开释申请后,向服务端发送确认应答,此时客户端进入 TIME-WAIT 状态。该状态会继续 2MSL(最大段生存期,指报文段在网络中生存的工夫,超时会被摈弃)工夫,若该时间段内没有服务端的重发申请的话,就进入 CLOSED 状态。当服务端收到确认应答后,也便进入 CLOSED 状态。

对 rest 参数的了解

扩大运算符被用在函数形参上时,它还能够把一个拆散的参数序列整合成一个数组

function mutiple(...args) {
  let result = 1;
  for (var val of args) {result *= val;}
  return result;
}
mutiple(1, 2, 3, 4) // 24

这里,传入 mutiple 的是四个拆散的参数,然而如果在 mutiple 函数里尝试输入 args 的值,会发现它是一个数组:

function mutiple(...args) {console.log(args)
}
mutiple(1, 2, 3, 4) // [1, 2, 3, 4]

这就是 … rest 运算符的又一层威力了,它能够把函数的多个入参收敛进一个数组里。这一点 常常用于获取函数的多余参数,或者像下面这样解决函数参数个数不确定的状况。

HTTP 和 HTTPS 协定的区别

HTTP 和 HTTPS 协定的次要区别如下:

  • HTTPS 协定须要 CA 证书,费用较高;而 HTTP 协定不须要;
  • HTTP 协定是超文本传输协定,信息是明文传输的,HTTPS 则是具备安全性的 SSL 加密传输协定;
  • 应用不同的连贯形式,端口也不同,HTTP 协定端口是 80,HTTPS 协定端口是 443;
  • HTTP 协定连贯很简略,是无状态的;HTTPS 协定是有 SSL 和 HTTP 协定构建的可进行加密传输、身份认证的网络协议,比 HTTP 更加平安。

ES6 中模板语法与字符串解决

ES6 提出了“模板语法”的概念。在 ES6 以前,拼接字符串是很麻烦的事件:

var name = 'css'   
var career = 'coder' 
var hobby = ['coding', 'writing']
var finalString = 'my name is' + name + ', I work as a' + career + ', I love' + hobby[0] + 'and' + hobby[1]

仅仅几个变量,写了这么多加号,还要时刻小心外面的空格和标点符号有没有跟错中央。然而有了模板字符串,拼接难度直线降落:

var name = 'css'   
var career = 'coder' 
var hobby = ['coding', 'writing']
var finalString = `my name is ${name}, I work as a ${career} I love ${hobby[0]} and ${hobby[1]}`

字符串不仅更容易拼了,也更易读了,代码整体的品质都变高了。这就是模板字符串的第一个劣势——容许用 ${}的形式嵌入变量。但这还不是问题的要害,模板字符串的要害劣势有两个:

  • 在模板字符串中,空格、缩进、换行都会被保留
  • 模板字符串齐全反对“运算”式的表达式,能够在 ${}里实现一些计算

基于第一点,能够在模板字符串里无障碍地间接写 html 代码:

let list = `    <ul>        <li> 列表项 1 </li>        <li> 列表项 2 </li>    </ul>`;
console.log(message); // 正确输入,不存在报错

基于第二点,能够把一些简略的计算和调用丢进 ${} 来做:

function add(a, b) {const finalString = `${a} + ${b} = ${a+b}`
  console.log(finalString)
}
add(1, 2) // 输入 '1 + 2 = 3'

除了模板语法外,ES6 中还新增了一系列的字符串办法用于晋升开发效率:

(1)存在性断定:在过来,当判断一个字符 / 字符串是否在某字符串中时,只能用 indexOf > -1 来做。当初 ES6 提供了三个办法:includes、startsWith、endsWith,它们都会返回一个布尔值来通知你是否存在。

  • includes:判断字符串与子串的蕴含关系:
const son = 'haha' 
const father = 'xixi haha hehe'
father.includes(son) // true
  • startsWith:判断字符串是否以某个 / 某串字符结尾:
const father = 'xixi haha hehe'
father.startsWith('haha') // false
father.startsWith('xixi') // true
  • endsWith:判断字符串是否以某个 / 某串字符结尾:
const father = 'xixi haha hehe'
  father.endsWith('hehe') // true

(2)主动反复:能够应用 repeat 办法来使同一个字符串输入屡次(被间断复制屡次):

const sourceCode = 'repeat for 3 times;'
const repeated = sourceCode.repeat(3) 
console.log(repeated) // repeat for 3 times;repeat for 3 times;repeat for 3 times;

常见的 HTTP 申请头和响应头

HTTP Request Header 常见的申请头:

  • Accept: 浏览器可能解决的内容类型
  • Accept-Charset: 浏览器可能显示的字符集
  • Accept-Encoding:浏览器可能解决的压缩编码
  • Accept-Language:浏览器以后设置的语言
  • Connection:浏览器与服务器之间连贯的类型
  • Cookie:以后页面设置的任何 Cookie
  • Host:发出请求的页面所在的域
  • Referer:发出请求的页面的 URL
  • User-Agent:浏览器的用户代理字符串

HTTP Responses Header 常见的响应头:

  • Date:示意音讯发送的工夫,工夫的形容格局由 rfc822 定义
  • server: 服务器名称
  • Connection:浏览器与服务器之间连贯的类型
  • Cache-Control:管制 HTTP 缓存
  • content-type: 示意前面的文档属于什么 MIME 类型

常见的 Content-Type 属性值有以下四种:

(1)application/x-www-form-urlencoded:浏览器的原生 form 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 形式提交数据。该种形式提交的数据放在 body 外面,数据依照 key1=val1&key2=val2 的形式进行编码,key 和 val 都进行了 URL 转码。

(2)multipart/form-data:该种形式也是一个常见的 POST 提交形式,通常表单上传文件时应用该种形式。

(3)application/json:服务器音讯主体是序列化后的 JSON 字符串。

(4)text/xml:该种形式次要用来提交 XML 格局的数据。

new 操作符的实现原理

new 操作符的执行过程:

(1)首先创立了一个新的空对象

(2)设置原型,将对象的原型设置为函数的 prototype 对象。

(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象增加属性)

(4)判断函数的返回值类型,如果是值类型,返回创立的对象。如果是援用类型,就返回这个援用类型的对象。

具体实现:

function objectFactory() {
  let newObject = null;
  let constructor = Array.prototype.shift.call(arguments);
  let result = null;
  // 判断参数是否是一个函数
  if (typeof constructor !== "function") {console.error("type error");
    return;
  }
  // 新建一个空对象,对象的原型为构造函数的 prototype 对象
  newObject = Object.create(constructor.prototype);
  // 将 this 指向新建对象,并执行函数
  result = constructor.apply(newObject, arguments);
  // 判断返回对象
  let flag = result && (typeof result === "object" || typeof result === "function");
  // 判断返回后果
  return flag ? result : newObject;
}
// 应用办法
objectFactory(构造函数, 初始化参数);

margin 和 padding 的应用场景

  • 须要在 border 外侧增加空白,且空白处不须要背景(色)时,应用 margin;
  • 须要在 border 内测增加空白,且空白处须要背景(色)时,应用 padding。

原型链指向

p.__proto__  // Person.prototype
Person.prototype.__proto__  // Object.prototype
p.__proto__.__proto__ //Object.prototype
p.__proto__.constructor.prototype.__proto__ // Object.prototype
Person.prototype.constructor.prototype.__proto__ // Object.prototype
p1.__proto__.constructor // Person
Person.prototype.constructor  // Person

什么是 margin 重叠问题?如何解决?

问题形容: 两个块级元素的上外边距和下外边距可能会合并(折叠)为一个外边距,其大小会取其中外边距值大的那个,这种行为就是外边距折叠。须要留神的是,浮动的元素和相对定位 这种脱离文档流的元素的外边距不会折叠。重叠只会呈现在 垂直方向

计算准则: 折叠合并后外边距的计算准则如下:

  • 如果两者都是负数,那么就去最大者
  • 如果是一正一负,就会正值减去负值的绝对值
  • 两个都是负值时,用 0 减去两个中绝对值大的那个

解决办法: 对于折叠的状况,次要有两种:兄弟之间重叠 父子之间重叠(1)兄弟之间重叠

  • 底部元素变为行内盒子:display: inline-block
  • 底部元素设置浮动:float
  • 底部元素的 position 的值为absolute/fixed

(2)父子之间重叠

  • 父元素退出:overflow: hidden
  • 父元素增加通明边框:border:1px solid transparent
  • 子元素变为行内盒子:display: inline-block
  • 子元素退出浮动属性或定位

Proxy 能够实现什么性能?

在 Vue3.0 中通过 Proxy 来替换本来的 Object.defineProperty 来实现数据响应式。

Proxy 是 ES6 中新增的性能,它能够用来自定义对象中的操作。

let p = new Proxy(target, handler)

target 代表须要增加代理的对象,handler 用来自定义对象中的操作,比方能够用来自定义 set 或者 get 函数。

上面来通过 Proxy 来实现一个数据响应式:

let onWatch = (obj, setBind, getLogger) => {
  let handler = {get(target, property, receiver) {getLogger(target, property)
      return Reflect.get(target, property, receiver)
    },
    set(target, property, value, receiver) {setBind(value, property)
      return Reflect.set(target, property, value)
    }
  }
  return new Proxy(obj, handler)
}
let obj = {a: 1}
let p = onWatch(
  obj,
  (v, property) => {console.log(` 监听到属性 ${property}扭转为 ${v}`)
  },
  (target, property) => {console.log(`'${property}' = ${target[property]}`)
  }
)
p.a = 2 // 监听到属性 a 扭转
p.a // 'a' = 2

在上述代码中,通过自定义 setget 函数的形式,在本来的逻辑中插入了咱们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。

当然这是简略版的响应式实现,如果须要实现一个 Vue 中的响应式,须要在 get 中收集依赖,在 set 派发更新,之所以 Vue3.0 要应用 Proxy 替换本来的 API 起因在于 Proxy 无需一层层递归为每个属性增加代理,一次即可实现以上操作,性能上更好,并且本来的实现有一些数据更新不能监听到,然而 Proxy 能够完满监听到任何形式的数据扭转,惟一缺点就是浏览器的兼容性不好。

CSS 选择器及其优先级

选择器 格局 优先级权重
id 选择器 #id 100
类选择器 #classname 10
属性选择器 a[ref=“eee”] 10
伪类选择器 li:last-child 10
标签选择器 div 1
伪元素选择器 li:after 1
相邻兄弟选择器 h1+p 0
子选择器 ul>li 0
后辈选择器 li a 0
通配符选择器 * 0

对于选择器的 优先级

  • 标签选择器、伪元素选择器:1
  • 类选择器、伪类选择器、属性选择器:10
  • id 选择器:100
  • 内联款式:1000

注意事项:

  • !important 申明的款式的优先级最高;
  • 如果优先级雷同,则最初呈现的款式失效;
  • 继承失去的款式的优先级最低;
  • 通用选择器(*)、子选择器(>)和相邻同胞选择器(+)并不在这四个等级中,所以它们的权值都为 0;
  • 样式表的起源不同时,优先级程序为:内联款式 > 外部款式 > 内部款式 > 浏览器用户自定义款式 > 浏览器默认款式。

如何判断一个对象是否属于某个类?

  • 第一种形式,应用 instanceof 运算符来判断构造函数的 prototype 属性是否呈现在对象的原型链中的任何地位。
  • 第二种形式,通过对象的 constructor 属性来判断,对象的 constructor 属性指向该对象的构造函数,然而这种形式不是很平安,因为 constructor 属性能够被改写。
  • 第三种形式,如果须要判断的是某个内置的援用类型的话,能够应用 Object.prototype.toString() 办法来打印对象的[[Class]] 属性来进行判断。

为什么须要革除浮动?革除浮动的形式

浮动的定义: 非 IE 浏览器下,容器不设高度且子元素浮动时,容器高度不能被内容撑开。此时,内容会溢出到容器里面而影响布局。这种景象被称为浮动(溢出)。

浮动的工作原理:

  • 浮动元素脱离文档流,不占据空间(引起“高度塌陷”景象)
  • 浮动元素碰到蕴含它的边框或者其余浮动元素的边框停留

浮动元素能够左右挪动,直到遇到另一个浮动元素或者遇到它外边缘的蕴含框。浮动框不属于文档流中的一般流,当元素浮动之后,不会影响块级元素的布局,只会影响内联元素布局。此时文档流中的一般流就会体现得该浮动框不存在一样的布局模式。当蕴含框的高度小于浮动框的时候,此时就会呈现“高度塌陷”。

浮动元素引起的问题?

  • 父元素的高度无奈被撑开,影响与父元素同级的元素
  • 与浮动元素同级的非浮动元素会追随其后
  • 若浮动的元素不是第一个元素,则该元素之前的元素也要浮动,否则会影响页面的显示构造

革除浮动的形式如下:

  • 给父级 div 定义 height 属性
  • 最初一个浮动元素之后增加一个空的 div 标签,并增加 clear:both 款式
  • 蕴含浮动元素的父级标签增加 overflow:hidden 或者overflow:auto
  • 应用 :after 伪元素。因为 IE6- 7 不反对 :after,应用 zoom:1 触发 hasLayout**
.clearfix:after{
    content: "\200B";
    display: table; 
    height: 0;
    clear: both;
  }
  .clearfix{*zoom: 1;}

如何应用 for…of 遍历对象

for…of 是作为 ES6 新增的遍历形式,容许遍历一个含有 iterator 接口的数据结构(数组、对象等)并且返回各项的值,一般的对象用 for..of 遍历是会报错的。

如果须要遍历的对象是类数组对象,用 Array.from 转成数组即可。

var obj = {
    0:'one',
    1:'two',
    length: 2
};
obj = Array.from(obj);
for(var k of obj){console.log(k)
}

如果不是类数组对象,就给对象增加一个 [Symbol.iterator] 属性,并指向一个迭代器即可。

// 办法一:var obj = {
    a:1,
    b:2,
    c:3
};

obj[Symbol.iterator] = function(){var keys = Object.keys(this);
    var count = 0;
    return {next(){if(count<keys.length){return {value: obj[keys[count++]],done:false};
            }else{return {value:undefined,done:true};
            }
        }
    }
};

for(var k of obj){console.log(k);
}


// 办法二
var obj = {
    a:1,
    b:2,
    c:3
};
obj[Symbol.iterator] = function*(){var keys = Object.keys(obj);
    for(var k of keys){yield [k,obj[k]]
    }
};

for(var [k,v] of obj){console.log(k,v);
}

数组有哪些原生办法?

  • 数组和字符串的转换方法:toString()、toLocalString()、join() 其中 join() 办法能够指定转换为字符串时的分隔符。
  • 数组尾部操作的办法 pop() 和 push(),push 办法能够传入多个参数。
  • 数组首部操作的办法 shift() 和 unshift() 重排序的办法 reverse() 和 sort(),sort() 办法能够传入一个函数来进行比拟,传入前后两个值,如果返回值为负数,则替换两个参数的地位。
  • 数组连贯的办法 concat(),返回的是拼接好的数组,不影响原数组。
  • 数组截取方法 slice(),用于截取数组中的一部分返回,不影响原数组。
  • 数组插入方法 splice(),影响原数组查找特定项的索引的办法,indexOf() 和 lastIndexOf() 迭代办法 every()、some()、filter()、map() 和 forEach() 办法
  • 数组归并办法 reduce() 和 reduceRight() 办法

单行、多行文本溢出暗藏

  • 单行文本溢出
overflow: hidden;            // 溢出暗藏
text-overflow: ellipsis;      // 溢出用省略号显示
white-space: nowrap;         // 规定段落中的文本不进行换行
  • 多行文本溢出
overflow: hidden;            // 溢出暗藏
text-overflow: ellipsis;     // 溢出用省略号显示
display:-webkit-box;         // 作为弹性伸缩盒子模型显示。-webkit-box-orient:vertical; // 设置伸缩盒子的子元素排列形式:从上到下垂直排列
-webkit-line-clamp:3;        // 显示的行数

留神:因为下面的三个属性都是 CSS3 的属性,没有浏览器能够兼容,所以要在后面加一个-webkit- 来兼容一部分浏览器。

正文完
 0