青菜不是葵花


  • 首页

  • 归档

  • 分类

  • 标签

  • 关于

js-pro-review1

发表于 2018-03-12 | 分类于 前端

前言:3月份到了实习内推的节点,寒假回家没有好好准备,于是来到学校后看到铺天盖地的实习内推信息就心急如焚。上个学期准备的JS基础、HTTP、CSS属性又忘了好些。直接看面经感觉有点虚,重新看书时间又不够。权衡了下还是感觉应该以夯实基础为主,保持技术路线的可持续发展。也许会错过一些内推的机会,但对之后的面试会有许多好处。这篇笔记主要记录上周所复习的JS高程以及CSS中的一些知识要点。

  1. 什么是闭包?
    闭包是指函数以及其被定义的词法环境的集合。譬如当我们在函数A里定义函数B,辣么根据作用域B可以访问A的活动对象,只要函数B一直保持着对A的活动对象的引用,A的活动对象就不会被销毁。
    注意每次执行一个包含闭包的函数,都会产生一个新的词法作用域;而在同一个词法作用域里产生的闭包函数,引用的都是同一个活动对象。
    比较好的文章:https://stackoverflow.com/questions/111102/how-do-javascript-closures-work

  2. 作用域的理解?
    作用域一般指函数可访问的变量的集合。
    JavaScript中没有块级作用域,但ES6中的let和const等关键字可以将变量绑定在块级作用域中。

  3. this的绑定?
    JS中this绑定利用四条规则(默认绑定、隐式上下文绑定、显示绑定、new绑定)。但ES6中的箭头函数的绑定不利用这四条规则,箭头函数的this继承外层函数调用的this绑定,且无法被修改。

  1. 回调函数。
  1. BFC是什么,有什么作用?
    BFC是指Block Formatting Context,块级格式化上下文,它是一个独立的渲染区域。
    只有Block-level box参与,规定了内部Block-level Box的布局方式,并且与外部区域毫不相干。
    block-level box为display属性为block,list-iten,table的元素。
    相对的inline-level box为display属性为inline,inline-block,inline-table的元素。
    BFC的规则包括:
    (1)内部的box垂直放置。
    (2)box垂直方向的距离由margin决定,属于同一个BFC的两个相邻box的margin会发生重叠,与包含块的border box相接触。
    (3)BFC的区域不会与float box重叠。
    (4)BFC就是页面上一个隔离的独立容器,与外界互不影响。
    (5)计算BFC的高度时,浮动元素也参与计算。
    因此按照上列规则可以用来清除浮动,避免边距重叠,以及制造两行排版。

  2. containing box的理解?
    (1)position属性为static和relative的元素其包含块为父元素的content-box。
    (2)position属性为absolute的元素其包含块为父元素的padding-box。
    (3)position属性为fixed的元素其包含块为viewport。
    (4)如果 position 属性是 absolute或fixed,包含块也可能是由满足以下条件的最近父级元素的内边距区的边缘组成的:transform或perspective不为none,或filter不为none。
    作用:当元素的宽高为百分数时,是基于containing box的宽高来计算。详细为hight、top、bottom基于包含块的高来计算,width、margin、padding等基于包含块的宽来计算。
    注意:当元素的高设置为百分数,而父元素的高度由内容决定,且position为static或relative时,该元素的高度为0。

上传图片并回显

发表于 2017-12-19 | 分类于 前端

功能:上传图片后,由服务器返回显示图片

1
2
3
4
5
6
<form>
<input type="file" name="image" id="image">
<input type="button" name="submit" id="submit" value="submit">
</form>
<div class="display" id="display"></div>
<script src="./index.js"></script>
1
2
3
4
5
6
7
8
9
10
.display {
width: 300px;
height: 300px;
border: 1px solid #ccc;
box-shadow: 0 0 2px #ddd;
}
.display img {
width: 100%;
height: 100%;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var postURL = "http://10.103.241.137:8888/upload";
document.getElementById("submit").onclick = function(){
var data = new FormData()
var file = document.getElementById("image").files[0]
data.append("image", file)
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if (xhr.readyState == 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
var img = null;
if (document.getElementsByTagName("img")[0]) {
img = document.getElementsByTagName("img")[0]
} else {
img = document.createElement("img")
document.getElementById("display").appendChild(img)
}
img.src = JSON.parse(xhr.responseText).url
}
}
}
xhr.open("Post", postURL, true)
xhr.send(data)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// nodejs
var http = require("http"),
url = require("url"),
formidable = require("formidable"),
fs = require("fs");
http.createServer(function(req, res){
console.log("request for url: "+ req.url)
res.setHeader('Access-Control-Allow-Origin', '*');
if (url.parse(req.url).pathname == "/upload") {
var form = formidable.IncomingForm();
form.uploadDir = "../tmp"
form.keepExtensions = true
form.parse(req,function(err, fields, files) {
if (err) {
console.log(err)
} else {
console.log(files)
fs.renameSync(files.image.path, '../tmp/'+files.image.name)
res.writeHead(200, {"content-type": "application/json"})
var body = JSON.stringify({
url: "http://10.103.241.137:8888/image/"+files.image.name
})
res.write(body)
res.end()
}
})
}
if (/^\/image\/.*/.test(url.parse(req.url).pathname)) {
var imgPath = "../tmp/" + url.parse(req.url).pathname.replace("/image/","")
fs.readFile(imgPath, function(err, file) {
if (err) {
res.writeHead(500, {"content-type": "text/plain"})
res.write(err + "\n")
res.end()
} else {
res.writeHead(200, {"content-type": "image/jpeg"})
res.write(file, "binary")
res.end()
}
})
}
}).listen(8888)

实现图片本地预览

发表于 2017-12-18 | 分类于 前端
思路:
    1. 监听input[type=file]的变化,一旦触发则开启显示事件。
    2. 读取file对象,检查类型。
    3. 新建image节点,添加到展示区域
    4. 新建FileReader对象,读取图片文件并将其添加到image的src
1
2
<input type="file" name="image" id="image" accept="image/png,image/jpeg">
<div class="display" id="display"></div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var fileInput = document.getElementById("image")
fileInput.onchange = function(event){
if (this.files.length == 0) { return }
var oFile = this.files[0]
if (!/image.*/.test(oFile.type)) {return}
var img = document.createElement("img")
document.getElementById("display").appendChild(img)
var reader = new FileReader();
reader.onload = function(evt) {
img.src= evt.target.result;
}
reader.readAsDataURL(oFile)
// 或者用createObjectURL
img.src = window.URL.createObjectURL(file)
}

以上方法兼容Chrome、火狐、IE edge及IE10。IE10以下并不兼容,以下为兼容版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//下面用于图片上传预览功能
var imgInput = document.getElementById("imgInput")
var imgPreview = document.getElementById("preview")
imgInput.onchange = function(event){
if (imgInput.files && imgInput.files[0]) {
var file = imgInput.files[0]
// createObjectURL 方法同步,内存占用少,网页关闭或使用revokeObjectURL时释放
imgPreview.src = window.URL.createObjectURL(file)
// readAsDataURL 方法异步,内存占用多,垃圾回收
// var read = new FileReader();
// read.onload = function(event) {
// imgPreview.src = event.target.result
// }
// read.readAsDataURL(file)
} else {
// IE10以下兼容
var imgSrc = imgInput.value
var localImag = document.getElementById("localImag");
try {
localImag.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale)";
localImag.filters.item("DXImageTransform.Microsoft.AlphaImageLoader").src = imgSrc;
} catch(e) {
alert("您上传的图片格式不正确,请重新选择!");
return false;
}
imgPreview.style.display = 'none';
}
}

有一个问题:为什么不能直接将imgInput的value值赋给展示区域imgPreview的src属性呢?
答:当我们通过input上传图片时,因为浏览器的沙箱机制,将可能包含敏感信息的真实图片路径给隐藏了起来,例如用”c:/fakepath/a.png”代替原路径,因此此时inputd的value值并不能正确连接到图片文件。而通过IE的滤镜、FileReader、Blob都可以获取图片的真实路径,只是实现方法不一样。

301 302 303 307

发表于 2017-11-16 | 分类于 前端

HTTP 1.0

301 moved permanently

规范:301表示永久重定向,当资源被永久迁移到一个新的URL时,任何未来对该资源的访问都会重定向到这个新的URL。具有编辑能力的用户将自动重新链接到新的地址上。新的链接地址需在服务器的响应首部给出,除了HEAD请求,这个响应报文的实体需要包含一个新的超链接的说明。但如果是对于POST方法的请求,若返回301状态码,用户则需要确认后才能重定向。

实现:但实际上关于post请求重定向用户确认的问题,浏览器上并没有实现,而且部分浏览器还会还会错误的发送一个GET请求到新地址。

302 moved temporarily

HTTP1.0:302表示临时重定向,location中的地址不应该被认为是资源路径,在后续的请求中应该继续使用原地址。原请求是post,则不能自动进行重定向,原请求是get,可以自动重定向。

实现:浏览器即便请求POST也会自动重定向。

应用:302劫持——若A站通过重定向到B站的资源x,A站具有较好的域名,但B站域名不那么友好,因此对搜索引擎而言,可能会保存A站的地址对应资源x而不是B站,这样A站就利用了B站的资源提高了自己的搜索排名。

总结:301与302规范和实现上一样,不同的是301是永久重定向,302是临时重定向。

HTTP 1.1

301 moved permanently

同1.0

302 found

302不再推荐使用,为兼容而保留。且再次重申只有当请求是GET或HEAD时才能自动重定向。为了消除HTTP1.0中302的二义性,在HTTP1.0中引入了303和307来细化302的语义。

303 see other

HTTP1.1: 1.0中302的实现

307 temporary redirect

HHTP1.1:1.0中302的规范

notes1

发表于 2017-11-06 | 分类于 前端

1. margin和padding

margin:块级元素都有效,行内元素只有左右有效。行内元素还会忽略周围元素上下的margin值而直接进行布局。

值 描述
auto 浏览器设置的上外边距。
length 定义固定的上外边距。默认值是 0。
% 定义基于父对象宽度的百分比。
inherit 规定应该从父元素继承上外边距。默认继承性:no

padding: 块级元素都有效,行内元素只有左右有效。行内元素不能忽略父级元素的上下padding值。

值 描述
auto 浏览器计算内边距。
length 规定以具体单位计的内边距值,比如像素、厘米等。默认值是 0。
% 规定基于父元素的宽度的百分比。
inherit 规定应该从父元素继承内边距。默认继承性:no

2. 如何html中开启和关闭DNS预读取?

参考网页:https://developer.mozilla.org/zh-CN/docs/Controlling_DNS_prefetching
通过X-DNS-Prefetch-Control: on/off来设置。

X-DNS-Prefetch-Control的功能是:
X-DNS-Prefetch-Control控制着浏览器的预读取功能,它可以使得浏览器主动去执行域名解析的功能,包括文档中所有的链接。预读取会在后台执行,所以DNS很可能在链接对应的东西出现之前就已经解析完毕。这样能够减少用户点击链接时的延迟。

为什么要预读取:
DNS请求带宽很小,但一旦涉及到多级解析,或者很少访问到的网站,需要从服务器硬盘中查找域名时,这时延迟就会比较高。这点在手机网络上特别明显。因此预读取可以使得解析提早完成,减少延迟。

打开和关闭DNS预读取:

1
<meta http-equiv="x-dns-prefetch-control" content="off">

强制查询特定主机名:

1
2
<link rel="dns-prefetch" href="http://www.spreadfirefox.com/">
<link rel="dns-prefetch" href="//www.spreadfirefox.com">

3. CSS属性position有几种值

absolute fixed relative static inherit

4. 如何用正则将ejs风格分隔符<%= %>变成mustache风格的{{}}

1
2
var text = `<%=hello world%><%=hello world%><%=hello world%>`
var result = text.replace(/<%=/g, `{{`).replace(/%>/g, `}}`)

5. 在HTTP响应Header中,Cache-control的常规值有哪些?

参考网页:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cache-Control

Cache-control通用消息头被用于在请求和响应中通过指定指令来实现缓存机制。单向。

缓存请求指令:
Cache-Control: max-age=<seconds>
Cache-Control: max-stale[=<seconds>] 表明客户端愿意接收一个已经过期的资源
Cache-Control: min-fresh=<seconds>示客户端希望在指定的时间内获取最新的响应
Cache-control: no-cache 强制所有缓存了该响应的缓存用户,在使用已存储的缓存数据前,发送带验证器的请求到原始服务器
Cache-control: no-store 缓存不应存储有关客户端请求或服务器响应的任何内容。
Cache-control: no-transform 不得对资源进行转换或转变。
Cache-control: only-if-cached 客户端只接受已缓存的响应, 并且不要向原始服务器检查是否有更新的拷贝

缓存响应指令:
Cache-control: must-revalidate 缓存必须在使用之前验证旧资源的状态,并且不可使用过期资源。
Cache-control: no-cache 强制所有缓存了该响应的缓存用户,在使用已存储的缓存数据前,发送带验证器的请求到原始服务器
Cache-control: no-store 缓存不应存储有关客户端请求或服务器响应的任何内容。
Cache-control: no-transform 不得对资源进行转换或转变。
Cache-control: public 响应可以被任何对象缓存
Cache-control: private 响应只能被单个用户缓存,不能作为共享缓存
Cache-control: proxy-revalidate 与must-revalidate作用相同,但它仅适用于共享缓存
Cache-Control: max-age= 设置缓存存储的最大周期
Cache-control: s-maxage= 覆盖max-age 或者 Expires 头,但是仅适用于共享缓存

expires: http响应首部,用来指明一个响应的到期时间。格式Expires: Wed, 21 Oct 2015 07:28:00 GMT

6. <script>标签defer和async属性的作用,以及二者的区别?

若没有defer和async,浏览器遇到<script>标签会立即加载并执行该脚本,且停止加载和渲染文档元素。有了defer和async时,浏览器就可以异步加载脚本,不影响文档的加载和渲染。但defer和async有一定区别,在于async会使得脚本加载完则立即执行,而defer会延迟到页面元素解析完毕后再执行。
图片来源(https://segmentfault.com/q/1010000000640869)

7.如何准确判断JS中一个变量的类型,请说明方法和原理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 方法一:typeof + instanceof
// 基本数据类型中typeof null返回object,复杂数据类型中typeof function返回function
var checkType = function(vari) {
var result = typeof vari
if (Array.isArray(vari)) {
result = 'array'
} else if (Object.is(vari,null)) {
result = 'null'
} else if (vari instanceof Date) {
result = 'date'
} else if (vari instanceof RegExp) {
result = 'RegExp'
}
return result
}
// 方法二:大招!
// Object原型对象的方法toString()会返回[object type]
var result = Object.prototype.toString.call(vari).slice(8,-1);

8.请写出下面代码的执行结果,并在注释中表名原因

1
2
3
4
5
6
7
8
9
10
function Foo() {
getName = function() {alert(1)}
return this;
}
var getName;
function getName() {alert(5)}
Foo.getName = function() {alert(2)}
Foo.prototype.getName = function() {alert(3)}
getName = function() {alert(4)}
getName() // 4

9.使用CSS3设计一个立起的圆形,并围绕自身中轴线做360度持续旋转(类似用手拨动一枚立起的硬币,在桌面上旋转的效果)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.circle {
width: 100px;
height: 100px;
background-color: #f37cae;
border: 1px solid #ee4e91;
border-radius: 100%;
animation: rotate 1s linear infinite;
}
@keyframes rotate {
0% { width: 100px; }
25% { width: 51px; }
50% { width: 2px; }
75% { width: 51px; }
100% { width: 100px; }
}
.center {
margin: 10px auto;
}
1
<div class="circle center"></div>

10. 编写javascript对象的深度克隆函数deepClone(注意对象值中的多种数据类型)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var deepClone = function(obj) {
var normal = JSON.parse(JSON.stringify(obj));
for(key in obj) {
var tp = Object.prototype.toString.call(obj[key]).slice(8,-1).toLowerCase()
if (tp === "undefined") {
normal[key] = undefined
} else if (tp === "function") {
normal[key] = obj[key]
} else if (tp === "date") {
normal[key] = new Date(obj[key])
} else if (tp === "RegExp") {
normal[key] = new RegExp(obj[key])
} else if (tp === "object") {
normal[key] = deepClone(obj[key])
}
}
return normal
}

11. 给定一个无序数组,请设计一个算法找出其中连续出现的数字区间。

12. 用CSS写一个三角形

1
2
3
4
5
6
7
/*四个三角形,若想设置单个,则其他颜色设置成transparent*/
.triangle {
width: 0;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-bottom: 50px solid blue;
}
triangle.PNG

事件委托的好处

发表于 2017-11-02 | 分类于 前端

当我们需要给许多DOM添加同一个类型的事件处理程序时,逐个添加事件处理程序就会有两个问题:每个事件处理函数都是对象,而对象会占用内存,因此会小号性能;二是多个DOM引用会延迟页面交互的就绪时间,降低用户体验。
这时可以考虑事件委托,事件委托的原理是通过给高层元素添加一个事件处理程序,利用事件的冒泡性质捕获一系列的事件,通过判断event.target来选择性的进行处理。这样就可以实现给许多DOM元素添加一类的事件处理程序。在减少了DOM引用的同时,还减少了事件处理的函数。此外,如果我们把事件添加到document节点上,那么只要页面元素可见就可以进行交互操作,而无需等待页面加载完毕。

OmniMarkupPreviewer预览md文件404问题

发表于 2017-10-09 | 分类于 前端

Question:

a.PNG

Solution:

On the Mac: subl “/Users//Library/Application Support/Sublime Text 3/Packages/OmniMarkupPreviewer/OmniMarkupLib/Renderers/libs/mdx_strikeout.py”

Replace the makeExtension() method with the following:

1
2
def makeExtension(*args, **kwargs):
return StrikeoutExtension(*args, **kwargs)

There are no packages for install

发表于 2017-10-09 | 分类于 前端

问题代码:12029

1
Connection refused (errno 12029) during HTTP write phase of downloading

VPN用不了结果百度了一上午找不着答案== 后来根据sublime的错误提示框进这个网址看了看 https://packagecontrol.io/docs/troubleshooting ,按照如下步骤解决问题。

第一步:

选择 View > Show Console ,调出命令行
选择 Package Control 开头的语言。
开启debug模式
确定Package Control中的proxy代理(如果有)信息正确
如果你有代理并重写了安全连接,则需要添加CA证书。(preferences-Browse Packages-open User folder-Create a file named Package Control.user-ca-bundle and paste in a PEM-formatted version of the certificate)

第二步:

* Open Internet Explorer
* Click the gear icon
* Select Internet Options
* Change to the Advanced tab
* Click the Reset Advanced Settings button
* Scroll down to the Security section of the options
* Verify that TLS 1.0, TLS 1.1 and TLS 1.2 are checked

如何配置多个SSH秘钥

发表于 2017-09-07 | 分类于 前端

给本地电脑配多个SSH是一个比较常见的需求,但是在生成新的SSH文件时如果不注意配置,很容易踩到各种地雷。因此记录下此次的配置过程。

step1: 进入.ssh文件目录

1
cd ~/.ssh

step2: 查看.ssh目录下的文件,检查是否有id_rsa文件,若有则是已经存在秘钥

1
ls

step3: 注意这里生成的默认文件名为id_rsa,若第二步中已经有id_rsa,则在命令中输入新的文件夹名如id_rsa_new。然后一路回车(密码为空)。

1
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"

step4: 将新生成的ssh秘钥添加到ssh-agent账户中。

1
ssh-add ~/.ssh/id_rsa_new

step5: 配置.ssh目录下的config文件。用编辑器打开config文件。
编辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
Host github.com
User qingywen@gmail.com
Hostname ssh.github.com
PreferredAuthentications publickey
IdentityFile ~/.ssh/id_rsa
Port 443
Host github2
User 742925032@qq.com
Hostname ssh.github.com
PreferredAuthentications publickey
IdentityFile ~/.ssh/id_rsa_gh
Port 443

注意:这里的host名称是区别你的秘钥的id,当你改变host名称后,从git提交的地址也要改变。比如第一个的提交地址是git@github.com:qingywen/qingywen.github.io.git,第二个提交地址是git@github2:still-wait/still-wait.github.io.git

另:在此次的配置过程中,因为之前设置过全局用户名和邮箱,因此即使提交使用的是对应的邮箱地址的ssh,但远程库显示的提交源仍是全局的邮箱。即使在本地库已经设置过当前的用户名和邮箱。这一点有些绕,还没能理解。

贝格尔编排算法

发表于 2017-08-25 | 分类于 算法

贝格尔编排算法可以有效的避免多队伍比赛中上一局优胜队跟轮空队比赛的不公平现象。一般用在队伍较少、比赛周期较长的情况下。

基本规则如下:queNum为参赛队伍数量。

  1. 将所有队伍从1、2、3…开始编排。若总队伍为奇数,则增加0置于末尾。
  2. 按编号从中间分成两半,左半边从上到下书写,右半边从下到上书写,位于同一水平线的队伍进行比赛。如下。
         1 - 0
         2 - 5
         3 - 4
  3. 在偶次轮,将0或最大数置于左上角;在奇次轮,则置于右上角。
  4. 每轮将1从上一轮的位置移动指定间隔,若遇到0则跳过不算。指定间隔 = Math.floor(queNum/2)。
  5. 当已产生不大于queNum的奇次轮,则停止。

若参赛队伍为5,即queNum = 5,举例如下:


第一轮 第二轮 第三轮 第四轮 第五轮
1 - 0 0 - 4 2 - 0 0 - 5 3 - 0
2 - 5 5 - 3 3 - 1 1 - 4 4 - 2
3 - 4 1 - 2 4 - 5 2 - 3 5 - 1

JS实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 贝格尔编排算法
let createMatches = function(queNum){
// 轮次
let turnNum = queNum + (queNum%2 -1);
// 赛制编排表
let matches = [];
// 初始轮
matches[0] = [];
for(let i = 0; i < queNum; i++){
matches[0].push(i+1);
}
if (turnNum == queNum) {
matches[0].push(0);
}
let left = false;
// 移动间隔
var space = Math.floor(queNum / 2);
// 后继轮次首先将最大数字或0换边
// 再将1按照间隔移动,其余数字逆时针填写
// 产生后继轮次
for(let j = 1; j < turnNum ; j ++) {
matches[j] = new Array(turnNum + 1);
let pos = matches[j-1].indexOf(1);
if (left) {
matches[j][turnNum] = matches[0][turnNum];
if (turnNum - pos > space) {
pos--;
}
}else {
matches[j][0] = matches[0][turnNum];
if (turnNum - pos >= space) {
pos--;
}
}
left = !left;
pos = (pos + space + 1)%(turnNum+1);
// 是否遇到大数
let meet = false;
for(let i = 0; i < turnNum; i++){
if (matches[j][(pos+i)%(turnNum+1)] === 0) {
meet = true;
}
if (meet) {
matches[j][(pos+i+1)%(turnNum+1)] = i+1;
} else {
matches[j][(pos+i)%(turnNum+1)] = i+1;
}
}
}
return matches;
};

123
青菜

青菜

主攻前端方向,偶尔来点杂文

28 日志
5 分类
15 标签
GitHub zhihu
© 2018 青菜
Patience is bitter, but its fruit is sweet.