JavaScript-基础

JavaScript

从交互的角度,描述行为(提升用户体验)。



特点

  1. 简单易用:可以使用任何文本编辑工具编写,只需要浏览器就可以执行程序。
  2. 解释型语言:事先不需要被编译为机器码再执行,逐行执行、无需进行严格的变量声明。
  3. 基于对象:内置大量现成对象,编写少量程序可以完成目标。

注释

// 我是单行注释
/*
  我是多行注释1
  我是多行注释2
 */

变量

变量可以用来保存字面量,而且变量的值可以任意改变。

变量定义

var a = 100;//赋值式声明
var b;//单纯的声明
var _abc;
var $abc;//必须是$或_或字母作为开头,变量名称里还可以包含。
//var 1abc;//不允许数字开头
//var .abc;//不能以除_$之外的符号开头
  • 定义变量:var是一个关键字,用来定义变量。关键字是有特殊功能的小词语。关键字后面一定要有空格隔开。
  • 变量的赋值:等号表示赋值,将等号右边的值,赋给左边的变量。
  • 变量名:必须是$_字母作为开头,变量名称里还可以包含。

命名规则

  1. 建议用驼峰命名规则:getElementById/matherAndFather/aaaOrBbbAndCcc
  2. 变量命名必须以字母或是下标符号”_”或者”$”为开头。
  3. 变量名长度不能超过255个字符。
  4. 变量名中不允许使用空格,首个字不能为数字。
  5. 不用使用脚本语言中保留的关键字及保留字作为变量名。
  6. 变量名区分大小写(javascript是区分大小写的语言)。
  7. 汉语可以作为变量名。但是不建议使用。

标识符的命名规则和变量的命令规则是一样的。


命名规范

变量名有命名规范:只能由英语字母、数字、下划线、美元符号$构成,且不能以数字开头,并且不能是JavaScript保留字。

JavaScript保留字:

abstract、boolean、byte、char、class、const、debugger、double、enum、export、extends、final、float、goto
implements、import、int、interface、long、native、package、private、protected、public、short、static、super、synchronized、throws、transient、volatile

大写字母是可以使用的,并且大小写敏感。也就是说A和a是两个变量。

var A = 250;    //变量1
var a = 888;    //变量2

声明提升

JavaScript在解释执行之前,有预编译的过程,会将变量声明提升到最前面,但是会将赋值提升。


变量类型

在JS中一共有六种数据类型:

  • 基本数据类型(值类型):
    • String 字符串
    • Number 数值
    • Boolean 布尔值
    • Null 空值
    • Undefined 未定义。
  • 引用数据类型(引用类型):
    • Object 对象。

注意:内置对象function、Array、Date、RegExp、Error等都是属于Object类型。除了五种基本数据类型之外,都称之为 Object类型。

面试问:引用数据类型有几种?
面试答:只有一种,即 Object 类型。

数据类型之间最大的区别:

  • 基本数据类型:参数赋值的时候,传数值。
  • 引用数据类型:参数赋值的时候,传地址(修改的同一片内存空间)。

字符串: String

  • 字符串使用需要引号引起来。使用双引号或单引号都可以,但不能混用
  • 引号不能嵌套:双引号里不能再放双引号,单引号里不能再放单引号。但是单引号里可以嵌套双引号、双引号里可以嵌套单引号。
  • 转义字符:在字符串中使用\作为转义字符,当表示一些特殊符号时可以使用\进行转义。

将其他数值转换为字符串有三种方式:

  • 拼串
  • toString()
  • String()

数值型:Number

  • 由于内存的限制,ECMAScript 并不能保存世界上所有的数值。
  • 如果使用Number表示的变量超过了最大值,则会返回Infinity

NaN和isNaN()函数
  • NaN:是一个特殊的数字,表示Not a Number,非数值。
    注意:typeof NaN的返回结果是number。

  • isNaN() :任何不能被转换为数值的值,都会让这个函数返回 true。

    isNaN(NaN);// true
    isNaN("blue"); // true
    isNaN(123); // false
    

浮点数运算

在JS中,整数的运算基本可以保证精确;但是小数的运算,可能会得到一个不精确的结果。所以,千万不要使用JS进行对精确度要求比较高的运算。

var a = 0.1 + 0.2;
console.log(a);  //打印结果:0.30000000000000004

布尔值:Boolean

true 和 false。主要用来做逻辑判断。

空值:null

专门用来表示一个为空的对象(例如:var a = null)。

  • Null类型的值只有一个,就是null。
  • 使用 typeof 检查一个null值时,会返回object。

未定义:undefined

声明了一个变量,但是没有赋值(例如:var a;),此时它的值就是undefined

  • Undefined类型的值只有一个,就是undefind
  • 使用 type of 检查一个undefined时,会返回undefined。

null和undefined有最大的相似性。看看null == undefined的结果(true)也就更加能说明这点。

但是null === undefined的结果(false)。它们虽然相似,但还是有区别的,其中一个区别是:和数字运算时,10 + null结果为:10;10 + undefined结果为:NaN。

任何数据类型和undefined运算都是NaN;
任何值和null运算,null可看做0运算。


赋值

将等号右边的值,赋给左边的变量;等号右边的变量,值不变。


隐式转换

我们知道,"2"+1得到的结果其实是字符串,但是"2"-1得到的结果却是数值1,这是因为计算机自动帮我们进行了“隐式转换”。

也就是说,-、*、/、`%``这几个符号会自动进行隐式转换。例如:

  var a = "4" + 3 - 6;
  console.log(a);

输出结果:
37
虽然程序可以对-、*、/、`%``这几个符号自动进行“隐式转换”;但作为程序员,我们最好自己完成转换,方便程序的可读性。


强制类型转换

强制类型转换:将一个数据类型强制转换为其他的数据类型。
类型转换主要指,将其他的数据类型,转换为:String、Number、Boolean。

其他的简单类型 --> String

  • 变量+"" 或者 变量+"abc"

    var a = 123;  // Number 类型
    console.log(a + '');  // 转换成 String 类型
    console.log(a + 'haha');  // 转换成 String 类型
    
  • 调用toString()方法

    变量.toString()
    
    • 该方法不会影响到原变量,它会将转换的结果返回。可以写成a = a.toString(),这样的话,就是直接修改原变量。

    • null和undefined这两个值没有toString()方法。如果调用,会报错。

    • Number类型的变量,在调用toString()时,可以在方法中传递一个整数作为参数。此时它将会把数字转换为指定的进制,如果不指定则默认转换为10进

  • 使用String()函数

    String(变量)
    
    • 对于Number和Boolean而言,实际上就是调用toString()方法。
    • 但是对于null和undefined,就不会调用toString()方法。它会将 null 直接转换为 "null"。将 undefined 直接转换为 "undefined"。

其他的简单类型 --> Number

  • 使用Number()函数

    • 字符串 --> 数字
      • 如果字符串中是纯数字,则直接将其转换为数字。
      • 如果字符串中有非数字的内容,则转换为NaN。(此处可以看到Number()函数的局限性)
      • 如果字符串是一个空串或者是一个全是空格的字符串,则转换为0。
    • 布尔值 --> 数字
      • true 转成 1
      • false 转成 0
    • null --> 数字
      • 结果为:0
    • undefined --> 数字
      • 结果为:NaN
  • 字符串 -> 整数:parseInt()
    parseInt()将字符串中的有效的整数内容转为数字。向下取整数。

    • 只保留字符串最开头的数字。
    • 自动带有截断小数的功能:向下取整,不四舍五入。
    • 如果对非String使用parseInt()或parseFloat(),它会先将其转换为String然后再操作。对于非字符串的非数值类型,不要使用parseInt进行转换,直接使用Number()函数进行转换。
    • 带两个参数时,表示进制转换。
  • 字符串 --> 浮点数:parseFloat()
    parseFloat()的作用是:将字符串转换为浮点数。

    parseFloat()parseInt()的作用类似,不同的是,parseFloat()可以获得有效的小数部分。

转换为 Boolean

使用Boolean()

  • 情况一:数字 --> 布尔。除了0和NaN,其余的都是true。
  • 情况二:字符串 ---> 布尔。除了空串,其余的都是true。
  • 情况三:null和undefined都会转换为false。
  • 情况四:对象也会转换为true。

typeof 运算符

获取变量类型:typeof ()
返回结果:

  • typeof 数值的返回结果:number
  • typeof 字符串的返回结果:string
  • typeof 布尔型的返回结果:boolean
  • typeof undefined的返回结果:undefined
  • typeof null的返回结果:object

运算符

比如说+*/( 都是运算符,而(3+5)/2则是表达式。

定义和分类

运算符也叫操作符。通过运算符可以对一个或多个值进行运算,并获取运算结果。

注:运算符都是会返回结果的。

运算符有很多分类,比如:

  • 算数运算符
  • 自增运算符
  • 逻辑运算符
  • 赋值运算符
  • 关系运算符
  • 三元运算符(条件运算符)

算术运算符

符号 功能
+ 加、字符串连接
-
*
|除
% 取余
( ) 括号 优先级

乘方

计算a的b次方Math.pow(a, b);

开方

计算数值a的开二次方Math.sqrt(a);


自增运算符(自减运算符)

自增 ++

自增分成两种:a++++a

  • 对于一个变量自增以后,原变量的值会立即自增1。也就是说,无论是 a++ 还是 ++a ,都会立即使原变量的值自增1。

  • 我们要注意的是:a是变量,而 a++++a 是表达式。

  • a++ 的值等于原变量的值(a自增前的值)

  • ++a 的值等于新值 (a自增后的值)

自减 --

原理同上。


逻辑运算符

逻辑运算符有三个:

  • && 与(且):两个都为真,结果才为真。
  • || 或:只要有一个是真,结果就是真。
  • ! 非:对一个布尔值进行取反。

注意:

  • 能参与逻辑运算的,都是布尔值
  • JS中的&&属于短路的与,如果第一个值为false,则不会看第二个值。
  • JS中的||属于短路的或,如果第一个值为true,则不会看第二个值。
  • 如果对非布尔值进行逻辑运算,则会先将其转换为布尔值,然后再操作。

非布尔值的与或运算

在实际开发中,经常用这种代码做容错处理。

非布尔值进行与或运算时,会先将其转换为布尔值,然后再运算,但返回结果是原值。

与运算的返回结果:(以两个非布尔值的运算为例)

  • 如果第一个值为true,则必然返回第二个值(所以说,如果所有的值都为true,则返回的是最后一个值)
  • 如果第一个值为false,则直接返回第一个值

或运算的返回结果:(以两个非布尔值的运算为例)

  • 如果第一个值为true,则直接返回第一个值
  • 如果第一个值为false,则返回第二个值

赋值运算符

可以将符号右侧的值赋值给符号左侧的变量。

  • +=。a += 5 等价于 a = a + 5
  • -=。a -= 5 等价于 a = a - 5
  • *=。a *= 5 等价于 a = a * 5
  • /=。a /= 5 等价于 a = a / 5
  • %=。a %= 5 等价于 a = a % 5

关系运算符

通过关系运算符可以比较两个值之间的大小关系,如果关系成立它会返回true,如果关系不成立则返回false。

  • > 大于
  • < 小于
  • >= 大于或等于
  • <= 小于或等于
  • == 等于
  • === 全等于
  • != 不等于
  • !== 不全等于

非数值的比较

  • 对于非数值进行比较时,会将其转换为数字然后再比较。
  • 特殊情况:如果符号两侧的值都是字符串时,不会将其转换为数字进行比较。比较两个字符串时,比较的是字符串的Unicode编码。
    • 比较字符编码时,是一位一位进行比较。如果两位一样,则比较下一位,所以借用它可以来对英文进行排序。
    • 比较两个字符串型的数字时,要先转型。
  • 任何值和NaN做任何比较都是false。

==的强调

注意==这个符号,它是判断是否等于,而不是赋值。

  • ==这个符号,可以验证字符串是否相同。
  • ==这个符号并不严谨,会将不同类型的东西,转为相同类型进行比较(大部分情况下,都是转换为数字)。
  • undefined 衍生自 null,所以这两个值做相等判断时,会返回true。
  • NaN不和任何值相等,包括他本身。

===的强调

如果要保证完全等于,我们就要用三个等号===。全等不会做类型转换。


三元运算符(条件运算符)

三元运算符也叫条件运算符。
语法:条件表达式?语句1:语句2;

执行的流程:

  • 条件运算符在执行时,首先对条件表达式进行求值:
    • 如果该值为true,则执行语句1,并返回执行结果
    • 如果该值为false,则执行语句2,并返回执行结果

如果条件表达式的求值结果是一个非布尔值,会将其转换为布尔值然后再运算。

运算符优先级

建议多用( )

运算符的优先级如下:(越往上,优先级越高)

  • .[]new
  • ()
  • ++--
  • !~+(单目)、-(单目)、typeofvoiddelete
  • %*/
  • +(双目)、-(双目)
  • <<>>>>>
  • <<=>>=
  • ==!=====
  • &
  • ^
  • |
  • &&
  • ||
  • ?:
  • =+=-=*=/=%=<<=>>=>>>=&=^=|=
  • ,

if语句

  • 条件判断语句
    条件成立才执行。如果条件不成立,那就什么都不做。

    if (条件表达式) {
          // 条件为真时,做的事情
      }
    
  • 条件分支语句

    • 格式1:

      if (条件表达式) {
              // 条件为真时,做的事情
          } else {
              // 条件为假时,做的事情
      }
      
    • 格式2:

    if (条件表达式1) {
      // 条件1为真时,做的事情
    } else if (条件表达式2) {
        // 条件1不满足,条件2满足时,做的事情
    } else if (条件表达式3) {
        // 条件1、2不满足,条件3满足时,做的事情
    } else {
        // 条件1、2、3都不满足时,做的事情
    }
    

switch语句(条件分支语句)

格式:

switch(表达式) {
    case 值1:
        语句体1;
        break;
    case 值2:
        语句体2;
        break;
    ...
    ...
    default:
        语句体 n+1;
        break;
}

执行流程

  1. 首先,计算出表达式的值,和case依次比较,一旦有对应的值,就会执行相应的语句,在执行的过程中,遇到break就会结束。
  2. 然后,如果所有的case都和表达式的值不匹配,就会执行default语句体部分。

结束条件

  • 情况a:遇到break就结束,而不是遇到default就结束。(因为break在此处的作用就是退出switch语句)
  • 情况b:执行到程序的末尾就结束。

注意: 必须要在每个casedefault中写上break;,否则会出现case穿透
case穿透:在满足条件的casedefault中,没有遇到break;,程序继续按顺序执行,直到遇到后续casedefault中的break;,或整个程序全部执行完毕后,程序才会结束。


for循环

语法:

for(①初始化表达式; ②条件表达式; ④更新表达式){
        ③语句...
    }

执行流程:

  • ①执行初始化表达式,初始化变量(初始化表达式只会执行一次)
  • ②执行条件表达式,判断是否执行循环:
    • 如果为true,则执行循环③
    • 如果为false,终止循环
  • ④执行更新表达式,更新表达式执行完毕继续重复②

while循环

语法:

while(条件表达式){
    语句...
}

执行流程:

  • while语句在执行时,先对条件表达式进行求值判断
  • 如果值为true,则执行循环体
    • 循环体执行完毕以后,继续对表达式进行判断
    • 如果为true,则继续执行循环体,以此类推
  • 如果值为false,则终止循环

注意: 如果有必要的话,可以使用break;来终止循环。


do...while循环

语法:

do{
    语句...
}while(条件表达式)

执行流程:

  • do...while语句在执行时,先执行循环体
  • 循环体执行完毕以后,在对while后的条件表达式进行判断
    • 如果结果为true,则继续执行循环体,执行完毕继续判断以此类推
    • 如果结果为false,则终止循环

while循环和 do...while循环的区别:

  • while是先判断后执行,而do...while是先执行后判断
  • do...while可以保证循环体至少执行一次,而while不能。

breakcontinue

break

  • break可以用来退出switch语句或整个循环语句(循环语句包括for、while)。
  • break会立即终止离它最近的那个循环语句。

continue

  • continue可以用来跳过当次循环。
  • continue默认只会离他最近的循环起作用。

输出信息

  • 弹出警告框:alert()
    alert(请少用alert())

  • 控制台输出:console.log()

    var a = "请少用console.log()";
    console.log(a);
    console.error("Error!");
    console.warn("Warning!");
    
  • 带确定和取消按钮的弹框:confirm()

    var res = confirm("请少用confirm()");
    // 选择确定,res值为true;选择取消,res值为false
    
  • 带输入框的弹框:prompt()语句

    var a = prompt("请少用prompt()");
    console.log(a);
    

函数

函数:就是将一些功能或语句进行封装,在需要的时候,通过调用的形式,执行这些语句。

  • 函数也是一个对象
  • 使用typeof检查一个函数对象时,会返回function

函数的作用:

  • 将大量重复的语句写在函数里,以后需要这些语句的时候,可以直接调用函数,避免重复劳动。
  • 简化编程,让编程模块化。

定义

  • 使用函数声明来创建一个函数。语法:

    function 函数名([形参1,形参2...形参N]){  // 备注:语法中的中括号,表示“可选”
            语句...
        }
    // 举例
    function sum(a, b){
          return a+b;
      }
    
    • function:是一个关键字。中文是“函数”、“功能”。
    • 函数名字:命名规定和变量的命名规定一样。只能是字母、数字、下划线、美元符号,不能以数字开头。
    • 参数:可选。
    • 大括号里面,是这个函数的语句。
    • 在有些编辑器中,方法写完之后,我们在方法的前面输入/**,然后回车,会发现,注释的格式会自动补齐。
      函数声明提升,会将整个函数对象提升到最前面。
  • 使用函数表达式来创建一个函数。语法:

    var 函数名  = function([形参1,形参2...形参N]){
          语句....
      }
    // 举例
    var fun3 = function() {
          console.log("我是匿名函数中封装的代码");
      };
    

    所谓的“函数表达式”,其实就是将匿名函数赋值给一个变量。

  • 使用构造函数来创建一个对象。(用的少)


调用

语法:

函数名([形参1,形参2...形参N]);


参数

形参

  • 可以在函数的()中来指定一个或多个形参。
  • 多个形参之间使用,隔开,声明形参就相当于在函数内部声明了对应的变量,但是并不赋值。

实参

  • 在调用函数时,可以在()中指定实参。
  • 实参将会赋值给函数中对应的形参。

实参的类型:

  • 函数的实参可以是任意的数据类型。
  • 调用函数时解析器不会检查实参的类型,所以要注意,是否有可能会接收到非法的参数,如果有可能则需要对参数进行类型的检查。

实参的数量:

注意: JavaScript里不会对函数实际传入的参数进行检测。可以传值,也可以不传值,也可以任意传多个值

调用函数时,解析器也不会检查实参的数量:

  • 多余实参不会被赋值。
  • 如果实参的数量少于形参的数量,则没有对应实参的形参将是undefined。

类数组arguments

可以通过arguments获取所有的实际传递的参数。

// 定义add()函数
function add() {
    //arguments:可以获取所有实际传递的参数
    var sum = 0;
    console.log(arguments)
    for (var i = 0; i < arguments.length; i++) {
        sum += arguments[i]
    }
    return sum
}
// 调用add()函数
var res = add(1, 3, 3, 5, 6, 7)
console.log(res);

在调用函数时,浏览器每次都会传递进两个隐含的参数:

  • 函数的上下文对象 this
  • 封装实参的对象 arguments

arguments是一个类数组对象,它可以通过索引来操作数据,也可以获取长度。

arguments代表的是实参。在调用函数时,我们所传递的实参都会在arguments中保存。有个讲究的地方是:arguments只在函数中使用。

  • 返回函数实参的个数:arguments.length
    • arguments.length可以用来获取实参的长度。
  • 返回正在执行的函数:arguments.callee
    • arguments里边有一个属性叫做callee,这个属性对应一个函数对象,就是当前正在指向的函数对象。
    • 在使用函数递归调用时,推荐使用arguments.callee代替函数名本身。
  • arguments可以修改元素
    • arguments是伪数组,是因为:arguments可以修改元素,但不能改变数组的长短。

返回值

return的作用是结束方法。

//举例
function sum(a, b) {
    return a + b;
}
console.log(sum(3, 4))

注意:

  • return后的值将会作为函数的执行结果返回,可以定义一个变量,来接收该结果。
  • 在函数中return后的语句都不会执行(函数在执行完return语句之后停止并立即退出)。
  • 如果return语句后不跟任何值,就相当于返回一个undefined
  • 如果函数中不写return,则也会返回undefined
    返回值可以是任意的数据类型,可以是对象,也可以是函数。

函数名、函数体和函数加载问题

函数名 == 整个函数 :

//定义fn方法
function fn(){
    alert(1)
};

console.log(fn) == console.log(function fn(){alert(1)});

调用一个函数时,通常使用函数()这种格式;此时,是直接使用函数这种格式,它的作用相当于整个函数。

函数的加载问题:JS加载的时候,只加载函数名,不加载函数体。所以如果想使用内部的成员变量,需要调用函数。

fn()fn的区别:

  • fn():调用函数。相当于获取了函数的返回值。
  • fn:函数对象。相当于直接获取了函数对象。

立即执行函数:函数定义完,立即被调用,这种函数叫做立即执行函数。

立即执行函数往往只会执行一次。为什么呢?因为没有变量保存它,执行完了之后,就找不到它了。

方法

函数也可以称为对象的属性。如果一个函数作为一个对象的属性保存,那么我们称这个函数是这个对象的方法。

调用这个函数就说调用对象的方法(method)。相比于方法,它只是名称上的区别,并没有其他的区别。

函数举例:

// 调用函数
fn();

方法举例:

// 调用方法
obj.fn();

作用域

作用域指一个变量的作用范围。在js中,一共有两种作用域:

  • 全局作用域
  • 函数作用域

全局作用域

直接编写在script标签中的JS代码,都在全局作用域。

  • 全局作用域在页面打开时创建,在页面关闭时销毁。
  • 在全局作用域中有一个全局对象window,它代表的是一个浏览器的窗口,它由浏览器创建我们可以直接使用。

在全局作用域中:

  • 创建的变量都会作为window对象的属性保存。
  • 创建的函数都会作为window对象的方法保存。

全局作用域中的变量都是全局变量,在页面的任意的部分都可以访问的到。


函数作用域

  • 作用域:变量和函数生效的区域。作用域在函数定义时,就已经确定了。
    在函数作用域中可以访问到全局作用域的变量,在全局作用域中无法访问到函数作用域的变量。
  • 执行期上下文:当函数执行时,会创建一个执行期上下文的内部对象。每调用一次函数,就会创建一个新的上下文对象,他们之间是相互独立的。当函数执行完毕,它所产生的执行期上下文会被销毁。
    当函数内部的变量被另外一个函数所引用,那么这个函数的变量将不会在执行完毕后销毁。
    参考链接
  • 作用域的上下级关系:
    • 当在函数作用域操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用(就近原则)。如果没有则向上一级作用域中寻找,直到找到全局作用域;如果全局作用域中依然没有找到,则会报错ReferenceError
    • 在函数中要访问全局变量可以使用window对象。(比如说,全局作用域和函数作用域都定义了变量a,如果想访问全局变量,可以使用window.a)

提醒1:

  • 在函数作用域也有声明提前的特性:
    • 使用var关键字声明的变量,会在函数中所有的代码执行之前被声明。
    • 函数声明也会在函数中所有的代码执行之前执行。

因此,在函数中,没有var声明的变量都是全局变量,而且并不会提前声明。

提醒2:

  • 定义形参就相当于在函数作用域中声明了变量。

面向对象

对象的作用是:封装信息。比如Student类里可以封装学生的姓名、年龄、成绩等。

对象具有特征(属性)行为(方法)

面向对象:可以创建自定义的类型,很好的支持继承和多态。

面向对象的特征:封装继承多态


简介

基本数据类型:

  • 基本数据类型的值直接保存在栈内存中,值与值之间是独立存在,修改一个变量不会影响其他的变量。

对象:

  • 只要不是五种基本数据类型,就全都是对象。
  • 如果使用基本数据类型的数据,我们所创建的变量都是独立,不能成为一个整体。
  • 对象属于一种复合的数据类型,在对象中可以保存多个不同数据类型的属性。
  • 对象是保存到堆内存中的,每创建一个新的对象,就会在堆内存中开辟出一个新的空间。变量保存的是对象的内存地址(对象的引用)。
  • 换而言之,对象的值是保存在堆内存中的,而对象的引用(即变量)是保存在栈内存中的。
  • 如果两个变量保存的是同一个对象引用,当一个通过一个变量修改属性时,另一个也会受到影响。

分类

  1. 内置对象:
    由ES标准中定义的对象,在任何的ES的实现中都可以使用
    比如:Math、String、Number、Boolean、Function、Object....
  2. 宿主对象:
    由JS的运行环境提供的对象,目前来讲主要指由浏览器提供的对象。
    比如BOM、DOM。比如consoledocument
  3. 自定义对象:
    由开发人员自己创建的对象

基本操作

  • 创建对象

    • 使用new关键字调用的函数,是构造函数constructor构造函数是专门用来创建对象的函数
    • 例如:var obj = new Object();
       
  • 向对象中添加属性

    • 在对象中保存的值称为属性。
    • 向对象添加属性的语法:对象.属性名 = 属性值;
    • 对象的属性值可以是任何的数据类型,也可以是个函数(也称之为方法)。
    • js中的属性值,也可以是一个对象。
       
  • 获取对象中的属性

    • 方式1:对象.属性名。如果获取对象中没有的属性,不会报错而是返回undefined。
    • 方式2:可以使用对象["属性名"] = 属性值这种形式去操作属性。
      • 重要:使用[]这种形式去操作属性,更加的灵活,因为,我们可以在[]中直接传递一个变量,这样变量值是多少就会读取那个属性。
         
  • 修改对象的属性值

    对象.属性名 = 新值
    对象[属性名] = 新值
    
  • 删除对象的属性

      delete obj.name
      delete obj['name']
    
  • in运算符
    通过in运算符可以检查一个对象中是否含有指定的属性。如果有则返回true,没有则返回false

    "属性名" in 对象
    对象.hasOwnProperty('属性名')
    
  • 用对象字面量创建对象
    var obj = {};

    // 举例
    var obj1 = {
      name: "小明",
      age: 12,
      gender: "男",
      body: {
          height: "150cm",
          weight: "35Kg"
      },
      sayName: function () {
          console.log("我是" + this.name)
        }
    }
    console.log(obj1.body.weight);
    console.log(obj1.sayName());
    }
    
  • 遍历对象中的属性:for in
    语法:

    for (var 变量 in 对象) {
    
    }
    

    解释:对象中有几个属性,循环体就会执行几次。每次执行时,会将对象中的每个属性的属性名赋值给变量。


创建对象

对象字面量

对象的字面量就是一个{}。里面的属性和方法均是键值对。

var Person = {
  name: "小明",
  age: "12",
  gender: "男",
  sayName: function() {
    console.log("我是" + this.name)
  }
}

工厂模式

可以大批量的创建对象。

function Person(name, age, gender) {
    var person = {
        name: name,
        age: age,
        gender: gender,
        sayName: function () {
            console.log("我是" + this.name);
        }
    }
    return person
}

var p = Person("小明", "12", "男")
console.log(p);
p.sayName()

弊端:

  • 使用工厂方法创建的对象,使用的构造函数都是Object。所以创建的对象都是Object这个类型,就导致我们无法区分出多种不同类型的对象。

利用构造函数

function Person(name, age, gender) {
    this.name = name
    this.age = age
    this.gender = gender
    this.sayName = function () {
        console.log("我是" + this.name)
    }
}
var p = new Person("小明", "12", "男")
console.log(p)
p.sayName()
  • 构造函数和普通函数的区别
    • 构造函数就是一个普通的函数,创建方式和普通函数没有区别,不同的是构造函数习惯上首字母大写。
    • 构造函数和普通函数的区别就是调用方式的不同:普通函数是直接调用,而构造函数需要使用new关键字来调用。
    • this的指向也有所不同:
      • 以函数的形式调用时,this永远都是window。比如fun();相当于window.fun();
      • 以方法的形式调用时,this是调用方法的那个对象
      • 以构造函数的形式调用时,this是新创建的那个对象

new一个构造函数的执行流程:

  • 开辟内存空间,存储新创建的对象
  • 将新建的对象设置为构造函数中的this,在构造函数中可以使用this来引用 新建的对象
  • 执行函数中的代码(包括设置对象属性和方法等)
  • 将新建的对象作为返回值返回

利用class创建对象

利用class创建对象是es6的规范。

class Person{
    constructor(name, age, gender) {
      this.name = name
      this.age = age
      this.gender = gender
      this.sayName = function() {
        console.log("我是" + this.name)
      }
    }
}
var p = new Person("小明", "12", "男")
console.log(p)
p.sayName()

完整构造函数创建对象的方式

  • 创建构造函数
  • 创建原型对象
  • 设置构造函数原型对象属性prototype

原型对象

共享属性和方法。
prototype

var objProto = {
    fn1: function() {},
    fn2: function() {}
}
Obj.prototype = objProto

Oby.prototype.fn3 = function() {}
  • 实例.__proto__构造函数.prototype都指的是原型对象。
  • 原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象,我们可以将对象中共有的内容,统一设置到原型对象中。
  • 使用 in 检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true
  • 可以使用对象的hasOwnProperty()来检查对象自身中是否含有该属性。

原型链

原型对象也是对象,所以它也有原型,当我们使用或访问一个对象的属性或方法时:

  • 它会先在对象自身中寻找,如果有则直接使用;
  • 如果没有则会去原型对象中寻找,如果找到则直接使用;
  • 如果没有则去原型的原型中寻找,直到找到Object对象的原型。
  • Object对象的原型没有原型,如果在Object原型中依然没有找到,则返回 null

类、实例

使用同一个构造函数创建的对象,我们称为一类对象,也将一个构造函数称为一个

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

使用instanceof可以检查一个对象是否为一个类的实例。

语法如下:
对象 instanceof 构造函数

如果是,则返回true;否则返回false

所有的对象都是Object的后代,因此 任何对象 instanceof Object 的返回结果都是true


栈内存和堆内存

JS中,所有的变量都是保存在栈内存中的。

基本数据类型:

  • 基本数据类型的值,直接保存在栈内存中。值与值之间是独立存在,修改一个变量不会影响其他的变量。

引用数据类型:

  • 对象是保存到堆内存中的。每创建一个新的对象,就会在堆内存中开辟出一个新的空间,而变量保存了对象的内存地址(对象的引用)。如果两个变量保存了同一个对象的引用,当一个通过一个变量修改属性时,另一个也会受到影响。

this

解析器在调用函数每次都会向函数内部传递进一个隐含的参数,这个隐含的参数就是this,this指向的是一个对象,这个对象我们称为函数执行的 上下文对象。

根据函数的调用方式的不同,this会指向不同的对象:

  1. 以函数的形式调用时,this永远都是window。比如fun();相当于window.fun();
  2. 以方法的形式调用时,this是调用方法的那个对象
  3. 以构造函数的形式调用时,this是新创建的那个对象
  4. 使用call和apply调用时,this是指定的那个对象

箭头函数中this的指向:

  • ES6中的箭头函数并不会使用上面四条标准的绑定规则,而是会继承外层函数调用的this绑定(无论this绑定到什么)。

改变this指向

  • 通过callapply在调用函数时修改this指向

    obj1.fn.call(obj2 [, "参数1", "参数2", ···]) // 参数按次序传入
    obj1.fn.apply(obj2 [, ["参数1", "参数2", ···]]) // 参数以数组的形式传入
    
  • 通过bind修改this指向

    var obj1 = {
      fn1: function() {}.bind(obj2)
    }
    
    var fn2 = function() {}.bind(obj3)
    

JSON

JSON:JavaScript Object Notation(JavaScript对象表示形式)。

JSON和对象字面量的区别:JSON的属性必须用双引号引号引起来,对象字面量可以省略。

{
    "name": "小明",
    "age": "12",
    "gender": "男",
};

注:json里一般放常量、数组、对象等,但很少放function。

对象和json没有长度,json.length的打印结果是undefined。不能用for循环遍历(因为遍历时需要获取长度length)。

json采用 for...in...进行遍历


数组

数组(Array)是属于内置对象。

数组和普通对象的功能类似,也是用来存储一些值的。不同的是:

  • 普通对象是使用字符串作为属性名的,而数组是使用数字来作为索引来操作元素。索引:从0开始的整数就是索引。

数组的存储性能比普通对象要好。在实际开发中我们经常使用数组来存储一些数据,使用频率非常高。


数组的基本操作

数组的元素可以是任意的数据类型,也可以是对象,也可以是函数,也可以是数组。

数组的元素中,如果存放的是数组,我们就称这种数组为二维数组。

  • 创建数组对象

    • 方式一:字面量定义。
      var arr = [1,2,3];
    • 方式二:对象定义(数组的构造函数)。
      var arr = new Array(参数);
      • 如果参数为空,则表示创建一个空数组
      • 参数位置是一个数值时,表示数组长度
      • 参数位置是多个数值时,表示数组中的元素。

    数组的类型其实也是属于对象。
     

  • 向数组中添加元素
    数组[索引] = 值
     

  • 获取数组中的元素
    数组[索引]
    如果读取不存在的索引(比如元素没那么多),系统不会报错,而是返回undefined。
     

  • 获取数组的长度
    数组的长度 = 数组名.length;
    对于连续的数组,使用length可以获取到数组的长度(元素的个数);对于非连续的数组,使用length会获取到数组的最大的索引+1。因此,尽量不要创建非连续的数组。
     

  • 修改数组的长度(修改length)

    • 如果修改的length大于原长度,则多出部分会空出来,置为 null
    • 如果修改的length小于原长度,则多出的元素会被删除,数组将从后面删除元素。
    • 特例:伪数组arguments的长度可以修改,但是不能修改里面的元素。

数组的常用方法

概览

四个基本方法:

方法 描述 备注
push() 向数组的最后面插入一个或多个元素,返回结果为该数组新的长度 会改变原数组
pop() 删除数组中的最后一个元素,返回结果为被删除的元素 会改变原数组
unshift() 在数组最前面插入一个或多个元素,返回结果为该数组新的长度 会改变原数组
shift() 删除数组中的第一个元素,返回结果为被删除的元素 会改变原数组

常见方法:

方法 描述 备注
slice() 从数组中提取指定的一个或多个元素,返回结果为新的数组 不会改变原数组
splice() 从数组中删除指定的一个或多个元素,返回结果为新的数组 会改变原数组
concat() 连接两个或多个数组,返回结果为新的数组 不会改变原数组
join() 将数组转换为字符串,返回结果为转换后的字符串 不会改变原数组
reverse() 反转数组,返回结果为反转后的数组 会改变原数组
sort() 对数组的元素,默认按照Unicode编码,从小到大进行排序 会改变原数组

遍历数组的方法:

方法 描述 备注
for循环    
forEach() 和 for循环类似,但需要兼容IE8以上 forEach() 没有返回值。也就是说,它的返回值是 undefined
map() 对原数组中的每一项进行加工,将组成新的数组 不会改变原数组
filter() 对数组中每一项运行回调函数,该函数返回结果是true的项,将组成新的数组,返回结果为新的数组。可以起到过滤的作用 不会改变原数组
every() 如果有一项返回false,则停止遍历,此方法返回 false 一假即假。要求每一项都返回true,最终的结果才返回true
some() 只要有一项返回true,则停止遍历,此方法返回true 一真即真。要求每一项都返回false,最终的结果才返回false
reduce 为数组中的每一个元素,依次执行回调函数  

其他方法:

方法 描述
indexOf(value) 从前往后索引,获取 value 在数组中的第一个下标
lastIndexOf(value) 从后往前索引,获取 value 在数组中的最后一个下标
find(function()) 找出第一个满足「指定条件返回true」的元素。
findIndex(function()) 找出第一个满足「指定条件返回true」的元素的index
Array.from(arrayLike) 将伪数组转化为真数组
Array.of(value1, value2, value3) 将一系列值转换成数组。

详解

基本方法
  • push()
    向数组的最后面插入一个或多个元素,返回结果为该数组新的长度。
    数组的新长度 = 数组.push(元素);
     
  • pop()
    删除数组中的最后一个元素,返回结果为被删除的元素。
    被删除的元素 = 数组.pop();
     
  • unshift()
    在数组最前面插入一个或多个元素,返回结果为该数组新的长度。插入元素后,其他元素的索引会依次调整。
    数组的新长度 = 数组.unshift(元素);
     
  • shift()
    删除数组中的第一个元素,返回结果为被删除的元素。
    被删除的元素 = 数组.shift();

常见方法
  • slice()
    从数组中提取指定的一个或者多个元素,返回结果为新的数组(不会改变原来的数组)。
     
    备注:该方法不会改变原数组,而是将截取到的元素封装到一个新数组中返回。

     新数组 = 原数组.slice(开始位置的索引, 结束位置的索引);    //注意:包含开始索引,不包含结束索引
    
  • splice()
    从数组中删除指定的一个或多个元素,返回结果为新的数组(会改变原来的数组)。
     
    备注:该方法会改变原数组,会将指定元素从原数组中删除;被删除的元素会封装到一个新的数组中返回。

    新数组 = 原数组.splice(起始索引index, 需要删除的个数, 第三个参数, 第四个参数...);
    

    第三个及之后的参数,表示:向原数组中添加新的元素,这些元素将会自动插入到开始位置索引的前面。
     

  • concat()
    连接两个或多个数组,返回结果为新的数组。(不会改变原数组)。
    新数组 = 数组1.concat(数组2, 数组3 ...);
     

  • join()
    将数组转换为字符串,返回结果为转换后的字符串(不会改变原来的数组)。
     
    补充:join()方法可以指定一个字符串作为参数,这个字符串将会成为数组中元素的连接符;如果不指定连接符,则默认使用 , 作为连接符,此时和toString()的效果是一致的。
    新的字符串 = 原数组.join(参数); // 参数选填
     

  • reverse()
    反转数组,返回结果为反转后的数组(会改变原来的数组)。
    反转后的数组 = 数组.reverse();
     

  • sort()
    对数组的元素进行从小到大来排序(会改变原来的数组)。

    • 无参时
      使用 sort() 方法时不带参,则默认按照Unicode编码,从小到大进行排序。
      排序后的数组 = 数组.sort();

    • 带参时

      • sort()方法中带参,我们就可以自定义排序规则。
      • 可以在sort()添加一个回调函数,来指定排序规则。回调函数中需要定义两个形参,浏览器将会分别使用数组中的元素作为实参去调用回调函数
      • 浏览器根据回调函数的返回值来决定元素的排序:(重要)
        • 如果返回一个大于0的值,则元素会交换位置
        • 如果返回一个小于0的值,则元素位置不变
        • 如果返回一个0,则认为两个元素相等,则不交换位置
    • 冒泡排序

      // 自定义排序规则
      var result = arr.sort(function(a, b) {
      return a - b; // 升序排列
      // return b - a; // 降序排列
      });
      

遍历方法

遍历数组即:获取并操作数组中的每一个元素。在我们的实战开发中,使用得非常频繁。

遍历数组的方法包括:every()filter()forEach()map()some()

备注:这几个方法不会修改原数组。

数组/boolean/无 = 数组.every/filter/forEach/map/some(function(item, index, arr){
    程序和返回值;
})
  • forEach()
    forEach()方法需要一个函数作为参数。这种函数,是由我们创建但是不由我们调用的,我们称为回调函数。
     
    数组中有几个元素,该回调函数就会执行几次。执行完毕后,浏览器会将遍历到的元素。
     
    回调函数中传递三个参数:

    • 第一个参数,就是当前正在遍历的元素
    • 第二个参数,就是当前正在遍历的元素的索引
    • 第三个参数,就是正在遍历的数组

    注意,forEach() 的返回值是 undefined。也就是说,它没有返回值。如果你尝试 tempArry = arr.forEach()这种方式来接收,是达不到效果的。
     

  • map()
    对数组中每一项运行回调函数,返回该函数的结果,组成的新数组(返回的是加工之后的新数组)。

    const arr2 = arr1.map(item => item.name); // 将数组 arr1 中的 name 属性,存储到 数组 arr2中
    
  • filter()
    对数组中每一项运行回调函数,该函数返回结果是true的项,将组成新的数组(返回值就是这个新的数组)。

    const arr1 = [1, 3, 6, 2, 5, 6];
    const arr2 = arr1.filter(item => item > 4); //将arr1中大于4的元素返回,组成新的数组
    
  • every()
    对数组中每一项运行回调函数,如果都返回trueevery就返回true;如果有一项返回false,则停止遍历,此方法返回false
     
    注意:every()方法的返回值是boolean值,参数是回调函数。
     

  • some()
    对数组中每一项运行回调函数,只要有一项返回true,则停止遍历,此方法返回true
     
    注意:some()方法的返回值是boolean值。
     

  • reduce()
    为数组中的每一个元素,依次执行回调函数。
    需要对每个内容进行遍历,并且最终输出1个结果的时候,可以使用reduce()

    arr.reduce(
        function(previousValue, item, index, arr) {
    
        }, initialValue)
    

    参数解释:

    • previousValue:上一次调用回调函数时的返回值,或者初始值
    • currentValue:当前正在处理的数组元素
    • currentIndex:当前正在处理的数组元素下标
    • array:调用reduce()方法的数组
    • initialValue:可选的初始值(作为第一次调用回调函数时传给 previousValue 的值)

    举例:

    var arr = [2, 0, 1, 9, 6];
    
    sumValue = arr.reduce(function(total, item) { //  计算 arr 数组中,所有元素项的综合
        return total + item;
    }, 0);
    
    console.log('sumValue:' + sumValue); // 打印结果:18
    

其他方法
  • indexOf()lastIndexOf()
    获取数据的索引。

    索引值 = 数组.indexOf(value);
    索引值 = 数组.lastIndexOf(value);
    

    解释:

    • indexOf(value):从前往后索引,获取 value 在数组中的第一个下标。
    • lastIndexOf(value) :从后往前索引,获取 value 在数组中的最后一个下标。

    作用:

    • 利用这个方法,我们可以判断某个值是否在指定的数组中。如果没找到则返回-1
       
  • find()
    找出第一个满足「指定条件返回true」的元素。
    find(function(item, index, arr){return true})
     
    备注:一旦找到符合条件的第一个元素,将不再继续往下遍历。
     

  • findIndex()
    找出第一个满足「指定条件返回true」的元素的index。
    findIndex(function(item, index, arr){return true})
     

  • Array.from()
    将伪数组或可遍历对象转换为真数组。
    array = Array.from(arrayLike)
     
    伪数组与真数组的区别:

    • 伪数组的原型链中没有 Array.prototype,而真数组的原型链中有 Array.prototype。因此伪数组没有 pop、join等属性。
       
  • Array.of()
    将一系列值转换成数组。
    Array.of(value1, value2, value3)
     

  • isArray()
    判断是否为数组。
    布尔值 = Array.isArray(被检测的值);
     

  • toString()
    把数组转换成字符串,每一项默认用,分割。
    字符串 = 数组.toString();
     

  • valueOf()
    返回数组本身。
    数组本身 = 数组.valueOf();
     
    这个方法的意义不大。因为我们指直接写数组对象的名字,就已经是数组本身了。


包装类

JS为我们提供了三个包装类:

  • String():将基本数据类型字符串,转换为String对象。
  • Number():将基本数据类型的数字,转换为Number对象。
  • Boolean():将基本数据类型的布尔值,转换为Boolean对象。

通过上面这这三个包装类,我们可以将基本数据类型的数据转换为对象。

需要注意的是:我们在实际应用中不会使用基本数据类型的对象。如果使用基本数据类型的对象,在做一些比较时可能会带来一些不可预期的结果

方法和属性只能添加给对象,不能添加给基本数据类型。

当我们对一些基本数据类型的值去调用属性和方法时,浏览器会临时使用包装类将其转换为对象,然后在调用对象的属性和方法;调用完以后,在将其转换为基本数据类型。


垃圾回收(GC)机制

程序运行过程中会产生垃圾,这些垃圾积攒过多以后,会导致程序运行的速度过慢。所以我们需要一个垃圾回收的机制,来处理程序运行过程中产生垃圾。

当一个对象没有任何的变量或属性对它进行引用时,此时我们将永远无法操作该对象,此时这种对象就是一个垃圾,这种对象过多会占用大量的内存空间,导致程序运行变慢,所以这种垃圾必须进行清理。

上面这句话,也可以这样理解:如果堆内存中的对象,没有任何变量指向它时,这个堆内存里的对象就会成为垃圾。

JS拥有自动的垃圾回收机制,会自动将这些垃圾对象从内存中销毁。我们不需要也不能进行垃圾回收的操作。我们仅仅需要做的是:如果你不再使用该对象,那么,将改对象的引用设置为 null 即可。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352