avatar

目录
JavaScript-学习笔记

IIFE

IIFE(立即调用函数表达式)是一个再定义就会立即执行的JavaScript函数

javascript
1
(function (){})()

这是一个被称为自执行匿名函数的设计模式,主要包含两部分。第一部分是包围再圆括号运算符()里的一个匿名函数,这个匿名函数拥有独立的词法作用域,这不仅避免了外界访问此IIFE中的变量,而且又不会污染全局作用域

在JavaScript里,圆括号不能包含声明。因为这点,当圆括号为了包裹函数碰上了function关键字,它便知道将它作为一个函数表达式而不是函数声明,

  • 当圆括号出现在匿名函数的尾部想要调用函数时,它会默认将函数当成函数声明
  • 当圆括号包裹函数时,它会默认将函数作为表达式去解析,而不是函数声明

匿名函数的其他使用方法,可替换为其他的一元运算符

function前面加上运算符时,就变成了一个函数表达式,而函数表达式后面又添加了以个()就变成了一个立即执行的函数

javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 +function(){
console.log("+号匿名函数");
}()

-function(){
console.log("-号匿名函数");
}()

!(function(){
console.log("!号匿名函数");
})()

~function(){
console.log("~号匿名函数")
}()

注意:在使用匿名函数时,前面或者后面的代码一定要加;不然会报错

javascript
1
2
3
4
5
6
7
8
9
10

~function(){
console.log("~号匿名函数")
}()
// 这里没有加;号所以报错
(function(){
console.log("hello")
})

Uncaught TypeError: (intermediate value)(...) is not a function

闭包

闭包的参考

答案:

  • 是一个能够读取其他函数内部变量的函数,实质上是变量的解析过程(由内而外)
  • 可以用来封装私有变量,实现模块化
  • 可以保存变量到内存中(内存泄漏)

例子:

假设出现了一个案例:

每点击按钮实现数字加1,这个很简单学过一点JS的人都会

javascript
1
2
3
let sum = 0;
function add(x){ sum += x}
add(x);

但是这样又污染了全局作用域,多了个全局变量,这时候我们可以使用闭包

javascript
1
2
3
4
5
6
7
8
9
10
11
function add(){
let sum = 0;

function addX(x){
sum += x;
}
return addX;
}

let sum = add();
sum(1);

私有方法

javascript
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
var makeCounter = function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
};

var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */

经典面试题

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
<script>
var aLi = document.getElementsByTagName('li');
for (var i = 0; i < aLi.length; i++) {
aLi[i].onclick = function() {
console.log(i); // ?
};
}
</script>

这是因为li节点的onclick事件属于异步的,在click被触发的时候,for循环以迅雷不及掩耳盗铃的速度就执行完毕了,此时变量i的值已经是4了

因此在li的click事件函数顺着作用域链从内向外开始找i的时候,发现i的值已经全是4了

解决方法就需要通过闭包,把每次循环的i值都存下来。然后当click事件继续顺着作用域链查找的时候,会先找到被存下来的i,这样每一个li点击都可以找到对应的i值了

Javascript
1
2
3
4
5
6
7
8
9
10
<script>
var aLi = document.getElementsByTagName('li');
for (var i = 0; i < aLi.length; i++) {
(function(n) { // n为对应的索引值
aLi[i].onclick = function() {
console.log(n); // 0, 1, 2, 3
};
})(i); // 这里i每循环一次都存一下,然后把0,1,2,3传给上面的形参n
}
</script>

值类型和应用类型

值类型 String Number Boolean Undefined Null
引用类型 Array Object Function

区别

  1. 存储位置不一样
    • 值类型的变量会保存再栈内存中,如果再一个函数中声明一个值类型的变量,那么这个变量当函数执行结束之后就会自动销毁
    • 引用类型的变量名会保存再栈内存中,但是变量值会存储再堆内存中,引用类型的不变量不会自动销毁,当没有引用变量应用时,系统的垃圾回收机制会回收
栈内存 堆内存
a = 10,b = 20
arr [10,20,30,40]
  1. 赋值类型不一样
    • 值类型的变量直接赋值就是深赋值,如var a = 10;var b = a那么a的值就复制给b了,b修改值不会影响a
    • 引用类型的变量直接赋值实际上是传递引用,只是浅复制
Code
1
2
3
4
5
var arr = [10,20,30];
var copyArr = arr;
arr[0] = 1;
copyArr[1] = 2;
console.log(arr,copyArr); // 都是[1.2.30]
  1. 值类型无法添加属性和方法
    • 添加了不报错但是会显示undefined
Code
1
2
3
let str = "hello";
str.index = 1;
console.log(str.index); // undefined
  1. 类型比较

    • 值类型的比较是值比较,只有当它们的值相等的时候才能相等
    • 引用类型比较的是地址比较
    Code
    1
    2
    3
    4
    5
    6
    7
    8
    9
    let num = 1;
    let numStr = "1";
    console.log(num == numStr) // true
    console.log(num === numStr) // false 类型不一样

    let obj = {}
    let obj1 = {}

    console.log(obj == obj1) // false

DOM事件流

当在页面上某个元素触发特定事件时,比如点击的目标元素,所在的祖先元素都是会触发该事件,一直到window。

那这样就出现了一个问题,是现在目标元素上触发事件,还是现在祖先元素上触发?这就是事件流的概念

事件流是事件在目标元素和祖先元素间触发顺序,在早期,微软和网景实现了相反的事件里流,网景主张捕获方式,微软主张冒泡方式

  • 捕获(Capture):事件由最顶层逐级向下传播,直到目标元素
  • 冒泡(Bubble):从下往上。事件由第一个被触发的元素接收,然后逐级向上传播

后来w3c采用泽中 的方式,规定先捕获再冒泡,如此一个事件就被分成了三个阶段

  • 捕获阶段:事件从最顶层元素window一直传递到目标元素的父元素
  • 目标阶段:事件到达目标元素,如果事件指定不冒泡,那就会再这里中止
  • 冒泡阶段:事件从目标元素向上逐级传递知道最顶层元素window及捕获阶段的反方向

在默认的情况下都是以冒泡排序

addEventListener

在DOM2中,事件监听机制提供了一个参数来决定事件是在捕获阶段生效还是在冒泡阶段生效

语法:

Code
1
2
3
target.addEventListener(type, listener[, options]);
target.addEventListener(type, listener[, useCapture]);
target.addEventListener(type, listener[, useCapture, wantsUntrusted ]); // Gecko/Mozilla only
  • type:表示监听事件类型的字符串
  • listener:当所监听的事件类型触发时的回调,会接收一个事件通知对象
  • options:可选,可用的选项如下:
    • capture:Boolean,如果是true,表示listener会在捕获阶段触发,默认是false
    • once:Boolean,如果是true,表示listener在添加之后最多调用一次
    • passive:Boolean,如果是true,表示listener永远不会调用preventDefault()。如果listener任然调用了这个函数,客户端将会忽略它并抛出一个警告
  • useCapture:可选。Boolean,同options-capture

相比以前的事件绑定,addEventListener的方式有一下几个优势:

  • 可以为同一个事件注册多个回调函数,依次触发,而onclick的方式会覆盖掉
  • 使用onclick会覆盖HTML的方式
  • 可以通过参数决定监听是在冒泡还是捕获
  • onclick注册的监听只会在冒泡阶段生效

三种事件的绑定:

javascript
1
2
3
4
5
6
7
8
9
10
<div id="app" onclick="alert(11)"></div>
<script>
document.querySelector("#app").onclick = function(){
alert(22);
}
document.querySelector("#app").addEventListener("click",function(){
alert(33);
})
</script>
// 当事件被触发时,输出的是22和33,HTML方式绑定的方式被覆盖了

三种事件的解绑:

javascript
1
2
3
app.setAttributer('onclick', false);
app.onclick = null;
app.removeEventListener('click',事件的名字);

三种方式优先级排序 onclick > html > addEventLisnter. onclick 会覆盖 html. 但是 addEventLisnter 不会被覆盖且可以叠加.

BOM

BOM(Browser Object Model) 是指浏览器对象模型,是用于描述这种对象与对象之间层次关系的模型,浏览器对象模型提供了独立于内容的、可以与浏览器窗口进行互动的对象结构。

window对象

  • alert(str):向用户弹出消息提示
  • confirm(str):向用户弹出确认消息框,返回选择的Boolean
  • prompt(str):向用户弹出输入框,返回输入的内容
  • print():打印页面

location对象

  • hash:保存当前url中的哈希值(url中#号后面的字符)
  • host:保存当前url中的域名和端口号
  • hostName:保存当前url中的域名
  • pathName:保存当前url中路劲或者文件名
  • port:保存当前url的端口号
  • href:返回完整的url
  • protocol:返回当前url的协议
  • search:返回从问号开始的url

screen对象

History对象

原型链

Javascript中所有的对象都是Object的实例,并继承Object.prototype的属性和方法,也就是说,Object.prototype是所有对象的爸爸。

  • 如果发现自己身上没有该属性,则会去原型链找

  • 如果发现自己身上有此属性,则会先用自己的

Javascript
1
2
3
4
5
6
7
8
9
10
11
function Something(){}
Something.prototype.foo = "bar";

let one = new Somethind();

console.log(one.foo) // bar
one.foo = "abc"
console.log(one.foo) // abc
console.log(Something.foo) // bar
delete one.foo;
console.log(one.foo) // bar

这个例子中可以看出,实例修改了foo属性,但是并不是修改构造函数里的foo,而是在one实例中又添加了一个foo的属性,构造函数中的foo属性可以通过原型链来拿到

既然说到原型链就不得不提new关键字

既然通过new关键字能把自身的属性给新的实例,那么能不能不通过new达到相同的效果

答案是可行的,在MDN上讲解,实例上的__proto__是等于构造函数的prototype就拿上面的例子举例

Javascript
1
2
3
function Some(){}
let aa = new Some();
console.log(aa.__proto__ === Some.prototype) // true

这种效果相等于new

Javascript
1
2
3
4
function Some(){}
let aa = {};
aa.__proto__ = Some.prototype;
console.log(aa.__proto__ === Some.prototype) // true

如果往自身的prototype中挂载属性则必须通过???.prototype.属性来获取

Javascript
1
2
3
4
function Some(){}
Some.prototype.foo = "123";
console.log(Some.foo) // undefined
console.log(Some.prototype.foo)

call apply bind

三者的区别

  • apply,call和bind都是用来改变this的指向

  • apply和call会让当前函数立即执行,而bind会返回一个函数,后续需要再调用

  • apply参数接收的是一个数组,call和bind都是接收一个参数

call源码实现

Javascript
1
2
3
4
5
6
7
8
Function.prototype.myCall = function(target){
target = target || window;
target.fn = this; // this指向的是调用他的那个函数
const args = [...argument].slice(1) // 如果有传入参数则接收这些参数
cosnt result = target.fn(args);
delete target.fn;
return result;
}

例子:

Javascript
1
2
3
4
5
let foo = {value: 1};
function bar() {
console.log(this.value);
}
bar.myCall(foo);

我们来看看这个函数做了什么:

  1. 先让target做个兼容性处理
  2. target.fn指向this,由于this是指向调用它的人,所有这里的this指向的是bar
  3. 接收call穿过来的参数,平且用args变量来接收
  4. 这里是设计的最精妙之处,由于前面说过,this的指向是调用它的人target.fn(args)。这里的this指向的是target(foo),而target.fn又是调用这个函数(call)的人,所以bar这时的this指向了foo,改变了this的指向。并且传入参数
  5. 删除target.fn属性
  6. 返回函数返回值

bind源码实现

Javascript
1
2
3
4
5
6
7
8
9
Function.prototype.myBind = function(target) {
target = target || window;
target.fn = this;
return function(){
const args = [...arguments];
target.fn(...args);
delete target.fn;
}
}

例子:

Javascript
1
2
3
4
5
6
7
8
9
var obj = {
value: 2,
};
function test(arr){
console.log(...arr);
console.log(this.value);
}
const bindThis = test.myBind(obj);
bindThis([1,2,3]);
  1. 他和call的区别在于他不是立即执行的,所以使用闭包是最好的选择

apply源码实现:

ES6(ES2015)

Proxy

Proxy 对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。

我们日常会操作一些对象的增删查改,而Proxy能监视他们的操作并给出已经预置好的函数来处理他们的操作

语法:const p = new Proxy(target, handler)

  • target:要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
  • handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。

例子:

Javascript
1
2
3
4
5
6
7
8
9
10
let handler = {
get: function(target,name){
console.log("target:"+target,"name:"+name);
}
}


var p = new Proxy({},handler);
p.a = 1;
conosle.log(p.a) // 这里我们获取了它的值,触发了代理中的get方法所以返回的是 target:{a:1},name:a

Reflect

数据请求

XMLHttpRequest:

javascript
1
2
3
4
5
6
7
8
9
10
var xhr = new XMLHttpRequest();
xhr.open('GET',"./20. promise.html");
xhr.responseType = 'text';
xhr.onload = function () {
console.log(xhr.response);
};
xhr.onerror = function () {
console.log('出错了');
};
xhr.send();

fetch:

javascript
1
2
3
4
5
6
7
8
fetch("./20. promise.html")
.then(function (response) {
return response.text()
}).then(function (jsonData) {
console.log(jsonData);
}).catch(function () {
console.log('出错了');
});

fetch的特点:

  1. 基于Promise实现的接口,避免了ajax的回调地狱
  2. 以数据流的形式返回数据,数据量大和传输大文件有优势
  3. Fetch API引入三个新的对象(也是构造函数):Headers,Request和Response。headers用于设置请求头还是比较方便的。浏览器其实是不怎么需要构造请求和处理响应,这个角度看request和response比较鸡肋。但随着Service Worker的部署,以后浏览器也可以向Service Worker发起请求
  4. cors的支持,fetch的response.type有三种不一样的状态。basic:正常的同域请求,cors:CORS机制下的跨域请求,opaque:非CORS机制下的跨域请求,这时无法读取返回的数据,也无法判断是否请求成功

fetch的缺点:

  1. 首先是现在fetch的兼容性还不是很好,可能需要配置polyfill
  2. 其次fetch没办法像XMLHttpRequest一样取消请求的发送

跨域

跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,这是跨域的广义

  1. 资源跳转:A链接,重定向
  2. 资源嵌入:<link> <script> <img> <frame>等标签,以及CSSbackground:url() @font-face()等等
  3. 脚本请求:js发起的ajax请求,dom和js对象的跨域操作等

JSONP

JSONP的原理很简单,就是利用<script>标签没有跨域限制的”漏洞”。通过<script>标签指向一个需要访问的地址,并且我们可以通过callback的形式拿到请求的数据

但是JSONP只限于get请求。

CORS

CORS:全称”跨域资源共享”(Cross-origin resource sharing)。

CORS需要浏览器和服务器同时支持,才可以实现跨域请求,目前几乎所有浏览器都支持CORS,IE则不能低于IE10。CORS的整个过程都由浏览器自动完成,前端无需做任何设置,跟平时发送ajax请求并无差异。so,实现CORS的关键在于服务器,只要服务器实现CORS接口,就可以实现跨域通信。

通过设置请求头来进行跨域,nodeJS中设置

javascript
1
res.setHeader('Access-Control-Allow-Origin', '*')

深浅拷贝

什么是深浅拷贝

首先你要明白什么是值类型和引用类型

值类型:字符串,数字。。。

引用类型:数组,对象。。。

明白这个之后,再明白他们的区别

Javascript
1
2
3
4
let aa = 11;
let bb = aa;
bb = 22;
console.log(aa,bb); //11 22

这个就是值类型,也是深拷贝,拷贝出来就跟原来的数据没有光系了

Javascript
1
2
3
4
let aa = [11,22,33];
let bb = aa;
bb[1] = 44;
console.log(aa,bb); // [11, 44, 33]  [11, 44, 33]

总所周知引用类型的指向的是一个地址,再赋值操作的时候其实是把地址赋值给他,所以修改的是同一个区域,这就是所谓的浅拷贝

浅拷贝

Object.assign()

明白什么是浅合并之前,先明白对象的合并Object.assign(目标对象, 被合并对象)

如果被合并对象有的属性会把目标对象的给覆盖掉

他会修改原目标对象,并且返回这个合并的对象

Javascript
1
2
3
4
let form = {name: 'liming', sex: '男'};
let obj = {class: '一班', age: 15};
Object.assign(form, obj);
console.log('after', form); // {name: 'liming', sex: '男',class: '一班', age: 15}}

注意:

  • 此方法为浅合并
  • undefined参与合并
  • 原型属性不参与和合并

深拷贝

直接赋值

for in

Javascript
1
2
3
4
5
let form = {name: 'liming', sex: '男'};
let obj = {class: '一班', age: 15};
for(let key in obj){
form[key] = obj[key];
}

扩展运算符

Javascript
1
2
3
4
5
6
7
8
9
var obj = {
name: 'FungLeo',
sex: 'man',
old: '18'
}
var { ...obj2 } = obj
obj.old = '22'
console.log(obj) // {name: "FungLeo", sex: "man", old: "22"}
console.log(obj2) // {name: "FungLeo", sex: "man", old: "18"}

JSON.parse(JSON.stringify(target))

Javascript
1
2
3
4
let aa = {a:11,b:22};
let bb = JSON.parse(JSON.stringify(aa));
bb.a = 33;
console.log(aa,bb) // {a: 11, b: 22} {a: 33, b: 22}

for循环

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
var arr = [1,2,3,4,5]
var arr2 = copyArr(arr)
function copyArr(arr) {
let res = []
for (let i = 0; i < arr.length; i++) {
res.push(arr[i])
}
return res
}

arr2[1] = 10
console.log(arr,arr2); // [1, 2, 3, 4, 5]  [1, 10, 3, 4, 5]

slice

Javascript
1
2
3
4
var arr = [1,2,3,4,5]
var arr2 = arr.slice(0)
arr[2] = 5
console.log(arr,arr2) // [1, 2, 5, 4, 5] [1, 2, 3, 4, 5]

concat

Javascript
1
2
3
4
var arr = [1,2,3,4,5]
var arr2 = arr.concat()
arr[2] = 5
console.log(arr,arr2) // [1, 2, 5, 4, 5] [1, 2, 3, 4, 5]

Event

Event Delegation

  • 事件代理(Event Delegation),又称之为事件委托。是 JavaScript 中常用绑定事件的常用技巧。顾名思义,“事件代理”即是把原本需要绑定的事件委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。使用事件代理的好处是可以提高性能
  • 可以大量节省内存占用,减少事件注册,比如在table上代理所有tdclick事件就非常棒
  • 可以实现当新增子对象时无需再次对其绑定

Event Emitter

Event Hub

EventHub是基于发布订阅模式实现的一个实例,那么要搞清楚EventHub就先要搞清楚什么是发布订阅模式。

Event Loop

防抖与节流

防抖

其实是为了防止一个函数在段时间内疯狂执行

那么在哪些场景下会发生这种现象

  • 搜索框
  • 鼠标移动
  • 视窗resize

一个简单的例子

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function aa() {
var timer = null;
return function(){
if(timer){
clearInterval(timer)
}
timer = setInterval(function(){
console.log("---------------");
},500)

}
}
const bb = aa();

document.onmousemove = ()=>{
bb();
}

这个例子只有当你的鼠标静止不动的时候才会一直触发

节流

规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。

应用场景:

  • 鼠标点击事件不断触发
  • 在页面的无限加载场景下,需要用户在滚动页面时,每隔一段时间发一次 ajax 请求,而不是在用户停下滚动页面操作时才去请求数据;
  • 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断

内存泄漏

由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。

意外的全局变量

Javascript
1
2
3
4
5
6
function my() {
name="my name is tokey"
}
my()
console.log(window)
// 这个name会挂载到window上

针对上面类型的内存泄漏我们在平时一定要声明变量,不要有全局直接引用。(在JavaScript文件中添加 'use strict',开启严格模式,可以有效地避免上述问题。)

console.log

作为前端平时使用console.log在控制台打出相对应的信息可以说是非常常见。但如果没有去掉console.log可能会存在内存泄漏。因为在代码运行之后需要在开发工具能查看对象信息,所以传递给console.log的对象是不能被垃圾回收

闭包

Javascript
1
2
3
4
5
6
7
8
function my(name) {
function sendName() {
console.log(name)
}
return sendName
}
var test=my("tokey")
test() //tokey

在my()内部创建的sendName()函数是不会被回收的,因为它被全局变量test引用,处于随时被调用的状态。如果向释放内存可以设置test=null;由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多。

DOM泄漏

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div class="main">
<div class="test">天</div>
<div class="item">天</div>
<div class="item">向</div>
</div>
<button id="add">点击我增加</button>
<button id="remove">点击我减少</button>

add.onclick=function () {
var itemN=document.createElement('div')
var txt=document.createTextNode('上')
itemN.appendChild(txt)
main.appendChild(itemN)
}
remove.onclick=function () {
main.removeChild(test)
}

在我点击了三次增加之后,可以明显的看到节点(绿线)有三次明显的增加,之后我又删除了一个节点,但绿线没有下降,这是为什么呢?,这也就是内存泄漏。原因就是删除的DOM在js中有全局的引用。也就是我删除的test在文中被引用,所以无法释放内存。所以在删除更新等操作后应该将其设置为null

timers

js中常用的定时器setInterval()、setTimeout().他们都是规定延迟一定的时间执行某个代码,而其中setInterval()和链式setTimeout()在使用完之后如果没有手动关闭,会一直存在执行占用内存,所以在不用的时候我们可以通过clearInterval()、clearTimeout() 来关闭其对应的定时器,释放内存。熟悉朋友都知道这类定时器是有误差的,所以游览器给出了专门的API-requestAnimationFrame();大家可以试一下。

requestAnimationFrame

这时HTML5中新加的一个API,主要也是做动画的

requestAnimationFramel()浏览器每刷新一次,动画代码执行一次,不会造成丢帧现象以及动画卡顿;

1. requestAnimationFrame 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧。
2、在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的的cpu,gpu和内存使用量。

函数柯里化

柯里化(Currying),把接收多个参数的函数转换册灰姑娘接受一个单一参数的函数

Javascript
1
2
3
4
5
6
7
let add = function(x) {
return function(y) {
return x + y
}
}

add(3)(4) // 7

奇淫技巧

!!

JS中判断语句一般在特殊条件下我们都会使用单个感叹号来取反,比如:

Javascript
1
2
3
4
5
6
7
8
const one = "";

if(!one){
这里表示的是one为空的时候我们要执行的语句
}

那么!!one就是拿到one的布尔值
console.log(!!one) // false

通过双感叹号我们就可以拿到值现在的布尔值

>>>

Javascript
1
2
3
4
5
6
7
8
9
10
11
-1 >>> 0    // 4294967295

0 >>> 0 // 0

1 >>> 0 // 1

2 >>> 0 //2

'1x' >>> 0 // 0

null >>> 0 // 0
文章作者: 青空
文章链接: https://gitee.com/AIR-ZRB/blog/2020/03/23/JavaScript-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 青空
打赏
  • 微信
    微信
  • 支付寶
    支付寶