1.数据类型检测(四种)
1.1 typeof 值
-
typeof [val]
检测基本类型值还是很准确的,返回当前值对应的数据类型,但不能检测基本数据类型的null
值,typeof null =>"object"
【01开头存储的是对象。】; -
typeof
无法对对象数据类型细分
,返回的都是"object"
;
typeof function fade(){}; // "function"
typeof null; // "object"
复制代码
1.2 instanceof
-
语法:
[val] instanceof 类
, 通过检测这个值是否属于这个类,从而验证是否属于这个类型; -
优势:对于数组、正则、对象,函数可以细分一下,但是无法检测基本数据类型
-
检测的原理:只要在当前实例的__proto__出现这个类,检测结果都是TRUR
var obj = {
'name':'蔡徐坤'
};
var arr = ['age','name'];
var fn = function fade(){};
//检测对象
console.log(obj instanceof Object);// true
console.log(obj instanceof Array);// false
//检测数组
console.log(arr instanceof Array);// true
//检测函数
console.log( fn instanceof Function) true
复制代码
1.3 constructor
- 语法:
[val].constructor === 类
;- 相对于instanceof来讲,基本类型也可以处理,而且因为获取实例的constructor实际上获取的是直接所属的类,所以在检测准确性上比instanceof还好一点
- 略势:constructor是可以
随意被改动
的
var obj = {
'name':'lee'
};
var arr = ['吃饭','睡觉'];
console.log(obj.constructor == Object);//true
console.log(arr.constructor == Array);//true
复制代码
1.4 Object.prototype.toString.call([val])
:最强大的检测方案
- 在其它数据类型的内置类原型上有toString,但是都是用来转换为字符串的,只有Object基类原型上的toString是用来检测数据类型的。
[11, 22].toString(); //结果是去掉逗号后面空格的"11,22".
var obj = { };
obj.toString();
/*
obj这个实例调用Object.prototype.toString执行,方法执行里面的THIS是
当前操作的实例obj,此方法就是检测实例THIS的数据类型的,返回结果:"[object 所属的类]"。
*/
/*
Object.prototype.toString.call([val]) 基于call强制改变方法中的
this是[val],就相当于在检测val的数据类型 <=> ({}).toString.call([val])
【 ({}).toString拿到 Object.prototype.toString。{} 可以是语句块,也可以是 对象字面量。】
*/
var o = {
'name':'蔡徐坤'
};
var a = ['red','blue'];
function c(name,age){
this.name = name;
this.age = age;
}
var c = new c('kingw','27');
console.log(Object.prototype.toString.call(a));//[object Array]
console.log(Object.prototype.toString.call(o));//[Object Object]
console.log(Object.prototype.toString.call(c));//[Object Object]
复制代码
1.4.1 封装方法
// 法1
let _type = function(obj){
const toString = Object.prototype.toString;
const typeMap = {
'[object String]' :'string',
'[object Number]':'number',
'[object Boolean]' :'boolean',
'[object Array]' :'array',
'[object Function]' :'function',
'[object Null]' :'null',
'[object Undefined]' :'undefined',
'[object Object]' :'object',
'[object Date]' :'date',
'[object RegExp]' :'regExp'
};
return typeMap[toString.call(obj)];
};
console.log(_type(1) === 'number')
console.log(_type(Array) === 'function') // true js中任何类都是函数。Array是类,属于函数。它的实例对象是个数组
复制代码
```js
// let typeMap = {
// isString: "[object String]",
// isNumber: "[object Number]",
// isBoolean: "[object Boolean]",
// isArray: "[object Array]",
// isFunction: "[object Function]",
// };
// let __type = {};
// let toString = Object.prototype.toString;
// for (var key in typeMap) {
// // 由于这里用的是var 所以为了防止最后调用的时取出的key是最后一次赋的值。用闭包来解决,也可以直接将这里改成let ,就可以不用写闭包
// if (!typeMap.hasOwnProperty(key)) break;
// __type[key] = (function () {
// var value = typeMap[key];
// return function (obj) {
// return toString.call(obj) === value;
// };
// })();
// }
/*--------------------------------------------------------------------------*/
let typeMap = {
isString: "String",
isNumber: "Number",
isBoolean: "Boolean",
isArray: "Array",
isFunction: "Function",
isDate: "Date",
isObject: "Object",
isRegExp: "RegExp",
isNull: "Null",
isUndefined: "Undefined",
};
let _toString = Object.prototype.toString;
let __type = {};
for (let key in typeMap) {
__type[key] = function (obj) {
let reg = new RegExp("\\[object " + typeMap[key] + "\\]");
return reg.test(_toString.call(obj));
};
}
__type.isString("");
console.log(__type.isString(""));
复制代码
1.4.2 面试题:如何判断数组与对象?
//方法一:ES6中通过Array.isArray()识别
Array.isArray([])//true
Array.isArray({})//false
//方法二:通过 `实例 instanceof 类`:
console.log({} instanceof Array)//false
console.log(['111','222'] instanceof Array)//true
//方法三:constructor
console.log([].constructor.name==='Array')//true
console.log({}.constructor.name==='Object')//true
//方法四:通过Object.prototype.toString.call()
Object.prototype.toString.call({}) //"[object object]"
Object.prototype.toString.call([]) //"[object Array]"
复制代码
2.变量
2.1 变量声明
1.定义变量有两种方式
a. 通过var定义变量,可以重复定义同名的变量,并且后定义的会覆盖先定义的,
通过let定义变量, "相同作用域内"不可以重复定义同名的变量;
b. 通过var定义变量, 可以先使用后定义(预解析)
通过let定义变量, 不可以先使用再定义(不会预解析)
c. 无论是var还是let定义在{}外面都是全局变量
将var定义的变量放到一个单独的{}里面, 还是一个全局变量
将let定义的变量放到一个单独的{}里面, 是一个局部变量
1.在JavaScript中{}外面的作用域, 我们称之为全局作用域
2.在JavaScript中函数后面{}中的的作用域, 我们称之为"局部作用域"
3.在ES6中只要{}没有和函数结合在一起, 那么应该"块级作用域"
4.块级作用域和局部作用域区别:
4.1在块级作用域中通过var定义的变量是全局变量
4.2在局部作用域中通过var定义的变量是局部变量
5.无论是在块级作用域还是在局部作用域, 省略变量前面的let或者var就会变成一个全局变量
复制代码
2.2 var/let/const三者区别:
第一:
- var定义的变量,没有
块
的概念,可以跨块
访问, 不能跨函数
访问。- let定义的变量,只能在
块作用域
里访问,不能跨块访问
,也不能跨函数访问
。- const用来定义
常量
,使用时必须初始化
(即必须赋值),只能在块作用域
里访问,而且不能修改
。(对于const声 明的引用类型,const仅保证指针不发生改变
,修改对象的属性不会改变对象的指针,所以是被允许的。)第二:
- var可以先
使用,后声明
;let必须先声明后使用
。第三:
- var是允许在相同作用域内
重复声明
同一个变量的,而let与const不允许这一现象。第四:
- 在全局上下文中,基于let声明的全局变量和全局对象GO(window)没有任何关系,var声明的变量会和GO有映射关系;
第五:解决暂时性死区:
- 暂时性死区是浏览器的bug:检测一个未被声明的变量类型时,不会报错,会返回undefined
```js
console.log(typeof a); //undefined
console.log(typeof a); //未声明之前不能使用
let a
```
复制代码
第六:
- let /const/function会把当前所在的大括号(除函数之外)作为一个全新的块级上下文,应用这个机制,在开发项目的时候,遇到循环事件绑定等类似的需求,无需再自己构建闭包来存储,只要基于let的块作用特征即可解决
2.3 变量提升
只有带var和function才会有变量提升
- 试题1:
var foo = 1;
function bar() {
//不执行,自己的foo先声明不赋值,为undefined转化为0,条件不成立,为假
if (foo) {
var foo = 2;
}
console.log(foo);
}
bar(); //undefined
复制代码
- 试题2:
var foo = 1;
function bar() {
/*
执行if,自己的foo先声明不赋值,为undefined转化为false,!undefined转化true,条件成立,执行if里面的赋值
*/
if (!foo) {
var foo = 2;
}
console.log(foo);
}
bar(); //2
复制代码
注意:
- 最新版浏览器向前兼容es3/es5
1.判断体和函数体不存在块级上下文,上下文只有全局和私有
2.不论条件是否成立,带function都要声明加定义
- 最新版浏览器向前兼容es6
1.存在块级作用域,大括号中出现let/const/function…都会被认为是块级作用域
2.不论条件是否成立,带function的只提前声明,不会提前赋值了
- 试题3:
var a = 0;
if (true) {
a = 1;
function a() {}
a = 21;
console.log(a);
}
console.log(a);
/*
低级打印 21 21
高级打印 21 1
高级版本中这样一看你是不是懂了:
var window.a = 0;
if (true) {
window.a = 1;
let a = function() {}
a = 21;
console.log(a);//21
}
console.log(a);//window.a=1
*/
复制代码
- 试题4:
{
function foo() {};
foo = 1;
console.log(foo);//1
}
console.log(foo);//foo(){}
/*
EC(G):{
大括号中出现let/const/function...都会被认为是块级作用域;
function的只提前声明,不会提前赋值;
变量提升:foo
代码执行:EC(block)块级作用域{
AO: foo-----AF0 改foo=1
变量提升 foo
代码执行: function foo() { } ==》AF0
foo = 1;
//foo在全局声明过,为了兼容es3,浏览器会把这行代码之前对foo操作映射到全局 :把全局的foo---AF0
}
代码执行2:console.log(foo);//function foo(){}
}
*/
复制代码
- 试题5:
{
function foo() {}
foo = 1;
function foo() {}
}
console.log(foo);
/*
EC(G):{
VO: foo 改为 1
变量提升:foo
代码执行:EC(block)块级作用域{
AO: foo-----AF0
变量提升 foo
代码执行: function foo() { } ==》AF0
foo = 1;
function foo() {}//foo在全局声明过,为了兼容es3,浏览器会把这行代码之前对foo操作映射到全局 :把全局的foo---1
}
代码执行2:console.log(foo);//1
}
*/
复制代码
- 试题6:
console.log(foo);//undefined
{
function foo() {}
foo = 1;
function foo() {}
foo = 2;
}
console.log(foo);//1
复制代码
3.函数运行过程
函数执行:
1.形成私有上下文
2.进栈执行
3.一系列操作
4. 正常情况下代码执行完,私有上下文会出栈释放;但是,若果当前私有上下文的某个东西(一般是一个堆)被上下文以外的事物占用了,则不会出战释放,形成不销毁的上下文.
- 试题7:
let x = 1;
function A(y) {
let x = 2;
function B(z) {
console.log(x + y + z);
}
return B;
}
let c = A(2);
c(3);// 7
复制代码
3.1 闭包
函数执行会形成全新的私有上下文,这个上下文可能被释放,也可能不被释放,不论是否被释放,他的作用是:
1.
保护
–划分一个独立的代码执行区域,在这个区域中有自己私有变量的存储空间,而用到的私有变量和其他区域中的变量不会有任何冲突(防止全局污染)。
2.保存
–如果上下文不被销毁,那么存储的私有变量的值也不会被销毁,可以被其下级上下文调取使用我们把形成私有上下文的保护和保存机制,称之为
"闭包"
。
- 试题8:
let x = 5;
function fn(x) {
return function (y) {
console.log(y + ++x);
};
}
let f = fn(6);
f(7);//14
fn(8)(9);//18
f(10);//18
console.log(x);//5
复制代码
- 试题9:
let a = 0,b = 0;
function A(a) {
A = function (b) {
console.log(a + b++);
};
console.log(a++);
}
A(1);//1
A(2);//4
复制代码
- 试题10:
var a = 10,b = 11,c = 12;//c=3
function test(a) {
a = 1;
var b = 2;
c = 3;
}
test(10);
console.log(a, b, c);
/*解析:
EC(G)
变量提升:a=10 ,b=11,c=12 ,
function test(){} scope:EC(G) 形参:a 存放到AF0中
代码执行1:test(10)---AF0(10)
{
形参赋值:a=10 改a=1
变量提升:a=1(找到自己形参改), b=2,c=3(找到上级修改)
代码执行:
}
代码执行2:console.log(a, b, c);//10,11,3
*/
复制代码
- 试题11:
var a = 4;
function b(x, y, a) {
console.log(a);
arguments[2] = 10;
console.log(a);
}
a = b(1, 2, 3);
console.log(a);
// 3 10 undefined
/*
EC(G):{
变量提升:a=4,
function b(){...} scope:EC(G) 形参:x,y,a 存到AF0中
代码执行:a=b(1,2,3) -->a=AF0(1,2,3) 形成EC(A)
console.log(a)==>undefined
}
EC(A)私有上下文 进栈执行{
形参赋值:x=1,y=2,a=3,a=10
变量提升
代码执行: console.log(a); ==>3
arguments[2] = 10; a=10
console.log(a); ==>10
函数执行完毕后返回给a=undefined
}
*/
复制代码
初始化实参集合
- 试题12:
function func(x, y, z) {
x = 100;
console.log(arguments[0]);
y = 200;
console.log(arguments[1]);
z = 300;
console.log(arguments[2]);
}
func(1, 2);//100 200 undefined
/*
执行函数时要初始化实参集合arguments:{0:10,1:20,length:2}
形参赋值:x=10 ,y=20 ,z=undefined
映射关系:x---argunments[0]
y---argunments[1]
z---argunments[2]
*/
复制代码
- 试题13:
var a = 9;
function fn() {
a = 0;
return function (b) {
return b + a++;
};
}
var f = fn();
console.log(f(5));
console.log(fn()(5));
console.log(f(5));
console.log(a);
/*
EC(G)全局上下文:{
VO: a=9 fn=AF0 f=AF0() a=0 f=BF0 a=1,a=0 ,a=1,a+1=2
变量提升:a
代码执行:f=fn()
形成EC(Fn)上下文:{
AO:
变量提升:
代码执行:a=0 //把全局的a改为0
return function (b) { return b + a++; };把这个函数作为结果赋值给f=BF0
}
}
AF0函数堆:{`
a = 0;
return function (b) {
return b + a++;
};
`}
BF0函数堆:{`
function (b) { return b + a++; };
`}
全局中代码执行1: console.log(f(5));
{
console.log(f(5));相当于打印console.log(5+a++);//5+0=5
私有:b=5
全局:a=0 a++=0 然后把全局的a+1
}
全局中代码执行2:console.log(fn()(5));//5
{
执行fn(),又把a=0了,返回function (b) { return b + a++;};
再把5传进去执行这个函数 5+0=5 全局下a+1
}
全局中代码执行3:console.log(f(5))// 5+1=6 全局a+1
全局中代码执行4:console.log(a);//2
*/
复制代码
- 试题14:
var test = (function (i) {
return function () {
alert(i * 2);
};
})(2);
test(5);//4
复制代码
- 试题15:
var x = 5,
y = 6;
function func() {
x += y;
func = function (y) {
console.log(y + --x);
};
console.log(x, y);
}
func(4);
func(3);
console.log(x, y);
/*
EC(G)全局上下文:{
VO: x=5,y=6,func=BF0,x=11,x=10
变量提升:x,y,func,
代码执行:func(4)---AF0(4)
形成EC(Fn)上下文:{
AO:
变量提升:
代码执行1:x+=y ==》x=11赋值给全局
代码执行2:func=BF0 形参:y
代码执行3:console.log(x, y);//输出11,6
}
代码执行func(3)---BF0(3)
形成EC(Fn1)上下文:{
AO:y=3
变量提升:
代码执行1:y+--x =3+10=13 全局x=10 //输出13
}
代码执行:console.log(x, y);//10,6
}
AF0函数堆:{
x += y;
func = function (y) { console.log(y + --x); };
console.log(x, y);
}
BF0函数堆:{
console.log(y + --x);
}
*/
复制代码
- 试题16:
function fun(n, o) {
console.log(o);
return {
fun: function (m) {
return fun(m, n);
},
};
}
var c = fun(0).fun(1);
c.fun(2);
c.fun(3);
/*
EC(G)全局上下文:{
VO: fun=AF0 ,c=fun(0).fun(1)[此刻要执行函数],c=BF0
变量提升:fun,c,
代码执行:c=fun(0).fun(1)--》AF(0).AF0(1)
形成EC(Fn1)上下文:{
AO:n=1,o=0
变量提升:
代码执行1:console.log(o);//undefined
代码执行2:return { fun: function (m) {return fun(m, n); },返回了一个对象,形成一个对象堆存储BF0
代码执行3:BF0.fun(1)执行了BF0对象的fun方法,传参数1===》执行BF1(1)里面的函数
形成BF1(1)上下文:{
AO:m=1
变量提升:
代码执行:return fun(m, n) //m=1自己的,n=0上级的,==》return fun(1,0)===>0
并且还返回了BF0对象给c
}
};
}
代码执行:c.fun(2);==>m=2,n=1==>fun(2,1)//1
代码执行:c.fun(3);==>m=3,n=1==>fun(3,1)//1
}
AF0函数堆 形参为:n,o {
console.log(o);
return {
fun: function (m) {return fun(m, n); },
};
}
BF0对象堆{
` fun: BF1 `
}
BF1函数堆{
function (m) {return fun(m, n)
}
*/
复制代码
4.堆栈内存
4.1 一些基本概念
栈内存
:浏览器在计算机内存中分配出一块内存供代码执行
的环境栈(ECStack),也称栈内存
,是用来执行代码和存储基本类型值的(创建的变量也存栈里面了);基本数据类型都是存到栈里面的,因为基本数据类型占用空间小、大小固定,通过值来访问,属于被频繁使用的数据。
注意 : 不仅全局代码执行(EC(G)全局执行上下文),而且函数执行(EC(X)私有上下文),最后也都会进栈执行的;基于ES6中的let/const形成的块作用域也是栈内存
堆内存
:堆内存是用来存储引用数据类型值
的 , 浏览器会把内置的属性和方法
放到一个单独的内存中;
引用数据类型
是先开辟一个堆内存
,把东西存进去,然后在栈中存储一个指针
,这个指针指向堆内存空间中该实体的起始地址。- js 中存在多种作用域(全局,函数私有的,块级私有的),代码执行前首先会形成自己的执行上下文,然后把上下文进栈,进栈后,在当前上下文再依次执行代码;
全局执行期上下文
(EC(G))进栈(ECStack)执行,执行完代码就会把形成的上下文释放(出栈),当页面关闭全局上下文出栈;
* VO 变量对象
:在当前上下文中,用来存放创建的对象和值的地方(每一个执行上下文都会有自己的一个变量对象,函数私有上下文叫 AO 活跃对象,但也是变量对象)。
GO 全局对象
:他是一个堆内存(存储的都是浏览器内置的 api 属性方法),在浏览器端,让 window 指向它
VO(G)全局变量对象
:全局上下文中用来存储全局变量的空间,他不是 GO=》只不过某些情况下 VO(G)中的东西会和 GO 中的东西有所关联而已;
函数执行过程:
- 函数执行的时候,形成一个全新的私有上下文
EC(FN)
,共字符串代码执行- 进栈执行,从上面进去,把全局往下压
- 私有上下文有私有变量对象 AO(FN),在私有上下文中创建的对象会放到这里来;
- 代码执行之前还需要:
- 1.初始化作用域链(scopeChain):<EC(FN),EC(G)>从自己所在的上下文,结尾段是当前函数创建的时候所在的上下文(是当前函数的作用域)。日后在私有上下文代码执行的时候,遇到一个变量,我们首先看是否是自己的私有变量,是则操作自己的,不是就按照作用域链找上及上下文的…直到找到全局为真。
- 2.初始化 this 指向:window
- 3.初始化实参集合:arguments
- 4.形参赋值
- 5.变量提升
- 6.代码执行
var x = [12, 23];
function fn(y) {
y[0] = 100;
y = [100];
y[1] = 200;
console.log(y); //[100, 200]
}
fn(x);
console.log(x); //[100, 23]
复制代码
4.2 堆栈内存释放
如果堆内存用完后,我们想去手动释放它,则取消所有的占用:赋值为NULL(NULL是空对象指针,也就是不指向任何的堆内存);
//=>创建一个引用类型值,就会产生一个堆内存
//如果当前创建的堆内存不被其它东西所占用了(浏览器会在空闲的时候,查找每一个内存的引用状况,不被占用的都会给回收释放掉),则会释放
let obj = {
name : 'xiaozhima'
};
let oop = obj;
//此时obj和oop都占用着对象的堆内存,想要释放堆内存,需要手动解除变量和值的关联(null:空对象指针)
obj = null;
oop = null;
复制代码
栈内存销毁:
全局栈内存:关掉页面的时候才会销毁
私有栈内存:1.一般情况下,函数只要执行完成,形成的私有栈内存就会被销毁释放掉(排除出现无限极递归、出现死循环的模式)
2.但是一旦栈内存中的某个东西(一般都是堆地址)被私有作用域以外的事物给占用了,则当前私有栈内存不能立即被释放销毁(特点:私有作用域中的私有变量等信息也保留下来了=>这种函数执行形成不能被释放的私有栈内存,也叫做闭包)
- 图解堆栈内存(引用别人的)
5.运输符
5.1 算数运算符
5.1.1 一元运算符++,--
一元运算符也叫是自增自减运算符
自增运算符: 可以快速的对一个变量中保存的数据进行+1操作
自减运算符: 可以快速的对一个变量中保存的数据进行-1操作
自增和自减写在变量的前面和后面的区别?写在变量的后面, 表示变量先参与其它的运算, 然后再自增或者自减;
写在变量的前面, 表示变量先自增或者自减, 然后再参与其它的运算;
let num = 1;
let res1 = num++ + 1; // 第一步:let res1 = num + 1; 第二步:num++; 2
console.log("num", num);//num 2
console.log("res2", res1); // res2 2
复制代码
let num = 1;
let res2 = ++num + 1; // 第一步:num=num+1; 第二步:res2=num+1
console.log("num", num); //num 2
console.log("res2", res2); // res2 3
复制代码
5.1.2 二元运算符+,-,*,/,%
1.加法运算的注意点:
- 1.1任何非数值类型的数据在参与加法运算之前, 都会被自动的转换成数值类型之后, 再参与运算
- 1.2任何数据和NaN进行运算, 结果都是NaN
- 1.3任何数据和字符串相加, 都会被先转换成字符串之后再运算
2.减法运算的注意点:
- 2.1任何非数值类型的数据在参与加法运算之前, 都会被自动的转换成数值类型之后, 再参与运算
- 2.2任何数据和NaN进行运算, 结果都是NaN
- 2.3任何数据和字符串相减, 都会先把字符串转换成数值类型之后再运算
3.乘法和除法运算的注意点 和减法运算的注意点一模一样
4.取模(取余)运算注意点:
- 格式: m%n = 余数
- 4.1如果m>n的, 那么就正常取余
- 4.2如果m<n的, 那么结果就是m
- 4.3如果n是0, 那么结果就是NaN
- 4.4取余运算结果的正负性, 取决于m而不是n
5.2 赋值运算符=、+=、-=、*=、/=
let a = 20;
console.log("初始值a", a); //20
a += 10;
console.log("a+=10", a); // 30;
var a = b = 3;
/*
实际是以下声明的简写:
b = 3;
var a = b;
*/
复制代码
5.3 字符串运算符+、+=
字符串只能进行
“连接”
运算,不能进行其它运算。
let a = "520";
a += 1314;
console.log(a); //5201314
复制代码
5.4 相等运算符(== 、===和!==、!===)
- 1.当使用
==
来比较两个值时,如果值的类型不同,则会自动进行类型转换,将其转换为相同的类型,然后再比较。
console.log(undefined==null);//true
console.log(null == 0); // 值为false
console.log(undefined == 0); // 值为false
console.log(isNaN(NaN)); // 值为true
复制代码
- 2.如果一个是对象,另一 个是数值或字符串,把对象转换成基础类型的值再比较。利用它的toString或者valueOf方法。js核心内置类,会尝试 valueOf先于toString;例外的是Date,Date利用的是toString转换。
- 3.
!=
用来判断两个值是否不相等,如果不相等返回true,反之返回false;- 4.
===
表示全等于(类型和值);
5.4.1经典例子:对象数组转化为基本数据类型
面试题:a等于什么值会让下面条件成立?
var a = ?;
if (a == 1 && a == 2 && a == 3) {
console.log('OK');
}
//解析
//a不是一个简单的基本类型值,若a是一个对象,对象
假设:var a = {
i:0,
toString(){
return ++this.i;
}
}
也可以用数组来解:
var a=[1,2,3];
a.toString=a.shift;//让a的私有属性toString等于Array原型上的shift:每次执行删除数组的第一项,并返回删除项
数据劫持(只能劫持对象中的一个属性):
在每一次获取a(windows)的时候,我们把它劫持,返回我们需要的值;
var i = 0;
Object.defineProperty(window,'a',{
//每一次获取到windows下的a就会调用get方法
//第一次获取a的值就++i,赋值给a
get(){
return ++i;
}
})
复制代码
5.5关系运算符>、<、>=、<=
- 1.两个操作数都是数值,则执行数值比较
- 2.如果有一个操作数是数值或布尔值,则转化为数值数值比较
- 3.两个操作数都是字符串, 则比较两个字符串对应的ascii码
- 4.如果一个操作数是对象,将对象隐式转换后再比较。
- 5.如果一个操作数非数值类型,则将其转换为数字类型再比较
5.6 逻辑运算符||(或),&&(与),!(非)
在传统的编程中,逻辑或仅能够操作布尔值。如果参与运算的任意一个参数为 true,返回的结果就为 true,否则返回 false。
5.6.1 特点
- 与 && :一假则假
- 或 ||:一真则真
- 非 !: 真变假, 假变真
if (1 || 0) { // 工作原理相当于 if( true || false )
console.log('11111')//11111
}
/*-----------------------------------------------------------------------*/
let hour = 9;
if (hour < 10 || hour > 18) {
console.log("我被执行了");//我被执行了
}
/*-----------------------------------------------------------------------*/
复制代码
5.6.2 优先级和结合性
逻辑运算符的结合性是左结合性(从左至右的运算)
在逻辑运算中&&的优先级高于||
5.6.3 逻辑短路现象
- 2.1 在逻辑运算中如果
不是布尔类型
, 那么会先转换成布尔类型, 再参与其它的运算;- 2.2在逻辑
与运算
中, 如果参与运算的不是布尔类型, 返回值有一个特点:
- 格式: 条件A && 条件B
- 如果条件A不成立, 那么就返回条件A
- 如果条件A成立, 无论条件B是否成立, 都会返回条件B
因为
在逻辑与运算中,有一个逻辑短路
现象:由于逻辑与运算的规则是一假则假, 所以只要条件A是假, 那么条件B就不会运算
- 2.3在逻辑
或运算
中, 如果参与运算的不是布尔类型, 返回值有一个特点:
- 格式: 条件A || 条件B
- 如果条件A成立, 那么就返回条件A
- 如果条件A不成立, 无论条件B是否成立, 都会返回条件B
因为
在逻辑或运算中,有一个逻辑短路
现象:由于逻辑或运算的规则是一真则真, 所以只要条件A是真, 那么条件B就不会运算
let x;
true || (x = 1);
alert(x); // undefined,因为 (x = 1) 没有被执行
/*-------------------------------------------------------------------------*/
let x;
false || (x = 1);
alert(x); // 1
复制代码
5.6.4!(非)
感叹符号 ! 表示布尔非运算。
将操作数转化为布尔类型:true/false。
返回相反的值。
5.7 剩余/扩展运算符(...)
- 剩余/扩展运算符同样也是ES6一个非常重要的语法,使用3个点(…),后面跟着一个含有iterator接口的数据结构。
5.7.1 在字符串或数组中使用
console.log(..."array");//a r r a y
console.log(...[1,2,3]) // 1 2 3
console.log(1,...[2,3,4],5) //1 2 3 4 5
[...document.querySelectorAll('div')] //div div div
复制代码
数组拷贝(copy)
var arr = [1, 2, 3];
var arr2 = [...arr];
arr2.push(4);
console.log(arr, arr2);//[1, 2, 3] [1, 2, 3, 4]
复制代码
连接多个数组
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
//var arr3 = arr1.concat(arr2);和下面一句一样
var arr3 = [...arr1, ...arr2];
复制代码
5.7.2 在函数中使用
function fn(x,y){
return x + y
}
let arr =[1,2]
fn(...arr) //3
复制代码
在函数调用时使用展开语法等价于apply的方式:
function myFunction(x, y, z) { }
var args = [0, 1, 2];
//myFunction.apply(null, args);这句和下句是一样的
myFunction(...args);
复制代码
在 new 表达式中应用:
var dateFields = [1970, 3, 10]; // 1970年4月10日
var d = new Date(...dateFields);
console.log(d);//1970年4月10日
复制代码
5.7.3 在对象中使用
构造字面量对象时,进行克隆或者属性拷贝
var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };
var clonedObj = { ...obj1 };
// 克隆后的对象: { foo: "bar", x: 42 }
var mergedObj = { ...obj1, ...obj2 };
// 合并后的对象: { foo: "baz", x: 42, y: 13 }
复制代码
5.8 三元运算符?
三目运算符格式
条件表达式 ? 结果A : 结果B;
在三目运算符中当条件为真的时候, 就会返回结果A
在三目运算符中当条件为假的时候, 就会返回结果B
复制代码
4.逻辑语句
4.1 if语句
//方案一:
if(条件表达式){
//当条件表达式为真满足执行的语句;
}
//方案二:
if(条件表达式){
条件成立执行的语句;
}else{
条件不成立执行的语句;
}
//方案三:
if(条件表达式A){
条件A满足执行的语句;
}else if(条件表达式B){
条件B满足执行的语句;
}
... ...
else{
前面所有条件都不满足执行的语句;
}
复制代码
注意:
1.对于非布尔类型的数据, 会先转换成布尔类型再判断
2.对于==/===判断, 将常量写在前面,这样做的目的是防止写成“=”变成赋值语句了,最后找不到错误。
3.if/else if/else后面的大括号都可以省略, 但是省略之后只有紧随其后的语句受到控制
4.在JavaScript中分号(;)也是一条语句(空语句),写了分号,后面两条语句都不受控制;
5.if选择结构可以嵌套使用
6.当if选择结构省略大括号时, else if/else会自动和距离最近没有被使用的if匹配
if(0)
if(1)
console.log("A");
else //这个else和if(1)匹配
console.log("B");
else //距离最近的if(1)被其他的else匹配了,他只能匹配if(0)
if (1)
console.log("C");
else //这个else和if(1)匹配
console.log("D");
//结果输出C
复制代码
4.2 switch 语法
switch(表达式){
case 表达式A:
语句A;
break;
case 表达式B:
语句B;
break;
... ...
default:
前面所有case都不匹配执行的代码;
break;
}
**switch特点:**
会从上至下的依次判断每一个case里面的表达式是否和switch(表达式)中表达式的结果相 等, 如果相等就执行对应case后面的代码, 如果前面所有的case都不匹配, 那么就会执行default后面的代码,并且所有的case和default只有一个会被执行, 并且只会被执行一次
let day = 7;
switch (day) {
case 1:
console.log("星期1");
break;
case 2:
console.log("星期2");
break;
case 3:
console.log("星期3");
break;
default:
console.log("Other");//执行这个打印other
break;
}
**注意事项:**
1.case 判断的是===, 而不是==,数据和数据类型都要相同
2.()中可以是常量也可以是变量还可以是表达式
3.case 后面可以是常量也可以是变量还可以是表达式
4.break 的作用是立即结束整个 switch 语句
在 switch 语句中一旦 case 或者 default 被匹配, 那么其它的 case 和 default 都会失效
5. switch 中的 default 无论放到什么位置, 都会等到所有 case 都不匹配再执行 6.和 if/else 中的 else 一样, default 也可以省略
javaScript-if和switch如何选择?
在企业开发中如果是对区间进行判断, 那么建议使用if
在企业开发中如果是对几个固定的值的判断, 那么建议使用switch
原则: 能用if就用if
复制代码
4.3 while 循环语法
while(条件表达式){
条件满足执行的语句;
}
特点: 只有条件表达式为真才会执行后面{}中的代码,大括号中的代码有可能会被执行多次
while的执行流程:
1首先会判断条件表达式是否为真, 如果为真就执行后面{}中的代码
2执行完后面{}中的代码, 会再次判断条件表达式是否还为真
3如果条件表达式还为真, 那么会再次执行后面{}中的代码
4重复1~3, 直到条件表达式不为真为止
书写循环结构的规则
1.不管三七二十一先写上循环结构的代码
2.将需要重复执行的代码拷贝到{}中
3.再()中指定循环的结束条件
if的特点: 只有条件表达式为真才会执行后面{}中的代码,大括号中的代码只会被执行一次
注意点:
1.while 循环结构中条件表达式为真的为死循环
2.和 if 一样对于非 Boolean 类型的值, 会先转换为 Boolean 类型再判断
3.和 if 一样 while 后如果只有一条语句它可以省略大括号
和 if 一样如果省略了后面的{}, 那么只有紧随其后的那条语句受到控制
复制代码
while 练习: 1.打印 1 ~ 100 之间 7 的倍数,并统计个数
let num = 1;
let count = 0;
while (num <= 21) {
// console.log(num);
if (num % 7 === 0) {
console.log(num);
count++;
}
num++;
}
console.log(count);
复制代码
while 练习: 2.提示用户输入一个正整数 n, 计算 1 + 2 + 3 + …n 的和
- 规律:
-
- 1.每次都是使用上一次的和加上当前的一个数
-
- 2.每次加上的这个数都是一个递增的数据
// 1.定义一个变量保存上一次相加的和
let sum = 0;
// 2.定义一个变量保存每次递增的那个数
let num = 1;
// 3.利用上一次的和加上递增的数
while (num <= 5){
sum = sum + num; // sum = 1 + 2
num++; // 3
}
console.log(sum);
复制代码
4.4 do…while 循环
1. while循环的特点: 只有条件表达式为真, 才会执行循环体
2.dowhile循环的格式
do{
需要重复执行的代码;
}while(条件表达式);
3.dowhile循环的特点: 无论条件表达式是否为真, 循环体都会被执行一次
复制代码
需求: 要求用户输入密码, 判断输入密码是否正确(假设正确密码是 123456),如果正确, 输出”欢迎回来”,如果不正确, 要求用户重新输入
let pwd = prompt("请输入密码");
while (pwd !== "123456") {
pwd = prompt("请输入密码");
}
alert("欢迎回来");
复制代码
let pwd = -1;
do{
pwd = prompt("请输入密码");
}while (pwd !== "123456");
alert("欢迎回来");
复制代码
- 1.在企业开发中大部分情况下 while 循环和 dowhile 循环是可以互换的
- 2.在企业开发中如果循环体中的代码无论如何都需要先执行一次, 那么建议使用 dowhile 循环
- 3.在企业开发中其它的情况都建议使用 while 循环
4.5 for 循环
1.for循环的格式
for(初始化表达式;条件表达式;循环后增量表达式){
需要重复执行的代码;
}
2.for循环的特点
for循环的特点和while循环的特点一样, 只有条件表达式为真, 才会执行循环体
3.for循环的执行流程
3.1首先会执行初始化表达式, 并且只会执行一次
3.2判断条件表达式是否为真, 如果条件表达式为真, 就执行循环体
3.3执行完循环体就会执行循环后增量表达式
3.4重复3.2~3.3, 直到条件表达式不为真为止
复制代码
let num = 1;
while (num <= 10){
console.log("发射子弹" + num);
num++;
}
复制代码
let num = 1;
for(let num = 1;num <= 10;num++){
console.log("发射子弹" + num);
}
复制代码
javaScript-for 和 while 如何选择
- 如果是while循环, 在循环结束之后还可以使用用于控制循环结束的变量
- 如果是 for 循环, 在循环结束之可以让外界使用, 也可以不让外界使用,在企业开发中由于 for 循环比 while 循环要灵活, 所以能用 for 循环就用 for 循环;
4.6 break和continue
break 关键字
- 1.break 关键字可以用于 switch 语句和循环结构中,作用是立即结束当前的循环结构
- 2.break 关键字的注意点
-
- 2.1break 关键字后面不能编写任何的语句, 因为永远执行不到
-
- 2.2 如果在循环嵌套的结构中, break 结束的是当前所在的循环结构
continue 关键字
- 1.什么是continue关键字?
-
- continue关键字只能用于循环结构
-
- 在循环结构中continue关键字的作用是跳过本次循环, 进入下一次循环
-
for(let num = 1; num <= 10; num++){
if(num === 1){
continue;
console.log("continue后面的代码"); // 永远执行不到
}
console.log("发射子弹" + num);
}
*/
for(let i = 0; i < 5; i++){
console.log("外面的循环结构" + i);
for(let j = 0; j < 5; j++){
if(j === 1){
continue;
}
console.log("里面的循环结构-----" + j);
}
}
复制代码
4.7 循环嵌套规律
在循环嵌套中外循环控制的是行数, 内循环控制的是列数
for(let j = 0; j < 3; j++){
for(let i = 0; i < 4; i++){
document.write("*");
}
document.write("<br>");
}
复制代码
循环嵌套练习 1
需求: 在界面中输出如下图形
00000
0000
000
00
0
复制代码
//规律: 如果尖尖朝下,那么只需要修改内循环的初始化表达式为外循环初始化表达式的变量即可;
for (let i = 0; i < 5; i++) {
console.log(i);
for (let j = i; j < 5; j++) {
document.write("*");
}
document.write("<br>");
}
复制代码
循环嵌套练习 2
需求: 在界面中输出如下图形
0
00
000
0000
00000
复制代码
//规律: 如果尖尖朝上,那么只需要修改内循环的条件表达式为外循环初始化表达式的变量即可;
for (let i = 0; i < 5; i++) {
console.log(i);
for (let j = 0; j <= i; j++) {
document.write("*");
}
document.write("<br>");
}
复制代码