加载页面中...
01-06js基础 | lwstkhyl

01-06js基础

js基础第一部分:变量、数据类型、对象、函数、数组、字符串以及作用域

写在前面:本篇是对之前我的txt版笔记的总结汇总,基于b站课程黑马程序员前端JavaScript入门到精通(例子中使用constlet的部分)以及尚硅谷JavaScript基础&实战(例子中使用var的部分)

基础知识

vscode中创建HTML文件后,先输入一个!,之后按tab即可生成标准格式

js注释:

  • // ctrl+/

  • /* */ shift+alt+a

html注释:<!-- --> ctrl+?

css注释:/* */

alt+b在默认浏览器中执行HTML文件

js代码的编写位置:

  • 写在HTML控件中(不推荐),如

    <button onclick="alert('点击了按钮')">一个按钮</button>
    <a href="javascript:alert('点击了超链接');">一个超链接</a>
    <a href="javascript:;">一个点击后没有任何效果的超链接</a>
    
  • HTML文件的<script>标签,其中放js代码,如<script>alert("xxx")</script>

  • 在HTML文件中引用已编写的js文件,如<script src="01.js"></script>

js中严格区分大小写,每一条语句以分号结尾,分号可省略(不写分号时浏览器也会自动添加)

变量与数据类型

声明变量

使用var/let/const声明变量,如var a;

区别:

  • var可以先使用再声明(未声明时值为undefined)

  • let必须先声明再使用,且不允许重复声明(将同一变量声明多次)

  • const同let,区别是声明的变量无法更改(同c中的const);且必须在声明时初始化,不能以后再赋值

注意:对于数组和对象这种变量保存内存地址的类型,const只保证变量名指向的地址不变,并不保证该地址的数据不变


标识符规则:可含有字母、数字、下划线和$,不能有其它符号,不能以数字开头,不能是js中的关键字/保留字。js底层保存标识符时采用Unicode编码,所以理论上utf-8中含有内容都可作为标识符(包括中文,但不建议使用)

数据类型

使用typeof a来检查变量a的类型

string

用单引号或双引号,一对引号括起来的内容里面不能出现一对同类型引号(引号不能嵌套),可以外双内单或外单内双

转义字符为\

\n表示换行,\t表示tab制表符,\\表示\\"表示",…

使用Unicode编码输出字符:4位16进制编码前加\u,如"\u0031"可以输出0031对应的字符;HTML中在10进制编码前加&#后加分号,如<h1>&#9760;</h1>

number

包括整数和浮点数

数字的最大值:Number.MAX_VALUE,若number超过这个值,则返回infinity正无穷;最小值就是-Number.MAX_VALUE,同理还有-infinity;最小的正小数是Number.MIN_VALUE

正负无穷都是number类型

NaN类型:即not a number的缩写,如若a = “abc" * "abc" ,则a的值就是NaN,它也是number类型

js的小数运算可能不准确

16进制数字以0x开头,如0x10=16 0xff=255;8进制数字以0开头,如070=56; 2进制数字以0b开头(不是所有浏览器都支持)

Boolean

即true和false

Null

只有一个值null,表示一个为空的对象 typeof null返回object

Undefined

只有一个值undefined,表示未定义

当声明一个变量但不给其赋值时,它的值就是undefined

强制类型转换

转为string
  • 调用toString方法,该方法不会改变原变量,而是将改变后的值返回。null和undefined不能使用这种方法转换

    var a = 123;
    var a_str = a.toString(); 
    
  • 调用String()函数,该方法可以将null和undefined转成”null”和”undefined”

    var a_str = String(a);
    
转为number
  • 调用Number()函数

    var a = "123";
    var a_num = Number(a);
    

    字符串->数值:

    • 纯数字的字符串->数字;

    • 有非数字内容的字符串->NaN;

    • 空字符串/只有空格的字符串->0

  • parse系列函数:专门用于字符串

    • parseInt():将一个字符串中有效的整数内容取出(从前往后读取字符串,将数字取出,直至读到不是数字的字符)

      var a = "123px";
      var a_num = parseInt(a);  //a_num为123
      

      “1b23px”->1

      “b123px”->NaN

    • parseFloat():获得小数,规则同parseInt

      “123.456px”->123.456

      “123.4.56px”->123.4

    • 用指定的进制将字符串转为数字:

      var a = "070";
      var a_10 = parseInt(a,10);  //70
      var a_8 = parseInt(a,8);  //56
      

注意:若对非字符串类型使用parse系列函数,会先将其转换为字符串,再执行parse操作。用此特性可以对小数进行取整:parseInt(123.456)->123

转为Boolean

使用Boolean()函数

数字->bool:除了0和NaN其它都转成true

字符串->bool:除了空字符串都是true

null->false

undefined->false

object->true

运算符

字符串的+

string+string/其它类型值:会先将其它类型值转换为字符串(使用String()函数),之后再拼接

一元运算符
  • +不会对数字产生影响,-取相反数(都是转成number取反,因此可以用a = +a将a变为number)

  • 自增自减a++;a--;,前置和后置自增的区别同c(表达式结果不同:a++为a ++a为a+1)

  • !非:先转为Boolean再运算(因此可以用a = !!a将a转为Boolean)

  • &&和||:如果第一个值为false/true就不会算第二个值(同c)。当其两边的值有非Boolean值的时候,会先将其转为Boolean再运算,并返回原值

    返回值的判断:返回的是检查的最后一个值–1&&2判定为true,要检查1和2,所以返回2;0&&NaN判定为false,只需检查0,所以返回0,同理1&&2->2、0&&NaN->0、1||0->1、0&&2->0

  • > < >= <=在非数值性的比较中,会先转化成数值型再比较;任何值和NaN作任何比较结果都是false

    特殊情况:如果符号两侧的值都是字符串,则不会将其转换成数值比较,而会比较字符的编码(一位一位的进行比较,即先比两个string的第一个字符,如果它们相同再比较下一位)

  • ==和!=:当两个值类型不同,会转换成相同类型再比较是否相等(大部分情况下都转为数字)

    特殊情况null==0->false undefined==null->true(undefined衍生自null)

    NaN不和任何值相等(包括NaN),可以通过isNaN(a)函数判断一个值a是否为NaN(该函数会先将a转成数值类型,再判断其是否为NaN,因此isNaN("abc")->true)

  • ===和!==:不会自动进行类型转换,当类型不同时就返回false/true,如undefined===null->false

三元运算符

条件表达式?语句1:语句2:若条件表达式为true执行语句1,反之执行语句2。如果条件表达式的值是非bool值,会先转换成bool值再判断

求最大值:

var max_2 = a > b ? a : b;    
var max_3 = a > b ? (a > c ? a : c) : (b > c ? b : c);

基础语段和常用函数

基础语段

if (条件) {语句}
else if (条件) {语句}
else if (条件) {语句}
else {语句}
switch (a) {    
  case 1: 语句1 break;
  case 2: 语句2 break;
  default: 语句
}
while (条件) {
  循环体
}
do {
  循环体
} while (条件)
for (var i = 0; i < 10; i++) {
  循环体
}

为循环语句创建label标识当前循环:

outer:  //标识下面的外层循环为outer
for(var i=0;i<10;i++)
{
    inner:  //标识下面的内层循环为inner
    for(var j=0;j<10;j++)
    {
        break outer;//终止外层循环
        continue outer;//跳过此次外层循环
    }
}

常用函数

  • alert("弹出警告窗")

    注意:js中代码顺序执行,alert弹出警告窗时,需点击“确定”按钮后才算执行完这条语句,并接着执行之后的代码

  • document.write("在页面上显示"),如document.write("<br/>");换行

  • console.log("在控制台中输出"),在页面中按f12进入控制台

  • prompt("xxx");可以弹出一个提示框,该提示框中会带有一个文本框,用户可以在文本框中输入一段内容,传入的参数”xxx”为提示框的提示文字,该函数返回输入的值(以string形式),如var input = prompt("请输入:");

    使用var input_number = + prompt("请输入:"); 可直接得到数值型输入

    当有多个prompt()时会顺序执行(输入完一个后再弹出下一个提示框)

  • parseInt(a)不仅可以字符串转数值,还可以将double转为int,如parseInt(a/100);获得a的百位数

  • Math.sqrt(a)对a开方

  • 计算某段代码运行时间:

    console.time("test")
    /*要计时的代码*/
    console.timeEnd("test");//在浏览器控制台中可以看到运行时间
    

对象

类似于python中的字典

创建

  • var obj = new Object();

  • var obj = {};

向对象中添加属性:

  • obj.name = "abc";键为name,值为”abc”

  • obj["name"] = "abc";属性名不强制要求遵守标识符规范,但如果使用特殊属性名,需用这种方式创建

  • var obj = {name:"abc", age:20};在创建对象的同时指定对象的属性(常用),键名可以加引号也可以不加,但使用特殊变量名时必须加引号

删除对象属性:delete obj.name;

注意:对象中的值可以是基本数据类型,也可以是一个对象/函数等

获取对象值

  • obj.键名

    var obj = {test:{name:"abc"}}; //对象中的值也可以是一个对象
    console.log(obj.test.name);  //"abc"
    
  • obj[键名](更加灵活)

    var a = "name";
    console.log(obj[a]); //等效于console.log(obj.name)
    console.log(obj.a); //是错误的
    

遍历对象中的属性:

for (var n in obj) {
    console.log("属性名:" + n + "  属性值" + obj[n]);
}

引用数据类型

js中变量都保存到栈内存中

基本数据类型(string number boolean null undefined):他们的变量都是在栈内存内直接存储,变量之间全部独立,修改其中一个变量对其它变量无影响

引用数据类型(object):对象保存到堆内存中,每创建一个新对象,就在堆中开辟出一个新空间,object类型的变量只在栈中保存相应对象的内存地址(对象的引用),若两个变量都指向一个对象,改变其中一个的属性值,会改变另一个的属性值

var obj1 = new Object(); 
obj1.name = "abc";
var obj2 = obj1;
obj2.name = "ABC";  //此时obj1.name也变为"ABC",相当于改变obj2指针指向的值
obj2 = null; //相当于指针置空,不影响obj1的值

当比较两个对象(obj1 == obj)时,比较的时它们的内存地址

类与对象

构造函数:就是一个普通的函数,创建方式与普通函数无差别,习惯上首字母大写;使用new关键字来调用

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

执行流程:

  • new立即创建一个新对象

  • 将新建的对象设为构造函数中的this

  • 执行构造函数中代码

  • 将新建对象this返回

var obj = new Person("abc", 20);
console.log(obj); //Person {name: 'abc', age: 20}

使用同一个构造函数创建的对象称为一类对象,构造函数称为一个类;通过类构造函数创建的对象称为该类的实例。

如上面的Person就是一个类,obj就是它的实例


检查一个对象是否为一个类的实例:对象 instanceof 构造函数名是则返回true,反之为false

所有对象都是object子类任何对象 instanceof Object都返回true

Date对象

用于表示时间

如果直接使用构造函数创建Date对象,则会封装为当前代码执行时间

要创建一个指定的时间对象,就需要在构造函数中传递一个表示时间的字符串为参数,格式:"月/日/年 时:分:秒"

注意,使用中文系统,这样写就是中国标准时间

var date1 = new Date();
console.log(date1); //Mon Jan 29 2024 14:49:45 GMT+0800 (中国标准时间)
var date2 = new Date("12/13/2011 11:12:13");
console.log(date2); //Tue Dec 13 2011 11:12:13 GMT+0800 (中国标准时间)

Date对象的方法:

  • getDate()几号

  • getDay()星期几(0表示周日,1表示周一,…)

  • getMonth()月份(0表示一月,1表示2月,…)

  • getFullYear()年份(4位数形式)

  • getHours()小时

  • getMinutes()分钟

  • getSeconds()秒数

  • getMilliseconds()毫秒数

  • toLocaleString()"2022/4/1 09:08:07"的形式得到表示日期和时间的字符串

  • toLocaleDateString()"2022/4/1"的形式得到表示日期的字符串

  • toLocaleTimeString()"09:08:07"的形式得到表示时间的字符串

  • getTime()/+new Date()获取日期对象的时间戳(从格林威治时间的1970年1月1日0时0分0秒到现日期经过的毫秒数),底层保存时间都是使用的时间戳

  • now();获取该行代码执行时的时间戳

获取指定代码的执行时间:

var start_time = Date.now();
/*要测量的代码*/
var end_time = Date.now();
console.log(end_time-start_time);

Math类

和其它的对象不同,它没有构造函数,属于一个工具类,不需创建对象,封装了数学运算相关的属性方法

  • Math.PI 圆周率

  • Math.E e

  • Math.LN2 ln2

  • Math.LN10 ln10 还有很多

  • Math.abs(a) a的绝对值

还有cos sin tan acos asin atan log求对数 exp求e的x次幂 sqrt开方等等

比较特殊的:

  • Math.ceil(a) 向上取整(小数位只要有值就自动进1–1.0->1 1.1->2)

  • Math.floor(a) 向下取整(只取整数位,小数部分会被舍掉)

  • Math.round(a) 四舍五入取整

  • Math.random() 生成(0,1)的随机数(真随机)

  • Math.max(a,b,...) 多个数中的最大值(同理还有min最小值)

  • Math.pow(x,y) x的y次幂

生成从min到max范围的随机数:

Math.floor(Math.random() * (max - min + 1) + min)  

函数

函数也是一个对象

方法:当函数作为对象的属性保存时,这个函数是这个对象的方法。函数和方法只有名称上的区别

创建

三种方式:

  • new Function

    var func = new Function("console.log('调用函数');")
    
  • function 函数名(常用)

    function func()
    {
      console.log("调用函数");
    }
    
  • 给匿名函数起名

    var func = function()
    {
      console.log("调用函数");
    }
    

调用函数:

func();

传参

传递形参:function func(a,b){}

其中a和b可以是任意的数据类型

在调用函数传参时,不会检查实参的类型和数量(多余的实参会被忽略,少的形参会是undefined)


对于基本数据类型都是值传递,对于对象类型是引用传递

function setName(obj) {
    obj.name = "小明";
}
let person = {};
setName(person);
console.log(person); // {name: "小明"}

函数也可以作为参数进行传递:

function func(a) {
    a("func");
}
function outer_func(content) {
    console.log(content);
}
func(outer_func); //相当于outer_func("func");即console.log("func");
对象属性用作实参

当一个函数包含的形参有多个时,可以通过传入对象的方式,以对象中的属性作为真正的实参,此时便无需关注参数的顺序

function foo(obj) {
    console.log(obj.name, obj.sex, obj.age);
}
foo({ sex: '', age: 18, name: '小明' }); //小明 男 18
函数默认值

设置默认值的参数应放在尾部

function sayHi(name = 'everyone') { //定义函数时,直接给形参赋值
	console.log( 'Hello ' + name + '!');
}
sayHi(); //'Hello everyone!' 
sayHi('Tony'); //'Hello Tony!' 
sayHi(undefined); //'Hello everyone!'

默认值可以是一个值、表达式以及函数调用

返回值

return;return undefined;和不写return语句都是返回undefined

return后面的语句不会被执行

函数的内部可以再声明函数,函数对象也可作为返回值

匿名函数

函数定义完,立即被调用,这类立即执行函数往往只执行一次

function(){alert("匿名函数");} //这样写会报错,需在两边加上括号来进行标识
(function(){alert("匿名函数");})  //正确写法
(function(){alert("匿名函数");})();  //调用匿名函数
(function(a,b){console.log(a+b);})(10,20);  //还可以传递参数

自执行函数:将一段程序封装进一个会自动执行的匿名函数,防止变量污染

语法:(function(){/*程序段*/})();

this

解析器每次在调用函数时都会向函数内部传进一个隐含的参数this,它指向一个对象,称为函数指向的上下文对象;在函数中可以直接使用this

根据函数调用方式不同,this会指向不同的对象。以函数形式调用时,this永远是window;以方法的形式调用时,this就是调用方法的对象;可以说,谁调用了这个函数,this就是谁

var name = "全局"; //即window.name
function say_name() {
    console.log(this.name);
}
var obj1 = {
    name: "obj1",
    say_name: say_name
}
var obj2 = {
    name: "obj2",
    say_name: say_name
}
say_name(); //"全局"
obj1.say_name(); //"obj1"
obj2.say_name(); //"obj2"

函数的call()apply()方法:可以改变函数中this指向

func.apply/call(指定的this,函数参数)使用指定的this调用函数

function func() {
    alert(this);
}
func(); //[object Window]
obj = {};
func.apply(obj); //[object Object]
func.call(obj); //[object Object]

区别:对于有参数的函数,call()可以将实参放在对象之后依次传入函数,而apply()需要将实参放到一个数组中统一传递

function func(key1, key2) {
    console.log(key1 + ":" + this[key1]);
    console.log(key2 + ":" + this[key2]);
}
obj = { name: "abc", age: 20 };
func.call(obj, "name", "age");  //name:abc    age:20
func.apply(obj, ["name", "age"]);   //name:abc    age:20

arguments

函数调用时,浏览器除了this,还好传递进一个隐含的参数arguments,用于封装实参;它是一个类数组对象,不是数组,但也可以通过索引来操作数据并获取长度;在调用函数时,传递进的实参在arguments中保存;即使不定义形参,也可以通过它来使用传进来的实参

function func() {
    console.log(arguments[0]);
    console.log(arguments[2]);
    console.log(arguments.length);
}
func(0, 1, 2);  //0  2  3

arguments可以跟形参一起使用,并且arguments对象中的值会和对应的形参保持同步

function foo(a) {
	arguments[0] ++;
    console.log(a);
}
foo(10); //11
function foo2(a) {
	a++;
    console.log(arguments[0]);
}
foo2(10); //11

如果缺少传参,那这个形参的值就不会和arguments对象中的对应值进行同步

function foo(a,b) {
    arguments[1] = 2;
    console.log(b);
}
foo(1); //undefined
//形参b没有传入实参,它的值会默认为undefined
foo(1, undefined); //2
//手动传入undefined时,arguments数组中会出现一个值为undefined的元素

arguments的callee属性:正在执行的函数对象

function func() {
    console.log(arguments.callee);
}
func();  //ƒ func() {console.log(arguments.callee);}

数组

数组也是一个对象,用于存储一些值。

不同的是:普通对象使用字符串作属性名,而数组使用数字作为索引操作元素,索引从0开始;数组的储存性能比普通对象好

创建

  • new数组对象Array

    var arr = new Array();
    var arr = new Array(10,20,30);
    
  • 字面量[]

    var arr = []; 
    var arr = [1,2,3];
    

区别:

var arr=[10]; //创建一个只有元素10的数组
var arr =new Array(10); //创建一个长度为10的数组

注意:数组中的元素可以是任意的数据类型(包括对象、函数、数组),且同一数组中可以有不同的元素类型

var arr = [[1,2,3],[10,20,30]]; //二维数组

赋值与获取

js数组中索引从0开始

  • 使用arr[index]获得/修改index位置上的元素。若不存在index,则返回undefined(不报错)

  • 使用arr.length获得/修改数组中元素个数。对非连续数组,length为数组最大的索引+1,不连续的部分用null补位

var arr = new Array();
arr[0] = 10;
arr[1] = 20;
arr[2] = 30;
console.log(arr); //(3) [10, 20, 30]  表示有3个元素,值为10 20 30
console.log(arr[0]); //10
console.log(arr[3]); //undefined
console.log(arr.length); //3
arr[10] = 110;
console.log(arr.length); //110  
console.log(arr); //(11) [10, 20, 30, 空 ×7, 110]
console.log(arr[arr.length-1]); //110 获取最后一个元素
arr[arr.length] = 40; //向数组最后一个位置添加元素

length可被修改:

如果修改的length大于原长度,则多余的部分为空;小于原长度,则多余部分被删除

var arr =new Array(10,20,30);
arr.length = 5;
console.log(arr); //(5) [10, 20, 30, 空 ×2]
arr.length = 2;
console.log(arr); //(2) [10, 20]

添加元素

  • arr.push()向数组末尾添加1个或多个元素,返回数组新的长度

    var arr = [1, 2, 3];
    var res = arr.push(4, 5);
    console.log(arr);  //(5) [1,2,3,4,5]
    console.log(res);  //5
    
  • arr.unshift()向数组开头添加1个或多个元素,返回数组新的长度

    var arr = [1, 2, 3];
    var res = arr.unshift(-1, 0);
    console.log(arr);  //(5) [-1,0,1,2,3]
    console.log(res);  //5
    
  • arr.splice(start,0,新值1,新值2,...)在start位置插入新值

    var arr = [0, 1, 2];
    var res = arr.splice(1, 0, "new_num1", "new_num2"); //在a[1]的位置插入"new_num1", "new_num2"
    console.log(res); //[]
    console.log(arr); //(5) [0, 'new_num1', 'new_num2', 1, 2]
    
  • arr1.concat(arr2,arr3,...)arr2,...追加到arr1后,返回新数组,不会改变原数组。其中arr2不仅可以是数组,也可以是单个元素

    var a1 = [1, 2, 3];
    var a2 = [10, 20, 30];
    var a3 = [100, 200, 300];
    var res = a1.concat(a2, a3);
    console.log(res); //(9) [1, 2, 3, 10, 20, 30, 100, 200, 300]
    
    var a1 = [1, 2, 3];
    var a2 = [10, 20, 30];
    var res = a1.concat(a2, 100, 200, 300);
    console.log(res); //(9) [1, 2, 3, 10, 20, 30, 100, 200, 300]  
    

删除元素

  • arr.pop()删除数组的最后一个元素,并将删去的那个元素返回

    var arr = [1, 2, 3];
    var res = arr.pop();
    console.log(arr);  //(2) [1,2]
    console.log(res);  //3
    
  • arr.shift()删除数组的第一个元素,并将删去的那个元素返回

    var arr = [1, 2, 3];
    var res = arr.shift();
    console.log(arr);  //(2) [2,3]
    console.log(res);  //1
    
  • arr.splice(start,number)删除数组中指定元素,从索引为start的元素开始删number个元素,传递的索引也可以为负数(表示倒数第几个数)。直接改变原数组,将被删除的元素返回

    var arr = [0, 1, 2, 3, 4, 5];
    var res = arr.splice(1, 2); //从arr[1]开始删除2个元素,arr[1]也算其中1个
    console.log(res); //(2) [1, 2]
    console.log(arr); //(4) [0, 3, 4, 5]
    

替换元素

arr.splice(start,number,新值1,新值2,...)表示将arr[start]开始的number个元素替换为新值,替换后元素个数可以大于替换前个数

var arr = [0, 1, 2, 3, 4, 5];
var res = arr.splice(1, 1, "new_num1", "new_num2"); //将"new_num1", "new_num2"替换到a[1]位置
console.log(res); //[1]
console.log(arr); //(7) [0, 'new_num1', 'new_num2', 2, 3, 4, 5]

切片

arr.slice(start,end)提取[start,end)索引的元素,end参数可省略,默认一直取到数组结尾;注意这个方法不会改变原数组,而是将切片后新数组返回

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

传递的索引也可以是负值,-n代表倒数第n个元素

console.log(arr.slice(0, -1)); //(5) [0, 1, 2, 3, 4]
console.log(arr.slice(-1, -4)); //[]空数组,注意slice只能正向切片,无法从后往前输出元素
console.log(arr.slice(-4, -1)); //(3) [2, 3, 4]
console.log(arr.slice(-2)); //(2) [4, 5]

遍历

  • 正常for循环

    var arr = [1, 2, 3];
    for (var i = 0; i < arr.length; i++) {
        console.log(arr[i]);
    }
    
  • arr.forEach(function (value, index, obj){...})

    该方法需要一个函数作为参数,像这种由我们创建但不由我们调用的函数称为回调函数

    数组中有几个元素,函数就执行几次,每次执行时,浏览器会将遍历到的元素以实参形式传递进函数中

    浏览器会传递进回调函数3个参数:当前正在遍历的元素值、当前正在遍历的元素索引、正在遍历的数组对象

    var arr = ["a", "b", "c"];
    arr.forEach(function (value, index, obj) {
        console.log("arr[" + index + "]=" + value); //arr[0]=a  arr[1]=b  arr[2]=c
        console.log(obj);  //(3) ['a', 'b', 'c']
    });
    

其它方法

  • arr.join(seq)将数组转换为字符串,以seq为分隔符,默认为逗号;返回新数组,不会改变原数组

    var a1 = [1, 2, 3];
    var res = a1.join()
    console.log(res); //1,2,3
    res = a1.join("|");
    console.log(res); //1|2|3
    res = a1.join("");
    console.log(res); //123
    
  • arr.reverse()反转数组;注意该方法会直接改变原数组

    var a1 = [1, 2, 3, 4];
    a1.reverse();
    console.log(a1); //(4) [4, 3, 2, 1]
    
  • arr.sort()对数组元素排序,默认按照Unicode编码从小到大排序;即使对于纯数字数组,默认也是按编码排,可能得到错误结果(如11<2<3<4);注意该方法会直接改变原数组

    var letters = ["a", "e", "c", "a", "b"];
    var number = [11, 3, 2, 6, 5, 4];
    letters.sort();
    number.sort();
    console.log(letters); //(5) ['a', 'a', 'b', 'c', 'e']
    console.log(number); //(6) [11, 2, 3, 4, 5, 6]
    

    可以自己指定排序规则:在sort中添加回调函数,在函数中需定义两个形参,浏览器将会分别使用数组中元素作为实参调用回调函数,并根据回调函数返回值决定因素顺序:若返回大于0的值,则元素会交换位置;返回<=0的值不换位置

    var number = [11, 3, 2, 6, 5, 4];
    number.sort(function (a, b) { 
        if (a > b) { 
            return 1;
        }
        else if (a < b) {
            return -1;
        }
        else {
            return 0;
        }
    });
    console.log(number); //(6) [2, 3, 4, 5, 6, 11]
    

    调换上面if与elseif中返回值,就可以实现从大到小排列

    一种更简便的方法:

    number.sort(function (a, b) {
        return a - b;
    });
    
  • arr.map(function (ele, index){...})遍历数组,传入一回调函数,按该函数对数组中的每个元素进行处理,将处理结果存入新数组并返回

    const arr = ['red', 'blue', 'pink'];
    const new_arr = arr.map(function (ele, index) { //ele表示当前元素值,index为当前元素索引
        return index + ":" + ele;
    });
    console.log(new_arr); //['0:red', '1:blue', '2:pink']
    
  • arr.filter(function (ele, index){...})根据条件筛选数组元素,将符合条件的元素整合成新数组返回

    const arr = [10,20,30];
    const res = arr.filter(function(item,index) { //item表示当前元素值,index为当前元素索引
        return item>=20; //item>=20即为条件
    });
    console.log(res); //(2)[20,30]
    

    注意:map方法是将原数组的每个元素处理后返回到新数组,filter是将筛选后结果返回;它们都不改变原数组

  • arr.reduce(function(上一次值,当前值){},初始值)

    • 当前值:reduce也是遍历数组元素,当前值就是当前遍历到的数组元素值

    • 上一次值:reduce回调函数内需返回一个值,上一次值就是上次循环返回的那个值

    • 初始值:如果有,第一次遍历时,上一次值被赋成初始值,当前值为数组的第一个元素;没有,第一次遍历,上一次值是数组第一个元素,当前值是第二个元素

    例:数组元素的递加

    const arr = [1,2,3,4,5];
    const total1 = arr.reduce(function(prev,current){
        return prev+current;
    }); //无初始值
    console.log(total1); //15
    const total2 = arr.reduce(function(prev,current){
        return prev+current;
    }, 20); //有初始值
    console.log(total2); //35
    

    有些时候必须写初始值,比如累加对象中的某属性:

    const arr=[{
        'name':'abc',
        'salary':1000
    },{
        'name':'bcd',
        'salary':2000
    },{
        'name':'cde',
        'salary':3000
    }];
    const total = arr.reduce(function(prev,current){
        return prev+current.salary
    }, 0); //如果不加初始值,第一次遍历的prev将是一个对象,无法累加
    console.log(total); //6000
    
  • arr.find(function (ele, index){...})返回符合条件的第一个数组元素,与它类似的有findIndex()是返回索引

    const res = arr.find(function(item, index){ //item表示当前元素值,index为当前元素索引(可选)
        return item.name == 'bcd';
    });
    console.log(res); //{name: 'bcd', salary: 1000}
    
  • arr.every(function (ele, index){...})检测数组内所有元素是否都符合条件,若是返回true,否则false。若传入空数组则直接返回true

    const res = arr.every(function(item){
        return item.salary>=1000;
    });
    console.log(res); //true
    
  • arr.some(function (ele, index){...})只要数组内有1个符合条件就是true

  • 静态方法Array.from(伪数组)将伪数组转换成真数组并返回,也可使用展开运算符...

    const li_list = document.querySelectorAll('ul li');
    const li_array = Array.from(li_list); //或者
    const li_array = [...li_list]; 
    li_array.pop(); //伪数组没有该方法,但真数组可以使用
    

一些简单应用

例1:将age不同的Person对象存入数组中,获取age>=18的person对象

function Person(name, age) {
    this.name = name;
    this.age = age;
}
var person = [new Person("p1", 18), new Person("p2", 10), new Person("p3", 19), new Person("p4", 30), new Person("p5", 1)];
function get_adult(arr) {
    var new_arr = []; //创建结果数组
    for (var i = 0; i < arr.length; i++) {
        if (arr[i].age >= 18) { //进行条件判断
            new_arr.push(arr[i]);
        }
    }
    return new_arr;
}
var adult = get_adult(person);

例2:利用splice生成随机不重复数组

设生成长度为3的不重复数组,元素在[0,4]范围内:

let range = [0, 1, 2, 3, 4]; //元素范围(可能取值)
let res = [];
for (let i = 0; i < 3; i++) {
    let random  _index = Math.floor(Math.random() * range.length);
    res.push(range[random_index]); //将随机数添加到结果中
    range.splice(random_index, 1); //将该数从可能取值中删除
}
console.log(res);

字符串与正则表达式

字符串

在底层,字符串是以字符数组的形式保存的,可以使用类似数组的属性

var str = "hello";
console.log(str.length); //5
console.log(str[1]); //e

字符串的方法:大部分都不会改变原字符串

char系列
  • str.charAt(n),返回指定位置的字符,相当于str[n]

  • str.charCodeAt(n)返回str[n]字符的Unicode编码

  • String.fromCharCode(n)获取编码为n的字符

查找子串
  • str.indexOf(substr,start=0)从start位置开始,检索字符串中是否含有指定内容,如果有就返回其第一次出现的索引;没有则返回-1

    var str = "hello";
    console.log(str.indexOf("el")); //1
    console.log(str.indexOf("h", 2)); //-1 从str[2](包括str[2])开始查找
    

    同理有lastIndexOf()返回最后一次出现的索引

  • str.includes(substring,startindex=0)从startindex开始查找substring是否在str内,是则返回true,反之为false

切片
  • str.slice(m,n)取[m,n)的字符串,n默认到字符串结束;m n也可以取负数,表示倒数第几个位置(与数组中slice相同)

    var str = "hello";
    var res = str.slice(1, 1);
    console.log(res); //空([m,m)取不到)
    
  • str.substring(m,n)取[m,n)的字符串,n默认到字符串结束;注意该方法与slice的不同是它不能接收负值,若传了负值,则默认为0;还可以自动调整参数位置,若传入的m < n,则自动交换(slice没有这项功能,该情况下返回空)

    var str = "hello";
    var res = str.substring(1, -4); //相当于substring(1,0),又相当于substring(0,1)
    console.log(res);  //h
    
  • str.substr(m,n)从str[m]开始截取n个字符(包括str[m])

    var str = "hello";
    var res = str.substr(1, 2);
    console.log(res); //el
    
  • str.split(seq)按seq将字符串拆分成数组,若不传seq就返回将整个字符串放入数组的第一个元素中返回

    var str = "hello,js,world";
    var res = str.split(",");
    console.log(res); //(3) ['hello', 'js', 'world']
    

    还可以分离字符串中的每个字母:

    var str = "Hello World";
    var res = str.split("");
    console.log(res); //(11) ['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd']
    
大小写转换
  • str.toLowerCase()所有字符变小写

  • str.toUpperCase()所有字符变大写

var str = "Hello World";
console.log(str.toLowerCase()); //hello world
console.log(str.toUpperCase()); //HELLO WORLD
检测是否以某字符开头

str.startsWith(substring,startindex=0)判断str是否以substring开头,startindex为开始检测的位置(默认为开头)

const str = 'abc ABC';
console.log(str.startsWith('abc')); //true
console.log(str.startsWith('ABC')); //false
console.log(str.startsWith('ABC',4)); //true

同理还有str.endswith(substring[, start[, end]])是否以substring结尾

字符串拼接

str1.concar(seq,str2)连接两个或多个字符串(与数组中相同)

var res = str.concat(" ", str1) //相当于str+" "+str1

ES6里面增加了`${变量}`新语法,用处是将字符串变量与字符串进行拼接,与python中的f-string类似。

注意:使用这种方法拼接的字符串两侧用`包裹,而不是引号

var a = 1;
console.log(`a的值是:${a}`); //a的值是:1

正则表达式

创建

创建正则表达式对象:

  • 正则表达式对象RegExpd的构造函数

    var reg = new RegExp("xxx"); //xxx应为正则表达式
    var reg = new RegExp("xxx","i"); //第二个参数"i"表示忽略大小写
    var reg = new RegExp("xxx","g");/ /第二个参数"g"表示全局匹配模式
    
  • 字面量:var 变量 = /正则表达式/匹配模式

    var reg = /xxx/i; //相当于RegExp("xxx","i")
    var reg = /[a-z]/ig; //相当于RegExp("[a-z]", "ig"),即忽略大小写又全局匹配
    

两种创建方式的区别:

  • 使用构造函数创建可以传入一个字符串变量作为正则表达式,而字面量创建不行

  • 用构造函数创建,因为传入的是字符串,需要进行转义。

    如正则表达式标准里面,用\.来代表.这个符号,使用字面量可以直接reg = /\./,而构造函数中就需将\进行转义(”\\”->\),写成RegExp("\\.")的形式;

    同理reg = /\\/等效于RegExp("\\\\")

^ $同时出现:精确匹配,表示整个字符串都必须符合指定规则,如/^a$/只能匹配"a",表示整个字符串就是一个"a""aa"都不行。一般情况下都使用这种精确匹配

使用
  • reg.test/exec(str)其中reg为正则表达式,str为待检测字符串

    • test返回true/false,检测str是否含符合正则表达式的子串;

    • exec返回数组,里面包含符合正则表达式的子串的索引等,若没有则返回空

    var reg = /[a-z]/ig;
    var res = reg.test(str);
    
  • str.split(reg)将正则表达式作为分隔符来拆分字符串

    var str = "1q2w3e4r5t6y";
    var res = str.split(/[A-z]/); //以所有字母为分隔符进行拆分
    console.log(res); //(7) ['1', '2', '3', '4', '5', '6', '']
    
  • str.search(reg)搜索字符串中是否含指定内容,返回该内容第一次出现的索引,不出现则返回-1

    var str = "abc acc adc";
    var res = str.search(/a[cd]c/); //是否含有abc或adc
    console.log(res); //4
    
  • str.match(reg)根据正则表达式,将符合条件的内容提取出;

    默认情况下只会提取第一个符合条件的内容,可以设置正则表达式为全局匹配模式来提取所有内容,都会封装到一个数组中返回

    var str = "1q2w3e4r1Q2W3E4R";
    var res1 = str.match(/[a-z]/);
    var res2 = str.match(/[a-z]/ig);
    console.log(res1); //['q', index: 1, input: '1q2w3e4r1Q2W3E4R', groups: undefined]
    console.log(res2); //(8) ['q', 'w', 'e', 'r', 'Q', 'W', 'E', 'R']
    
  • str.replace(reg,new_val)将字符串中指定内容替换成新的内容,返回替换后的新串,不会改变原串;

    也是默认只替换第一个,设置全局匹配模式替换所有内容;将要替换的值写成空串可以删除指定内容

    var str = "1q2w3e4r1Q2W3E4R";
    var res1 = str.replace(/[A-z]/g, "|");
    var res2 = str.replace(/[a-z]/ig, "");
    console.log(res1); //1|2|3|4|1|2|3|4|
    console.log(res2); //12341234
    

注意这4个方法中,split不用设置全局匹配,在找到所有符合正则的位置后进行拆分;search不能设置全局匹配,只能找到第一次出现位置

作用域

全局作用域

指编写在script标签内的代码。在页面打开时创建,关闭时销毁,在页面的任意部分都可以访问

其中有一个全局对象window,可以直接使用,它代表浏览器的窗口;创建的变量都会作为window对象的属性保存

函数作用域

调用函数时创建的作用域,函数执行完后销毁;

每调用一次就创建一个新的函数作用域,它们之间相互独立;

在函数作用域中可以访问并修改全局作用域中的变量(不需传参,可以直接用变量名取到),但全局不能访问函数作用域的变量;

使用变量时,函数内会优先寻找函数作用域内变量,要是没有才向上一级作用域中找,直到全局作用域中也没有就报错

在函数中访问全局变量a:window.a

声明提前

ES5

变量的声明提前:使用var关键字声明的变量,会在所有的代码执行前被声明(不是被赋值,赋值还是在var处)

如果声明变量时不使用var关键字,则不会被提前


函数的声明提前:使用函数声明创建的函数(function xxx(){})会在所有的代码执行前被创建(不只是声明,也包括其中的内容),可以在函数调用之后写函数内容(对变量赋值);

而使用函数表达式创建的函数(var xxx=function(){};),只会将xxx变量声明,不会提前创建函数内容,如果在函数调用之后写会报错


函数作用域中也有声明提前的特性,即用var声明的变量会在函数中所有代码执行前被声明,使用函数声明创建的函数会在函数中所有的代码执行前被创建;

函数内声明新变量时不用var,就会自动将其声明为全局变量

ES6

let不存在变量提前,且作用在块级作用域

解决ES5中,变量提前与函数作用域可能造成的不合理后果。例如:

var tmp = '你好';
function f() {
  console.log(tmp);
  if (false) {
    var tmp = 'hello world';
  }
}
f(); // undefined

此代码原意是if代码块的外部使用外层的tmp变量,内部使用内层的tmp变量,应该输出'你好'。但if内的tmp因为变量声明提前,覆盖了外层的tmp变量,且因为if不可能执行,无法赋值

function f1() {
  let n = 20;
  if (true) {
    let n = 10;
  }
  console.log(n); //20
}

使用let,外层代码块不受内层代码块的影响,该思想与c里面的变量作用域相似,都是哪里声明哪里使用,不会越级

const的作用域与let命令相同:

  • 只在声明所在的块级作用域内有效

  • 声明不提前,只能在声明的位置后面使用

  • 不可重复声明

程序调试

对程序的调试:f12->源代码->js文件->单击代码前数字,可以设置断点->刷新页面,代码栏右侧可以控制执行下一步等等

监视某个变量:代码栏右侧的监视->点击添加+->输入变量名