JS 基础知识

406 阅读8分钟

代码结构

js 将换行符理解成“隐式”的分号。这也被称为 自动分号插入

alert('Hello')
alert('World')

例外:

//如果一行以加号 "+" 结尾,这是一个“不完整的表达式”,js认为不需要分号。
alert(3 +
1
+ 2);
//结果输出 6 

alert("All fine now");
[1, 2].forEach(alert)
//输出All fine now ,然后报错
//在[] 前面js也认为是 不完整表达式
(function() {
  'use strict';
    let a = 'dd'
})()

严格模式

use strict,必须在最顶部才有全局效果 控制台 需要 使用自执行函数

(function() {
  'use strict';
    a = '111'  ///会报错
  // ...你的代码...
})()

变量命名

变量名称必须仅包含字母,数字,符号 $ 和 _。 首字符必须非数字。 允许非英文字母,但不推荐,如 let 李 = 'x'

数据类型

7 种原始类型和 1 种引用类型

Number

常规的数字 和 特殊数值 Infinity、-Infinity 和 NaN Infinity 是 在数学里是无穷大 ,可以有正负

1 / 0// Infinity
Infinity//  可以直接访问

NaN 代表一个计算错误。它是一个不正确的或者一个未定义的数学操作所得到的结果

"not a number" / 2// NaN,这样的除法是错误的
"not a number" / 2 + 5// NaN

BigInt

尾部的 "n" 表示这是一个 BigInt 类型

const bigInt = 1234567890123456789012345678901234567890n;

String字符串

  • 双引号:"Hello".
  • 单引号:'Hello'.
  • 反引号:`Hello`.
  • js 没有char
//可以使用 ${} 做运算  
let a = '张三'
`the result is ${a} ${1 + 2}`// the result is 3

Boolean 布尔类型

“null” 值

  • 特殊的 null 值不属于上述任何一种类型,它构成了一个独立的类型,只包含 null 值:
  • let age = null;
  • 相比较于其他编程语言,JavaScript 中的 null 不是一个“对不存在的 object 的引用”或者 “null 指针”。
  • JavaScript 中的 null 仅仅是一个代表“无”、“空”或“值未知”的特殊值。
  • 上面的代码表示 age 是未知的。

undefined

  • 特殊值 undefined 和 null 一样自成类型。
  • undefined 的含义是 未被赋值。
  • 如果一个变量已被声明,但未被赋值,那么它的值就是 undefined:
let age;
alert(age); // 弹出 "undefined"

let age = 100;
// 将值修改为 undefined
age = undefined;
alert(age); // "undefined"

symbol 用于唯一的标识符。

object 复杂数据类型

typeof

typeof 运算符返回参数的类型 作为运算符:typeof x。 函数形式:typeof(x)。

typeof undefined // "undefined"
typeof 0 // "number"
typeof 10n // "bigint"
typeof true // "boolean"
typeof "foo" // "string"
typeof Symbol("id") // "symbol"
typeof Math // "object"  (1)
typeof null // "object"  (2)  // js的bug,null 绝对不是一个 object。null 有自己的类型,它是一个特殊值。
typeof alert // "function"  (3)

  • typeof alert 的结果是 "function",
  • 因为 alert 在 JavaScript 语言中是一个函数。我们会在下一章学习函数, JavaScript 语言中没有一个特别的 “function” 类型。
  • 函数隶属于 object 类型。但是 typeof 会对函数区分对待,并返回 "function"。
  • 这也是来自于 JavaScript 语言早期的问题。从技术上讲,这种行为是不正确的,但在实际编程中却非常方便。

类型转换

  • 定义:运算符和函数会自动将赋予它们的值转换为正确的类型
  • 运算符如:+ / ,会显示调用Number(内容), "1" + "2" => Number("1") + Number("2")
  • 函数如:alert ,会显示调用String(内容) , alert(1) => alert("1")

基本数据类型转换

  1. 字符串类型 String(value)

  2. 数字类型

  • Number(value)
  • undefined => NaN
  • null => 0
  • true / false => 1 / 0
  • string => “按原样读取”字符串,两端的空白会被忽略。空字符串变成 0。转换出错则输出 NaN。
  1. 布尔类型
  • 0, null, undefined, NaN, "" => false
  • 其他值 , " "(包括空格的字符串) => true
    let a 
    if (a) { //false
        
    }
    if("") { //false
        
    }
    if(" ") { //true
        
    }

基础运算符,数学

一元运算符

x = -x;
alert( x ); // -1,一元负号运算符生效

二元运算符”

let x = 1, y = 3;
alert( y - x ); // 2,二元运算符减号做减运算

数学运算:

加法 +, 减法 -, 乘法 *, 除法 /,

取余 %

a % b 的结果是 a 整除 b 的 余数
alert( 5 % 2 ); // 1,5 除以 2 的余数
alert( 8 % 3 ); // 2,8 除以 3 的余数

求幂 **

其实就是平方,也支持平方根

//平方
alert( 2 ** 2 ); // 4  (2 * 2,自乘 2 次)
alert( 2 ** 3 ); // 8  (2 * 2 * 2,自乘 3 次)
alert( 2 ** 4 ); // 16 (2 * 2 * 2 * 2,自乘 4 次)
//平方根
alert( 4 ** (1/2) ); // 2(1/2 次方与平方根相同)
alert( 8 ** (1/3) ); // 2(1/3 次方与立方根相同)

二元运算符 + 连接字符串

原则:有字符串,优先转String(value), 其他走Number(value)

alert( '1' + 2 ); // "12"
alert( 2 + '1' ); // "21"
alert(2 + 2 + '1' ); //  "41",不是 "221"
alert( true + true ); // 2

二元运算符 - * /

- * / 都是Number(value) 处理

一元运算符 +

数字转化

// 对数字无效
let x = 1;
alert( +x ); // 1

let y = -2;
alert( +y ); // -2

// 转化非数字
alert( +true ); // 1
alert( +"" );   // 0

优先级

优先级名称符号
17一元加号+
17一元负号-
16求幂**
15乘号*
15除号/
13加号+
13减号-
3赋值符=
1逗号,

链式赋值

let a, b, c;

a = b = c = 2 + 2;

alert( a ); // 4
alert( b ); // 4
alert( c ); // 4

原地修改

let n = 2;
n = n + 5;
//等价于 运算符 +=
let n = 2;
n += 5;

自增 / 自减

前置形式返回一个新的值,但后置返回原来的值(做加法/减法之前的值)。

let counter = 0;
alert( ++counter ); // 1

let counter = 0;
alert( counter++ ); // 0

let counter = 1;
alert( 2 * counter++ ); // 2,因为 counter++ 返回的是“旧值”

注意:5++ 是错误

位运算符

  • 位运算符把运算元当做 32 位整数,并在它们的二进制表现形式上操作。
  • 按位与 ( & )
  • 按位或 ( | )
  • 按位异或 ( ^ )
  • 按位非 ( ~ )
  • 左移 ( << )
  • 右移 ( >> )
  • 无符号右移 ( >>> )

值的比较

所有比较运算符均返回布尔值:

字符串比较

首位比较,如果相同继续比较,比较根据 Unicode 编码顺序

'Z' > 'A'// true
'Glow' > 'Glee'// true
'Bee' > 'Be'// true

不同类型间的比较

JS 会首先将其转化为数字(number)再判定大小

'2' > 1// true,字符串 '2' 会被转化为数字 2
'01' == 1// true,字符串 '01' 会被转化为数字 1
'a' == 1//永远都是false  因为 'a' => Number('a') = NaN , NaN 跟任何数字判断都为 false
'a' > 1//false

'0' == false//true
'' == false//true

严格相等

'' === false  // false  

null 和 undefined 进行比较

undefined 和 null 在相等性检查 == 中不会进行任何的类型转换,

"> < >= <= " 这些是正常转化 Number, Number(null) => 0 Number(undefined) => NaN

null === undefined  // false
null == undefined  // false //js特殊处理了, 只在非严格相等下成立

null < undefined //false  会正常转化成 数字,Number(null) => 0  Number(undefined) => NaN 

null > 0  // (1) false
null == 0  // (2) false
null >= 0 // (3) true

"0" == +"\n0\n" //true 这里 +先执行 Number(“\n0\n”) => 0 ,然后 "0" == 0 比较,Number("0") == 0 

总结

  • 除了严格相等 === 外,其他但凡是有 undefined/null 参与的比较,我们都需要格外小心。

  • 除非你非常清楚自己在做什么,否则永远不要使用 >= > < <= 去比较一个可能为 null/undefined 的变量。对于取值可能是 null/undefined 的变量,请按需要分别检查它的取值情况。

  • 比较运算符始终返回布尔值。

  • 字符串的比较,会按照“词典”顺序逐字符地比较大小。

  • 当对不同类型的值进行比较时,它们会先被转化为数字(不包括严格相等检查)再进行比较。

  • 在非严格相等 == 下,null 和 undefined 相等且各自不等于任何其他的值。

  • 在使用 > 或 < 进行比较时,需要注意变量可能为 null/undefined 的情况。比较好的方法是单独检查变量是否等于 null/undefined。

条件分支

? 三元运算法,优先级比较低

let age = 1
let a = (age > 18) ? true : false;  //
//括号可以省略
let a = age > 18 ? true : false;

多个 ‘?’

let age = prompt('age?', 18);

let message = (age < 3) ? 'Hi, baby!' :
  (age < 18) ? 'Hello!' :
  (age < 100) ? 'Greetings!' :
  'What an unusual age!';

alert( message );
//等价于
if (age < 3) {
  message = 'Hi, baby!';
} else if (age < 18) {
  message = 'Hello!';
} else if (age < 100) {
  message = 'Greetings!';
} else {
  message = 'What an unusual age!';
}

逻辑运算符

判断时候 会自动做Boolean(value) 转化

||(或)

1.或运算寻找第一个真值

  • result = value1 || value2 || value3;
  • 从左到右依次计算操作数。
  • 处理每一个操作数时,都将其转化为布尔值。如果结果是 true,就停止计算,返回这个操作数的初始值。
  • 如果所有的操作数都被计算过(也就是,转换结果都是 false),则返回最后一个操作数。
//中间true 直接返回中间值
let firstName = '' ,lastName = 'bbb' ,lastName = '' ;
firstName || lastName || lastName || "aaa"); // bbb

//中间前面都是false ,直接返回最后一个
let firstName = '' ,lastName = '' ,lastName = '' ;
firstName || lastName || lastName || "aaa"); // aaa

1.短路求值(Short-circuit evaluation)

操作数可以是:变量赋值或函数调用

true || alert("not printed");
false || alert("printed");
  • 在第一行中,或运算符 || 在遇到 true 时立即停止运算,所以 alert 没有运行。
  • 有时,人们利用这个特性,只在左侧的条件为假时才执行命令。

&&(与)

与运算寻找第一个假值

 1 && 2 && 3 // 输出 3

同时存在 && 和 ||

可以使用 ( 1 && 2 ) 把 "与"内容包裹 如:

0 || 2 && 0 || 3 && 4 
//等价于
0 || (2 && 0) || (3 && 4)  // => 0 || 0 || 4  结果为 4

!(非)

Boolean(value) 返回相反值 ,优先级在所有逻辑运算符里面最高,在&& 和 || 之前执行。 两个非运算 !! 有时候用来将某个值转化为布尔类型:

alert( !!"non-empty string" ); // true
alert( !!null ); // false

空值合并运算符 '??' (新符号)

第一个参数不是 null/undefined,则 ?? 返回第一个参数。否则,返回第二个参数。

let a,b;
a ?? b
//等价于
(a !== null && a !== undefined) ? a : b;

//连续判断
let firstName = null;
let lastName = null;
let nickName = "Supercoder";
// 显示第一个已定义的值:
alert(firstName ?? lastName ?? nickName ?? "Anonymous"); // Supercoder

?? 与 || 比较

let height = 0;
alert(height || 100); // 100 // Boolean(0) = false
alert(height ?? 100); // 0  // 不是undefined 也不是null     

优先级

?? 在 = 和 ? 之前计算,但在大多数其他运算符(例如,+ 和 *)之后计算。

出于安全原因,JS 禁止将 ?? 运算符与 && 和 || 运算符一起使用,除非使用括号明确指定了优先级。

let x = 1 && 2 ?? 3; // Syntax error
let x = 1 && (2 ?? 3); // 正常

循环:while 和 for

while

while (i != 0) 可简写为 while (i):

let i = 3;
while (i) { // 当 i 变成 0 时,条件为假,循环终止
 alert( i );
 i--;
}
//单行循环体不需要大括号
while (i) alert(i--);

“do…while” 循环

let i = 0;
do {
  alert( i );
  i++;
} while (i < 3);

for

//内联变量声明
for (let i = 0; i < 3; i++) {
  alert(i); // 0, 1, 2
}
alert(i); // 错误,没有这个变量。
//可以通过外部定义
let i = 0;
for (i = 0; i < 3; i++) { // 使用现有的变量
  alert(i); // 0, 1, 2
}
alert(i); //3,可见,因为是在循环之外声明的

//省略语句段
//省略begin
let i = 0; // 我们已经声明了 i 并对它进行了赋值
for (; i < 3; i++) { // 不再需要 "begin" 语句段
  alert( i ); // 0, 1, 2
}
//省略step
let i = 0;
for (; i < 3;) {
  alert( i++ );
}
//等价于
while (i < 3)

//省略所有
for (;;) {
  // 无限循环
}

break/continue

break 终止循环 , continue 继续下一个循环 禁止 break/continue 在 ‘?’ 的右边

 (i > 5) ? alert(i) : continue; // continue 不允许在这个位置 

标签 xxx:

xxx: 定义 ,break outer必须要循环内部

outer: for (let i = 0; i < 3; i++) {
  for (let j = 0; j < 3; j++) {
    let input = prompt(`Value at coords (${i},${j})`, '');
    // 如果是空字符串或被取消,则中断并跳出这两个循环。
    if (!input) break outer; // (*)
    // 用得到的值做些事……
  }
}
alert('Done!');

//也可以换行声明
outer: 
for (let i = 0; i < 3; i++) {...}

题目:重复输入,直到正确为大于100的值为止

let input  
do {
  input = prompt("请输入大于100数字")
}while(input <= 100 && input)

题目 :输出素数(prime)

let n = 10
outer: for(let i = 2;i <= n;i++){ 
  let j = 2;
  while(j<i ){
    if(i%j === 0) { //当前不是素数
        //  console.log("当前不是素数",i)
        continue outer;
    }
     j++
  }
  console.log("素数",i)
}

switch

  • 如果相等,switch 语句就执行相应 case 下的代码块,直到遇到最靠近的 break 语句.
  • 如果没有符合的 case,则执行 default 代码块(如果 default 存在)。
//case的内容是 严格匹配 === 
let a = 2
switch(a) {
    case '2': alert("ddd")
}
//无效 2 === '2' => false

任何表达式都可以成为 switch/case 的参数

let a = "1";
let b = 0;

switch (+a) {
  case b + 1:
    alert("this runs, because +a is 1, exactly equals b+1");
    break;

  default:
    alert("this doesn't run");
}

case 分组

let a = 3;

switch (a) {
  case 4:
    alert('Right!');
    break;

  case 3: // (*) 下面这两个 case 被分在一组
  case 5:
    alert('Wrong!');
    alert("Why don't you take a math class?");
    break;

  default:
    alert('The result is strange. Really.');
}

函数

局部变量

function showMessage() {
  let message = "Hello, I'm JavaScript!"; // 局部变量
  alert( message );
}
showMessage(); // Hello, I'm JavaScript!
alert( message ); // <-- 错误!变量是函数的局部变量

外部变量

let userName = 'John';
function showMessage() {
  let message = 'Hello, ' + userName;
  alert(message);
}
showMessage(); // Hello, John

局部变量 和 外部变量 同时存在,优先使用内部

参数

函数可以修改参数,但在函数外部看不到更改,函数修改的是复制的变量值副本:

function showMessage(from, text) {
  from = '*' + from + '*'; // 让 "from" 看起来更优雅
  alert( from + ': ' + text );
}
let from = "Ann";
showMessage(from, "Hello"); // *Ann*: Hello
// "from" 值相同,函数修改了一个局部的副本。
alert( from ); // Ann //值没有变化

默认值

没有设置参数,默认值则是 undefined。

//可以指定默认
function showMessage(from, text = "no text given") {
  alert( from + ": " + text );
}
showMessage("Ann"); // Ann: no text given

后备的默认参数

//1 自己判断
function showMessage(text) {
  if (text === undefined) {
    text = 'empty message';
  }
  alert(text);
}
showMessage(); // empty message

//2.|| 运算符
function showMessage(text) {
  text = text || 'empty';
  ...
}

//3.空值合并运算符 ??
function showCount(count) {
  alert(count ?? "unknown");
}
showCount(0); // 0
showCount(null); // unknown
showCount(); // unknown

return

不要在 return 与返回值之间添加新行

空值的 return 或没有 return 的函数返回值为 undefined

函数命名

函数就是行为(action)。所以它们的名字通常是动词。

  • 一个函数 一个行为
  • 不要用简写或者特殊符号

函数表达式

在 JavaScript 中,函数不是“语言结构”,而是一种特殊的值。

函数声明

function sayHi() {
  alert( "Hello" );
}

函数表达式

let sayHi = function() {
  alert( "Hello" );
};

注意结尾必须加上 不然可能会与后面的语句关联执行

在这里,函数被创建并像其他赋值一样,被明确地分配给了一个变量。

回调函数

function ask(question, yes, no) {
  if (confirm(question)) yes()
  else no();
}
​
function showOk() {
  alert( "You agreed." );
}
​
function showCancel() {
  alert( "You canceled the execution." );
}
​
// 用法:函数 showOk 和 showCancel 被作为参数传入到 ask
ask("Do you agree?", showOk, showCancel);

ask 的两个参数值 showOkshowCancel 可以被称为 回调函数 或简称 回调

匿名函数

function ask(question, yes, no) {
  if (confirm(question)) yes()
  else no();
}
​
ask(
  "Do you agree?",
  function() { alert("You agreed."); },
  function() { alert("You canceled the execution."); }
);

这里直接在 ask(...) 调用内进行函数声明。这两个函数没有名字,所以叫 匿名函数

这样的函数在 ask 外无法访问(因为没有对它们分配变量)

函数声明与函数表达式的区别

函数声明

是全局的

  • 当 JavaScript 准备 运行脚本时,首先会在脚本中寻找全局函数声明,并创建这些函数。
  • 我们可以将其视为“初始化阶段”。在处理完所有函数声明后,代码才被执行。所以运行时能够使用这些函数。
sayHi("John"); // Hello, John
​
function sayHi(name) {
  alert( `Hello, ${name}` );
}

函数表达式

是声明后,后面的代码有效,下面代码不工作

sayHi("John"); // error!
let sayHi = function(name) {  // (*) no magic any more
  alert( `Hello, ${name}` );
};

代码块下的函数声明

严格模式下,当一个函数声明在一个代码块内时,它在该代码块内的任何位置都是可见的。但在代码块外不可见。

let age = prompt("What is your age?", 18);
// 有条件地声明一个函数
if (age < 18) {
  function welcome() {
    alert("Hello!");
  }
} else {
  function welcome() {
    alert("Greetings!");
  }
}
welcome(); //这里报错

优化代码

let age = prompt("What is your age?", 18);
let welcome;
if (age < 18) {
  welcome = function() {
    alert("Hello!");
  };
} else {
  welcome = function() {
    alert("Greetings!");
  };
}
welcome(); // ok

总结

  • 函数是值。它们可以在代码的任何地方被分配,复制或声明。
  • 如果函数在主代码流中被声明为单独的语句,则称为“函数声明”。
  • 如果该函数是作为表达式的一部分创建的,则称其“函数表达式”。
  • 在执行代码块之前,内部算法会先处理函数声明。所以函数声明在其被声明的代码块内的任何位置都是可见的。
  • 函数表达式在执行流程到达时创建。

箭头函数

函数表达式的另一种方式

let sum = function(a, b) {
  return a + b;
};
//等价于
let sum = (a, b) => a + b;

括号省略

let double = n => n * 2;

参数省略

let sayHi = () => alert("Hello!");

总结

  1. 不带花括号:(...args) => expression — 右侧是一个表达式:函数计算表达式并返回其结果。
  2. 带花括号:(...args) => { body } — 花括号允许我们在函数中编写多个语句,但是我们需要显式地 return 来返回一些内容。

箭头函数高阶应用

1.没有this概念

代码会从最近的上下文获取this

let group = {
  title: "Our Group",
  students: ["John", "Pete", "Alice"],
  showList() {
    this.students.forEach(
      student => alert(this.title + ': ' + student)//这里获取的是 group对象
    );
  }
};
group.showList();

普通函数,则报错

let group = {
  title: "Our Group",
  students: ["John", "Pete", "Alice"],
  showList() {
    this.students.forEach(function(student) { 
      alert(this.title + ': ' + student)//这里会报错
    });
  }
};
group.showList();

2.没有function关键字

  • 箭头函数没有prototype
  • 箭头函数不能用作构造器(constructor)。不能用 new 调用它们。

3. 不绑定arguments

利用箭头没有arg的特性

function defer(f, ms) {
  return function() {
    setTimeout(() => f.apply(this, arguments), ms)
  };
}
function sayHi(who) {
  alert('Hello, ' + who);
}
let sayHiDeferred = defer(sayHi, 2000);
sayHiDeferred("John"); // 2 秒后显示:Hello, John

普通函数

function defer(f, ms) {
  return function(...args) {
    let ctx = this;
    setTimeout(function() {
      return f.apply(ctx, args);
    }, ms);
  };
}