前端开发 大前端 W3Cbest

一个专注 WEB 开发的技术博客

0%

1,http和https的区别

http传输的数据都是未加密的,也就是明文的,网景公司设置了SSL协议来对http协议传输的数据进行加密处理,简单来说https协议是由http和ssl协议构建的可进行加密传输和身份认证的网络协议,比http协议的安全性更高。主要的区别如下:

  • Https协议需要ca证书,费用较高。
  • http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
  • 使用不同的链接方式,端口也不同,一般而言,http协议的端口为80,https的端口为443
  • http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

2,get请求传参长度的误区、get和post请求在缓存方面的区别

误区:我们经常说get请求参数的大小存在限制,而post请求的参数大小是无限制的。

实际上HTTP 协议从未规定 GET/POST 的请求长度限制是多少。对get请求参数的限制是来源与浏览器或web服务器,浏览器或web服务器限制了url的长度。为了明确这个概念,我们必须再次强调下面几点:

  • HTTP 协议 未规定 GET 和POST的长度限制
  • GET的最大长度显示是因为 浏览器和 web服务器限制了 URI的长度
  • 不同的浏览器和WEB服务器,限制的最大长度不一样
  • 要支持IE,则最大长度为2083byte,若只支持Chrome,则最大长度 8182byte

补充补充一个get和post在缓存方面的区别:

  • get请求类似于查找的过程,用户获取数据,可以不用每次都与数据库连接,所以可以使用缓存。
  • post不同,post做的一般是修改和删除的工作,所以必须与数据库交互,所以不能使用缓存。因此get请求适合于请求缓存。

3,cookie、sessionStorage、localStorage的区别

共同点:都是保存在浏览器端,并且是同源的

  • Cookie:cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递。而sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下,存储的大小很小只有4K左右。(key:可以在浏览器和服务器端来回传递,存储容量小,只有大约4K左右)
  • sessionStorage:仅在当前浏览器窗口关闭前有效,自然也就不可能持久保持,localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie只在设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭。(key:本身就是一个回话过程,关闭浏览器后消失,session为一个回话,当页面不同即使是同一页面打开两次,也被视为同一次回话)
  • localStorage:localStorage 在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的。(key:同源窗口都会共享,并且不会失效,不管窗口或者浏览器关闭与否都会始终生效)

补充说明一下cookie的作用:

保存用户登录状态。例如将用户id存储于一个cookie内,这样当用户下次访问该页面时就不需要重新登录了,现在很多论坛和社区都提供这样的功能。cookie还可以设置过期时间,当超过时间期限后,cookie就会自动消失。因此,系统往往可以提示用户保持登录状态的时间:常见选项有一个月、三个 月、一年等。

跟踪用户行为。例如一个天气预报网站,能够根据用户选择的地区显示当地的天气情况。如果每次都需要选择所在地是烦琐的,当利用了 cookie后就会显得很人性化了,系统能够记住上一次访问的地区,当下次再打开该页面时,它就会自动显示上次用户所在地区的天气情况。因为一切都是在后 台完成,所以这样的页面就像为某个用户所定制的一样,使用起来非常方便

定制页面。如果网站提供了换肤或更换布局的功能,那么可以使用cookie来记录用户的选项,例如:背景色、分辨率等。当用户下次访问时,仍然可以保存上一次访问的界面风格。

使用微信开发工具手写css真的每次都很浪费时间;现在终于可以用scss在微信开发者工具上自编译出wxss了

主要配置流程如下

1,下载EasySass插件复制到微信开发者工具,可以在vscode扩展里面找,然后安装,安装后可以在C盘 用户目录/.vscode/extensions 找到下载的扩展包

2,打开微信开发工具 找到菜单栏 设置 > 扩展设置 > 拓展 > 编辑器自定义扩展 打开扩展文件夹, 将vscode安装的扩展包复制到这里,然后重启微信开发工具,然后找到菜单栏 设置 > 扩展设置 > 拓展 > 编辑器自定义扩展 就能看到扩展文件,点击开启

3,配置编辑器,找到菜单栏 设置 > 扩展设置 > 编辑器 > 更多及工作区设置 进入设置页面,点击扩展 找到 EasySass configuration 看到 formatssetting.json 中编辑进入后就会看到生成的配置信息,将.css改为.wxss,如果不想要压缩版可删除

"easysass.formats": [
    {
    "format": "expanded",
    "extension": ".css"
    },
    {
    "format": "compressed",
    "extension": ".min.css"
    }
]

改为

"easysass.formats": [
    {
    "format": "expanded",
    "extension": ".wxss"
    }
]

在JavaScript中,数组是一个特殊的变量,用于存储不同的元素。它具有一些内置属性和方法,可用于根据需要添加,删除,迭代或操作数。并且了解JavaScript数组方法可以提升你的开发技能。 在本文中,我们将介绍15种关于JavaScript的数组方法,这些方法可以帮助你正确地处理数据。 1.some() 2. reduce() 3. Every() 4. map() 5. flat() 6. filter() 7. forEach() 8. findIndex() 9. find() 10. sort() 11. concat() 12. fill() 13. includes() 14. reverse() 15. flatMap() 注意,大多数情况下,我们将简化作为参数传递的函数。

// Instead of using this way
myAwesomeArray.some(test => {
if (test === "d") {
return test
}
})
// We'll use the shorter one
myAwesomeArray.some(test => test === "d")

1、some()

此方法为参数传递的函数测试数组。如果有一个元素与测试元素匹配,则返回true,否则返回false。 译者注: some() 不会对空数组进行检测;some() 不会改变原始数组。

const myAwesomeArray = ["a", "b", "c", "d", "e"];

myAwesomeArray.some(test => test === "d")
//-------> Output : true

2、reduce()

此方法接收一个函数作为累加器。它为数组中的每个元素依次执行回调函数,不包括数组中被删除或者从未被赋值的元素。函数应用于累加器,数组中的每个值最后只返回一个值。 译者注:reduce() 方法接受四个参数:初始值(上一次回调的返回值),当前元素值,当前索引,原数组。

const myAwesomeArray = [1, 2, 3, 4, 5]
myAwesomeArray.reduce((total, value) => total * value)
// 1 * 2 * 3 * 4 * 5
//-------> Output = 120

3、Every()

此方法是对数组中每项运行给定函数,如果数组的每个元素都与测试匹配,则返回true,反之则返回false。

const myAwesomeArray = ["a", "b", "c", "d", "e"]

myAwesomeArray.every(test => test === "d")
// -------> Output : false

const myAwesomeArray2 = ["a", "a", "a", "a", "a"]

myAwesomeArray2.every(test => test === "a")

//-------> Output : true

4、map()

该方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。它按照原始数组元素顺序依次处理元素。 译者注:map() 不会对空数组进行检测;map() 不会改变原始数组。

const myAwesomeArray = [5, 4, 3, 2, 1]

myAwesomeArray.map(x => x * x)

//-------> Output : 25
// 16
// 9
// 4
// 1

5、flat()

此方法创建一个新数组,其中包含子数组上的holden元素,并将其平整到新数组中。请注意,此方法只能进行一个级别的深度。

const myAwesomeArray = [[1, 2], [3, 4], 5]
myAwesomeArray.flat()
//-------> Output : [1, 2, 3, 4, 5]

6、filter()

该方法接收一个函数作为参数。并返回一个新数组,该数组包含该数组的所有元素,作为参数传递的过滤函数对其返回true。 译者注:filter()方法是对数据中的元素进行过滤,也就是说是不能修改原数组中的数据,只能读取原数组中的数据,callback需要返回布尔值;为true的时候,对应的元素留下来;为false的时候,对应的元素过滤掉。

const myAwesomeArray = [
{ id: 1, name: "john" }, 
{ id: 2, name: "Ali" },
{ id: 3, name: "Mass" }, 
{ id: 4, name: "Mass" }
]

myAwesomeArray.filter(element => element.name === "Mass")
//-------> Output : 0:{id: 3, name: "Mass"},
// 1:{id: 4, name: "Mass"}

7、forEach()

此方法用于调用数组的每个元素。并将元素传递给回调函数。 译者注: forEach() 对于空数组是不会执行回调函数的。

const myAwesomeArray = [
{ id: 1, name: "john" },
{ id: 2, name: "Ali" },
{ id: 3, name: "Mass" }
]

myAwesomeArray.forEach(element => console.log(element.name))
//-------> Output : john
// Ali
// Mass

8、 findIndex()

此方法返回传入一个测试条件(函数)符合条件的数组第一个元素位置。它为数组中的每个元素都调用一次函数执行,当数组中的元素在测试条件时返回 true 时, findIndex() 返回符合条件的元素的索引位置,之后的值不会再调用执行函数。如果没有符合条件的元素返回 -1 译者注:findIndex() 对于空数组,函数是不会执行的, findIndex() 并没有改变数组的原始值。

const myAwesomeArray = [
{ id: 1, name: "john" },
{ id: 2, name: "Ali" },
{ id: 3, name: "Mass" }
]

myAwesomeArray.findIndex(element => element.id === 3)
// -------> Output : 2

myAwesomeArray.findIndex(element => element.id === 7)
//-------> Output : -1

9、 find()

此方法返回通过测试(函数内判断)的数组的第一个元素的值。find() 方法为数组中的每个元素都调用一次函数执行:当数组中的元素在测试条件时回 true 时, find() 返回符合条件的元素,之后的值不会再调用执行函数。如果没有符合条件的元素返回 undefined。 译者注: find() 对于空数组,函数是不会执行的;find() 并没有改变数组的原始值。

const myAwesomeArray = [
{ id: 1, name: "john" },
{ id: 2, name: "Ali" },
{ id: 3, name: "Mass" }
]
myAwesomeArray.find(element => element.id === 3)
// -------> Output : {id: 3, name: "Mass"}

myAwesomeArray.find(element => element.id === 7)
//-------> Output : undefined

10、 sort()

此方法接收一个函数作为参数。它对数组的元素进行排序并返回它。也可以使用含有参数的sort()方法进行排序。

const myAwesomeArray = [5, 4, 3, 2, 1]

// Sort from smallest to largest
myAwesomeArray.sort((a, b) => a - b)
// -------> Output : [1, 2, 3, 4, 5]

// Sort from largest to smallest
myAwesomeArray.sort((a, b) => b - a)
//-------> Output : [5, 4, 3, 2, 1]

11、 concat()

此方法用于连接两个或多个数组/值,它不会改变现有的数组。而仅仅返回被连接数组的一个新数组。

const myAwesomeArray = [1, 2, 3, 4, 5]
const myAwesomeArray2 = [10, 20, 30, 40, 50]
myAwesomeArray.concat(myAwesomeArray2)
//-------> Output : [1, 2, 3, 4, 5, 10, 20, 30, 40, 50]

12、 fill()

此方法的作用是使用一个固定值来替换数组中的元素。该固定值可以是字母、数字、字符串、数组等等。它还有两个可选参数,表示填充起来的开始位置(默认为0)与结束位置(默认为array.length)。 译者注:fill() 方法用于将一个固定值替换数组的元素。

const myAwesomeArray = [1, 2, 3, 4, 5]

// The first argument (0) is the value
// The second argument (1) is the starting index
// The third argument (3) is the ending index

myAwesomeArray.fill(0, 1, 3)
//-------> Output : [1, 0, 0, 4, 5]

13、 includes()

此方法用于判断字符串是否包含指定的子字符串。如果找到匹配的字符串则返回 true,否则返回 false。 译者注:includes() 方法区分大小写。

const myAwesomeArray = [1, 2, 3, 4, 5]

myAwesomeArray.includes(3)
// -------> Output : true

myAwesomeArray.includes(8)
// -------> Output : false

14、 reverse()

此方法用于颠倒数组中元素的顺序。第一个元素成为最后一个,最后一个元素将成为第一个。

const myAwesomeArray = ["e", "d", "c", "b", "a"]
myAwesomeArray.reverse()
// -------> Output : ['a', 'b', 'c', 'd', 'e']

15、 flatMap()

该方法将函数应用于数组的每个元素,然后将结果压缩为一个新数组。它在一个函数中结合了flat()和map()。

const myAwesomeArray = [[1], [2], [3], [4], [5]]

myAwesomeArray.flatMap(arr => arr * 10)
//-------> Output : [10, 20, 30, 40, 50]
// With .flat() and .map()

myAwesomeArray.flat().map(arr => arr * 10)
//-------> Output : [10, 20, 30, 40, 50]

在这篇文章中,我将教你如何使用 CSS Grid 来创建一个超酷的图像网格图,它将根据屏幕的宽度来改变列的数量。最精彩的地方在于:所有的响应特性被添加到了一行 css 代码中。这意味着我们不必将 HTML 与丑陋的类名(如col-sm-4, col-md-8)混杂在一起,也不必为每个屏幕创建媒体查询。ok,让我们发车吧。

设置

在本文中,我将继续使用我在第一篇 CSS Grid 布局教程文章中的网格布局。然后,我们将在文章末尾添加图片。下面是我们初始化网格的外观: HTML 代码:

<div class="container">
  <div>1</div>
  <div>2</div>
  <div>3</div>
  <div>4</div>
  <div>5</div>
  <div>6</div>
</div>

CSS 代码:

.container {
display: grid;
grid-template-columns: 100px 100px 100px;
grid-template-rows: 50px 50px;
}

注意: 示例中有一些基础的样式,但我在这里没有写出来,因为这对 CSS 网格布局没有任何影响 如果这段代码让你感到困惑,我建议你去好好读下我的这篇文章Learn CSS Grid in 5 minutes,其中就详细的解释了布局的基础知识。 让我们让列开始具有自适应特性吧。 基础响应单位: fraction CSS 栅格布局带来了一个全新的值:fraction单位,fraction单位通常简写为fr,它允许你根据需要将容器拆分为多个块。 让我们将每一列更改为一个 fraction 单位宽:

.container {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 50px 50px;
}

结果是栅格布局将会把整个宽度分成三个 fraction,每列占据一个 fraction 单位,效果如下: 如果我们将grid-template-columns的值更改为1fr 2fr 1fr,第二列的宽度将会是其它两列的两倍。总宽现在是四个 fraction 单位,第二列占据两个 fraction 单位,其它列各占一个 fraction。效果如下: 总的来说,fraction 单位值将使你可以很容易的更改列的宽度。 高级响应 然而,上面列子并没有给出我们想要的响应性,因为网格总是三列宽。我们希望网格能根据容器的宽度改变列的数量。要做到这一点,你必须学习如下三个概念:

repeat()

首先我们学习repeat()函数。这是一个强大的指定列和行的方法。让我们使用repeat()函数来更改网格:

.container {
  display: grid;
  grid-template-columns: repeat(3, 100px);
  grid-template-rows: repeat(2, 50px);
  }

在上面代码中,repeat(3, 100px)等于100px 100px 100px。第一个参数指定行与列的数量,第二个参数指定它们的宽度,因此它将为我们提供与开始时完全相同的布局:

auto-fit

然后是auto-fit。让我们跳过固定数量的列,将3替换为自适应数量:

.container {
display: grid;
grid-gap: 5px;
grid-template-columns: repeat(auto-fit, 100px);
grid-template-rows: repeat(2, 100px);
}

效果如下: 现在,栅格将会根据容器的宽度调整其数量。它会尝试在容器中容纳尽可能多的 100px 宽的列。但如果我们将所有列硬写为 100px,我们将永远没法获得所需的弹性,因为它们很难填充整个宽度。正如你在上图看到的,网格通常在右侧留有空白。

minmax()

为了解决上述问题,我们需要minmax()。我们将 100px 替换为 minmax(100px, 1fr),代码如下:

.container {
display: grid;
grid-gap: 5px;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
grid-template-rows: repeat(2, 100px);
}

请注意,所有响应都发生在一行 css 代码中 效果如下: 正如你所见,效果完美。minmax()函数定义的范围大于或等于 min, 小于或等于 max。 因此,现在每列将至少为 100px。但如果有更多的可用空间,栅格布局将简单地将其均分给每列,因为这些列变成了 fraction 单位,而不是 100px。

添加图片

最后一步是添加图片。这与 CSS Grid 布局无关,但让我们看下代码。 我们在每个网格中添加一个图片标签:

<div><img src="img/forest.jpg"/></div>

为了使图片适应于每个条目,我们将其宽、高设置为与条目本身一样,我们使用object-fit:cover。这将使图片覆盖它的整个容器,根据需要,浏览器将会对其进行裁剪。

.container > div > img {
width: 100%;
height: 100%;
object-fit: cover;
}

效果如下: ok!现在你已经了解了 CSS Grid 布局中最复杂的概念之一了,请给自己一个赞吧。

浏览器兼容性

在结束本文前,我提下浏览器支持情况,在撰写本文时,全球77%的网站将支持 CSS Grid,而且比例还在逐步攀升。 2018将是 CSS 网格布局的元年。它将获得突破,并成为前端开发者的必备技能,就像过去几年 CSS Flexbox 布局发生的情况一样。

1. 按强类型风格写代码

JS是弱类型的,但是写代码的时候不能太随意,写得太随意也体现了编码风格不好。下面分点说明:

(1)定义变量的时候要指明类型,告诉JS解释器这个变量是什么数据类型的,而不要让解释器去猜,例如不好的写法:

var num,
    str,
    obj;

声明了三个变量,但其实没什么用,因为解释器不知道它们是什么类型的,好的写法应该是这样的:

var num = 0,
    str = '',
    obj = null;

定义变量的时候就给他一个默认值,这样不仅方便了解释器,也方便了阅读代码的人,他会在心里有数——知道这些变量可能会当作什么用。

(2)不要随意地改变变量的类型,例如下面代码:

var num = 5; 
    num = "-" + num;

第1行它是一个整型,第2行它变成了一个字符串。因为JS最终都会被解释成汇编的语言,汇编语言变量的类型肯定是要确定的,你把一个整型的改成了字符串,那解释器就得做一些额外的处理。并且这种编码风格是不提倡的,有一个变量第1行是一个整型,第10行变成了一个字符串,第20行又变成了一个object,这样就让阅读代码的人比较困惑,上面明明是一个整数,怎么突然又变成一个字符串了。好的写法应该是再定义一个字符串的变量:

var num = 5;
var sign = "-" + num;

(3)函数的返回类型应该是要确定的,例如下面不确定的写法:

function getPrice(count){
    if(count < 0) return "";
    else return count \* 100;
}

getPrice这个函数有可能返回一个整数,也有可能返回一个空的字符串。这样写也不太好,虽然它是符合JS语法的,但这种编码风格是不好的。使用你这个函数的人会有点无所适从,不敢直接进行加减乘除,因为如果返回字符串进行运算的话值就是NaN了。函数的返回类型应该是要确定的,如下面是返回整型:

function getPrice(count){
    if(count < 0) return -1;
    else return count \* 100;
}

然后告诉使用者,如果返回-1就表示不合法。如果类型确定,解释器也不用去做一些额外的工作,可以加快运行速度。

2. 减少作用域查找

(1)不要让代码暴露在全局作用域下

例如以下运行在全局作用域的代码:

<script>
    var map = document.querySelector("#my-map");
    map.style.height = "600px";
</script>

有时候你需要在页面直接写一个script,要注意在一个script标签里面,代码的上下文都是全局作用域的,由于全局作用域比较复杂,查找比较慢。例如上面的map变量,第二行在使用的时候,需要在全局作用域查找一下这个变量,假设map是在一个循环里面使用,那可能就会有效率问题了。所以应该要把它搞成一个局部的作用域:

<script>
!function(){
    var map = document.querySelector("#my-map");
    map.style.height = "600px";
}()
</script>

上面用了一个function制造一个局部作用域,也可以用ES6的块级作用域。由于map这个变量直接在当前的局部作用域命中了,所以就不用再往上一级的作用域(这里是全局作用域)查找了,而局部作用域的查找是很快的。同时直接在全局作用域定义变量,会污染window对象。

(2)不要滥用闭包

闭包的作用在于可以让子级作用域使用它父级作用域的变量,同时这些变量在不同的闭包是不可见的。这样就导致了在查找某个变量的时候,如果当前作用域找不到,就得往它的父级作用域查找,一级一级地往上直到找到了,或者到了全局作用域还没找到。因此如果闭包嵌套得越深,那么变量查找的时间就越长。如下:

function getResult(count){
    count++;
    function process(){
        var factor = 2;
        return count \* factor - 5;
    }
    return process();
}

上面的代码定义了一个process函数,在这个函数里面count变量的查找时间要高于局部的factor变量。其实这里不太适合用闭包,可以直接把count传给process:

function getResult(count){
    count++;
    function process(count){
        var factor = 2;
        return count \* factor - 5;
    }
    return process(count);
}

这样count的查找时间就和factor一样,都是在当前作用域直接命中。这个就启示我们如果某个全局变量需要频繁地被使用的时候,可以用一个局部变量缓存一下,如下:

var url = "";
if(window.location.protocal === "https:"){
    url = "wss://xxx.com" + window.location.pathname + window.location.search;
}

频繁地使用了window.location对象,所以可以先把它缓存一下:

var url = "";
var location = window.location;
if(location.protocal === "https:"){
    url = "wss://xxx.com" + location.pathname + location.search;
}

搞成了一个局变变量,这样查找就会明显快于全局的查找,代码也可以写少一点。

3. 避免==的使用

这里你可能会有疑问了,有些人喜欢用==,有些人喜欢用===,大家的风格不一样,你为什么要强制别人用===呢?习惯用==的人,不能仅仅是因为==比===少敲了一次键盘。为什么不提倡用==呢?

(1)如果你确定了变量的类型,那么就没必要使用==了,如下:

if(typeof num != "undefined"){ } 
var num = parseInt(value);
if(num == 10){ }

上面的两个例子都是确定类型的,一个是字符串,一个是整数。就没必要使用==了,直接用===就可以了。

(2)如果类型不确定,那么应该手动做一下类型转换,而不是让别人或者以后的你去猜这里面有类型转换,如下:

var totalPage = "5";
if(parseInt(totalPage) === 1){ }

(3)使用==在JSLint检查的时候是不通过的:

if(a == b){ }

如下JSLint的输出:

Expected ‘===’ and instead saw ‘==’. 
if(a == b){ }

(4)并且使用==可能会出现一些奇怪的现象,这些奇怪的现象可能会给代码埋入隐患:

null == undefined        //true
'' == '0'//false
0  == ''//true
0  == '0'//true
'' == 0//true
new String("abc") == "abc"                      //true
new Boolean(true) == true                       //true
true == 1//true

上面的比较在用===的时候都是false,这样才是比较合理的。例如第一点null居然会等于undefined,就特别地奇怪,因为null和undefined是两个毫无关系的值,null应该是作为初始化空值使用,而undefined是用于检验某个变量是否未定义。 这和第1点介绍强类型的思想是相通的。

4. 合并表达式

如果用1句代码就可以实现5句代码的功能,那往往1句代码的执行效率会比较高,并且可读性可能会更好

(1)用三目运算符取代简单的if-else

如上面的getPrice函数:

function getPrice(count){
    if(count < 0) return -1;
    else return count \* 100;
}

可以改成:

function getPrice(count){
    return count < 0 ? return -1 : count \* 100;
}

这个比写一个if-else看起来清爽多了。当然,如果你写了if-else,压缩工具也会帮你把它改三目运算符的形式:

function getPrice(e){return 0>e?-1:100\*e}

(2)连等

连等是利用赋值运算表达式会返回所赋的值,并且执行顺序是从右到左的,如下:

overtime = favhouse = listingDetail = {...}

有时候你会看到有人这样写:

var age = 0;
if((age = +form.age.value) >= 18){
    console.log("你是成年人");
} else {
    consoe.log("小朋友,你还有" + (18 - age) + "就成年了");
}

也是利用了赋值表达式会返回一个值,在if里面赋值的同时用它的返回值做判断,然后else里面就已经有值了。上面的+号把字符串转成了整数。

(3)自增

利用自增也可以简化代码。如下,每发出一条消息,localMsgId就自增1:

chatService.sendMessage(localMsgId++, msgContent);

5. 减少魔数

例如,在某个文件的第800行,冒出来了一句:

dialogHandler.showQuestionNaire("seller", "sell", 5, true);

就会让人很困惑了,上面的四个常量分别代表什么呢,如果我不去查一个那个函数的变量说明就不能够很快地意会到这些常量分别有什么用。这些意义不明的常量就叫“魔数”。 所以最好还是给这些常量取一个名字,特别是在一些比较关键的地方。例如上面的代码可改成:

var naireType = "seller",
dialogType = "sell",
questionsCount = 5,
reloadWindow = true;

naireHandler.showNaire(naireType, dialogType, questionsCount, reloadWindow);

这样意义就很明显了。

6. 使用ES6简化代码

ES6已经发展很多年了,兼容性也已经很好了。恰当地使用,可以让代码更加地简洁优雅。

(1)使用箭头函数取代小函数

有很多使用小函数的场景,如果写个function,代码起码得写3行,但是用箭头函数一行就搞定了,例如实现数组从大到小排序:

var nums = [4, 8, 1, 9, 0];
nums.sort(function(a, b){
    return b - a;
});
//输出[9, 8, 4, 1, 0]

如果用箭头函数,排序只要一行就搞定了:

var nums = [4, 8, 1, 9, 0];

nums.sort(a, b => b - a);

代码看起来简洁多了,还有setTimeout里面经常会遇到只要执行一行代码就好了,写个function总感觉有点麻烦,用字符串的方式又不太好,所以这种情况用箭头函数也很方便:

setTimeout(() => console.log("hi"), 3000)

箭头函数在C++/Java等其它语言里面叫做Lambda表达式,Ruby比较早就有这种语法形式了,后来C++/Java也实现了这种语法。 当然箭头函数或者Lambda表达式不仅适用于这种一行的,多行代码也可以,不过在一行的时候它的优点才比较明显。

(2)使用ES6的class

虽然ES6的class和使用function的prototype本质上是一样的,都是用的原型。但是用class可以减少代码量,同时让代码看起来更加地高大上,使用function要写这么多:

function Person(name, age){
    this.name = name;
    this.age = age;
}

Person.prototype.addAge = function(){
    this.age++;
};


Person.prototype.setName = function(name){
    this.name = name;
};

使用class代码看加地简洁易懂:

class Person{
    constructor(name, age){
        this.name = name;
        this.age = age;
    }
    addAge(){
        this.age++;
    }
    setName(name){
        this.name = name;
    }
}

并且class还可以很方便地实现继承、静态的成员函数,就不需要自己再去通过一些技巧去实现了。

(3)字符串拼接

以前要用+号拼接:

var tpl = 
    '<div>' + 
    '    <span>1</span>' +
    '</div>';

现在只要用两个反引号“`”就可以了:

var tpl = 
    `<div>
        <span>1</span>
    </div>
    `;

另外反引号还支持占位替换,原本你需要:

var page = 5,
type = encodeURIComponet("#js");
var url = "/list?page=" + page + "&type=" + type;

现在只需要: var url = `/list?page=${page}&type=${type}`; 就不用使用+号把字符串拆散了。

(4)块级作用域变量

块级作用域变量也是ES6的一个特色,下面的代码是一个任务队列的模型抽象:

var tasks = [];
for(var i = 0; i < 4; i++){
    tasks.push(function(){
        console.log("i is " + i);
    });
}
for(var j = 0; j < tasks.length; j++){
    tasks[j]();
}

但是上面代码的执行输出是4,4,4,4,并且不是想要输出:0,1,2,3,所以每个task就不能取到它的index了,这是因为闭包都是用的同一个i变量,i已经变成4了,所以执行闭包的时候就都是4了。那怎么办呢?可以这样解决:

var tasks = [];
for(var i = 0; i < 4; i++){
    !function(k){
        tasks.push(function(){
            console.log("i is " + k);
        });
    }(i);
}
for(var j = 0; j < tasks.length; j++){
    tasks[j]();
}

把i赋值给了k,由于k它是一个function的一个参数,每次执行函数的时候,肯定会实例化新的k,所以每次的k都是不同的变量,这样就输出就正常了。 但是代码看起来有点别扭,如果用ES6,只要把var改成let就可以了:

var tasks = [];
for(let i = 0; i <= 4; i++){
    tasks.push(function(){
        console.log("i is " + i);
    });
}
for(var j = 0; j < tasks.length; j++){
    tasks[j]();
}

只改动了3个字符就达到了目的。因为for循环里面有个大括号,大括号就是一个独立的作用域,let定义的变量在独立的作用域里面它的值也是独立的。当然即使没写大括号for循环执行也是独立的。 除了以上几点,ES6还有其它一些比较好用的功能,如Object的assign,Promise等,也是可以帮助写出简洁高效的代码。

目前,JS原始类型有六种,分别为:

  • Boolean
  • String
  • Number
  • Undefined
  • Null
  • Symbol(ES6新增)
  • ES10新增了一种基本数据类型:BigInt

复杂数据类型只有一种: Object

null 不是一个对象,尽管 typeof null 输出的是 object,这是一个历史遗留问题,JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,null 表示为全零,所以将它错误的判断为 object 。 基本数据类型和复杂数据类型的区别为: 1、内存的分配不同

  • 基本数据类型存储在栈中。
  • 复杂数据类型存储在堆中,栈中存储的变量,是指向堆中的引用地址。

2、访问机制不同

  • 基本数据类型是按值访问
  • 复杂数据类型按引用访问,JS不允许直接访问保存在堆内存中的对象,在访问一个对象时,首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象中的值。

3、复制变量时不同(a=b)

  • 基本数据类型:a=b;是将b中保存的原始值的副本赋值给新变量a,a和b完全独立,互不影响
  • 复杂数据类型:a=b;将b保存的对象内存的引用地址赋值给了新变量a;a和b指向了同一个堆内存地址,其中一个值发生了改变,另一个也会改变。
let b = {
age: 10
}

let a = b;
a.age = 20;
console.log(a); //{ age: 20 }

参数传递的不同(实参/形参)

函数传参都是按值传递(栈中的存储的内容):基本数据类型,拷贝的是值;复杂数据类型,拷贝的是引用地址

//基本数据类型
let b = 10

function change(info) {
info=20;
}
//info=b;基本数据类型,拷贝的是值得副本,二者互不干扰
change(b);
console.log(b);//10
//复杂数据类型
let b = {
age: 10
}

function change(info) {
info.age = 20;
}
//info=b;根据第三条差异,可以看出,拷贝的是地址的引用,修改互相影响。
change(b);
console.log(b);//{ age: 20 }

如果用一句话说明 this 的指向,那么即是: 谁调用它,this 就指向谁。 但是仅通过这句话,我们很多时候并不能准确判断 this 的指向。因此我们需要借助一些规则去帮助自己: this 的指向可以按照以下顺序判断:

1. 全局环境中的 this

浏览器环境:无论是否在严格模式下,在全局执行环境中(在任何函数体外部)this 都指向全局对象 window; node 环境:无论是否在严格模式下,在全局执行环境中(在任何函数体外部),this 都是空对象 {};

2. 是否是 new 绑定

如果是 new 绑定,并且构造函数中没有返回 function 或者是 object,那么 this 指向这个新对象。如下: 构造函数返回值不是 function 或 object。


function Super(age) {
    this.age = age;
}
let instance = new Super('26');
console.log(instance.age); //26

构造函数返回值是 function 或 object,这种情况下 this 指向的是返回的对象。


function Super(age) {
    this.age = age;
    let obj = {a: '2'};
    return obj;
}
let instance = new Super('hello');
console.log(instance.age); //undefined

你可以想知道为什么会这样?我们来看一下 new 的实现原理:

  1. 创建一个新对象。
  2. 这个新对象会被执行 [原型] 连接。
  3. 属性和方法被加入到 this 引用的对象中。并执行了构造函数中的方法.
  4. 如果函数没有返回其他对象,那么 this 指向这个新对象,否则 this 指向构造函数中返回的对象。

function new(func) {
    let target = {};
    target.__proto__ = func.prototype;
    let res = func.call(target);
    //排除 null 的情况
    if (res && typeof(res) == "object"  typeof(res) == "function") {
    return res;
    }
    return target;
}

3. 函数是否通过 call,apply 调用,或者使用了 bind 绑定,如果是,那么this绑定的就是指定的对象【归结为显式绑定】。


function info(){
    console.log(this.age);
}
var person = {
    age: 20,
    info
}
var age = 28;
var info = person.info;
info.call(person);   //20
info.apply(person);  //20
info.bind(person)(); //20

这里同样需要注意一种特殊情况,如果 call,apply 或者 bind 传入的第一个参数值是 undefined 或者 null,严格模式下 this 的值为传入的值 null /undefined。非严格模式下,实际应用的默认绑定规则,this 指向全局对象(node环境为global,浏览器环境为window)


function info(){
    //node环境中:非严格模式 global,严格模式为null
    //浏览器环境中:非严格模式 window,严格模式为null
    console.log(this);
    console.log(this.age);
}
var person = {
    age: 20,
    info
}
var age = 28;
var info = person.info;
//严格模式抛出错误;
//非严格模式,node下输出undefined(因为全局的age不会挂在 global 上)
//非严格模式。浏览器环境下输出 28(因为全局的age会挂在 window 上)
info.call(null);

4. 隐式绑定,函数的调用是在某个对象上触发的,即调用位置上存在上下文对象。典型的隐式调用为: xxx.fn()


function info(){
    console.log(this.age);
}
var person = {
    age: 20,
    info
}
var age = 28;
person.info(); //20;执行的是隐式绑定

5. 默认绑定,在不能应用其它绑定规则时使用的默认规则,通常是独立函数调用。


非严格模式: node环境,执行全局对象 global,浏览器环境,执行全局对象 window。 严格模式:执行 undefined

function info(){
    console.log(this.age);
}
var age = 28;
//严格模式;抛错
//非严格模式,node下输出 undefined(因为全局的age不会挂在 global 上)
//非严格模式。浏览器环境下输出 28(因为全局的age会挂在 window 上)
//严格模式抛出,因为 this 此时是 undefined
info();

6. 箭头函数的情况:

箭头函数没有自己的this,继承外层上下文绑定的this。


let obj = {
    age: 20,
    info: function() {
        return () => {
            console.log(this.age); //this继承的是外层上下文绑定的this
        }
    }
}

let person = {age: 28};
let info = obj.info();
info(); //20

let info2 = obj.info.call(person);
info2(); //28

任何一种编程语言都具有超出基本用法的功能,它得益于成功的设计和试图去解决广泛问题。 JavaScript 中有一个这样的函数: Array.from:允许在 JavaScript 集合(如: 数组、类数组对象、或者是字符串、map 、set 等可迭代对象) 上进行有用的转换。 在本文中,我将描述5个有用且有趣的 Array.from() 用例。

1. 介绍

在开始之前,我们先回想一下 Array.from() 的作用。语法:

Array.from(arrayLike[, mapFunction[, thisArg]])

arrayLike:必传参数,想要转换成数组的伪数组对象或可迭代对象。 mapFunction:可选参数,mapFunction(item,index){…} 是在集合中的每个项目上调用的函数。返回的值将插入到新集合中。 thisArg:可选参数,执行回调函数 mapFunction 时 this 对象。这个参数很少使用。 例如,让我们将类数组的每一项乘以2:

const someNumbers = { '0': 10, '1': 15, length: 2 };

Array.from(someNumbers, value => value * 2); // => [20, 30]

2.将类数组转换成数组

Array.from() 第一个用途:将类数组对象转换成数组。 通常,你会碰到的类数组对象有:函数中的 arguments 关键字,或者是一个 DOM 集合。 在下面的示例中,让我们对函数的参数求和:

function sumArguments() {
return Array.from(arguments).reduce((sum, num) => sum + num);
}

sumArguments(1, 2, 3); // => 6

Array.from(arguments) 将类数组对象 arguments 转换成一个数组,然后使用数组的 reduce 方法求和。 此外,Array.from() 的第一个参数可以是任意一个可迭代对象,我们继续看一些例子:

Array.from('Hey'); // => ['H', 'e', 'y']
Array.from(new Set(['one', 'two'])); // => ['one', 'two']

const map = new Map();
map.set('one', 1)
map.set('two', 2);
Array.from(map); // => [['one', 1], ['two', 2]]

3.克隆一个数组

在 JavaScript 中有很多克隆数组的方法。正如你所想,Array.from() 可以很容易的实现数组的浅拷贝。

const numbers = [3, 6, 9];
const numbersCopy = Array.from(numbers);

numbers === numbersCopy; // => false

Array.from(numbers) 创建了对 numbers 数组的浅拷贝,numbers === numbersCopy 的结果是 false,意味着虽然 numbers 和 numbersCopy有着相同的项,但是它们是不同的数组对象。 是否可以使用 Array.from() 创建数组的克隆,包括所有嵌套的?挑战一下!

function recursiveClone(val) {
return Array.isArray(val) ? Array.from(val, recursiveClone) : val;
}
const numbers = [[0, 1, 2], ['one', 'two', 'three']];
const numbersClone = recursiveClone(numbers);
numbersClone; // => [[0, 1, 2], ['one', 'two', 'three']]
numbers[0] === numbersClone[0] // => false

recursiveClone() 能够对数组的深拷贝,通过判断 数组的 item 是否是一个数组,如果是数组,就继续调用 recursiveClone() 来实现了对数组的深拷贝。 你能编写一个比使用 Array.from() 递归拷贝更简短的数组深拷贝吗?如果可以的话,请写在下面的评论区。

4. 使用值填充数组

如果你需要使用相同的值来初始化数组,那么 Array.from() 将是不错的选择。 我们来定义一个函数,创建一个填充相同默认值的数组:

const length = 3;
const init = 0;
const result = Array.from({ length }, () => init);
result; // => [0, 0, 0]

result 是一个新的数组,它的长度为3,数组的每一项都是0。调用 Array.from() 方法,传入一个类数组对象 { length } 和 返回初始化值的 mapFunction 函数。 但是,有一个替代方法 array.fill() 可以实现同样的功能。

const length = 3;
const init = 0;
const result = Array(length).fill(init);
fillArray2(0, 3); // => [0, 0, 0]

fill() 使用初始值正确填充数组。

4.1 使用对象填充数组

当初始化数组的每个项都应该是一个新对象时,Array.from() 是一个更好的解决方案:

const length = 3;
const resultA = Array.from({ length }, () => ({}));
const resultB = Array(length).fill({});

resultA; // => [{}, {}, {}]
resultB; // => [{}, {}, {}]

resultA[0] === resultA[1]; // => false
resultB[0] === resultB[1]; // => true

由 Array.from 返回的 resultA 使用不同空对象实例进行初始化。之所以发生这种情况是因为每次调用时,mapFunction,即此处的 () => ({}) 都会返回一个新的对象。 然后,fill() 方法创建的 resultB 使用相同的空对象实例进行初始化。不会跳过空项。

4.2 使用 array.map 怎么样?

是不是可以使用 array.map() 方法来实现?我们来试一下:

const length = 3;
const init = 0;
const result = Array(length).map(() => init);

result; // => [undefined, undefined, undefined]

map() 方法似乎不正常,创建出来的数组不是预期的 [0, 0, 0],而是一个有3个空项的数组。 这是因为 Array(length) 创建了一个有3个空项的数组(也称为稀疏数组),但是 map() 方法会跳过空项。

5. 生成数字范围

你可以使用 Array.from() 生成值范围。例如,下面的 range 函数生成一个数组,从0开始到 end - 1。

function range(end) {
return Array.from({ length: end }, (_, index) => index);
}
range(4); // => [0, 1, 2, 3]

在 range() 函数中,Array.from() 提供了类似数组的 {length:end} ,以及一个简单地返回当前索引的 map 函数 。这样你就可以生成值范围。

6.数组去重

由于 Array.from() 的入参是可迭代对象,因而我们可以利用其与 Set 结合来实现快速从数组中删除重复项。

function unique(array) {
return Array.from(new Set(array));
}

unique([1, 1, 2, 3, 3]); // => [1, 2, 3]

首先,new Set(array) 创建了一个包含数组的集合,Set 集合会删除重复项。 因为 Set 集合是可迭代的,所以可以使用 Array.from() 将其转换为一个新的数组。 这样,我们就实现了数组去重。

7.结论

Array.from() 方法接受类数组对象以及可迭代对象,它可以接受一个 map 函数,并且,这个 map 函数不会跳过值为 undefined 的数值项。这些特性给 Array.from() 提供了很多可能。 如上所述,你可以轻松的将类数组对象转换为数组,克隆一个数组,使用初始化填充数组,生成一个范围,实现数组去重。 实际上,Array.from() 是非常好的设计,灵活的配置,允许很多集合转换。 你知道 Array.from() 的其他有趣用例吗?可以写在评论区。

var str="123abc456";
var i=3;

1 取字符串的前i个字符:(不操作原字符)

str=str.substring(0,i);

2 从右边开始取i个字符:(不操作原字符)

str=str.substring(str.Length-i);

3 从右边开始去掉i个字符:(不操作原字符)

str=str.substring(0,str.Length-i);

4 如果字符串中有”abc”则替换成”ABC”:(操作原字符)

str=str.replace("abc","ABC");

5 如果想从某一个字符的下标开始:(不操作原字符)

str=str.substr(str.indexOf('a'),3);// 从‘a’下标开始(包括‘a’)截取3个元素,不操作原字符

如果你现在刚刚熟悉渐变是什么,我们会在你深入研究更强硬的东西之前为你画画。渐变是具有起点和终点的颜色的渐变 - 因此,线性渐变以一种颜色(如红色)开始,并逐渐沿直线过渡到另一种颜色(如蓝色)。就像我们在线性渐变文章中所说的那样,将日落视为线性渐变的最佳示例,以及每种颜色如何精美无缝地进入下一个。

文字渐变

h1 {
  font-size:72px;
  background:-webkit-linear-gradient(#eee,#333);
  -webkit-background-clip:text;
  -webkit-text-fill-color:transparent;
}