前端基础 03、JavaScript 入门

一、什么是 JavaScript

1. 概述
  • JavaScript 是世界上最流行的脚本语言;
  • JavaScript 是运行在浏览器中的,解释型的编程语言;
  • JavaScript Java;
  • 只用了 10 天设计完成;
  • 后端人员,必须要精通 JavaScript
  • 在 Web 世界里,只有 JavaScript 能跨平台、跨浏览器,驱动网页,与用户交互;
2. JavaScript 历史
  • 1995年,网景公司凭借 Navigator 浏览器,成为 Web 时代最著名的第一代互联网公司。
  • 该公司希望能在静态 HTML 页面上,添加一些动态效果,于是找 Brendan Eich 在两周之内(10天)设计出了 JavaScript 语言。
  • 为什么起名叫 JavaScript ?原因是,当时 Java 语言非常红火,所以网景公司希望借 Java 的名气来推广,但事 实上 JavaScript 除了语法上有点像Java,其他部分基本上没啥关系。
3. ECMAScript
  • ECMAScirpt:可以理解为是 JavaScript 的一个标准;
  • 版本已经到 ES6 及更高版本,但是大部分浏览器,还只停留在支持 ES5 代码上;
  • 开发环境与线上环境,版本不一致。

二、快速入门

1. 引入 JavaScript
  1. 内部标签:JS 代码可以直接嵌在网页的任何地方,通常放到 head 中;

    
    
  2. 外部引入:把 JS 代码放到一个单独的 .js 文件,然后引入;

    
    
  3. 测试代码:

    
    
    
        
        第一个JavaScript
        
        
        
        
        
        
        
        
        
    
    
    
    
    
    
    
  4. 调试

    • 安装 Google Chrome 浏览器;
    • 打开开发者工具:快捷键 F12Ctrl + Shift + I,或鼠标右键 检查
    • 先点击控制台(Console),在这里可以直接输入 JS 代码,按回车后执行;

    • 要查看一个变量的内容,在 Console 中输入 console.log(a);,回车后显示的值,就是变量的内容。

    • 关闭 Console 请点击右上角的 × 按钮。

2. 基本语法入门
  • JavaScript 的语法和 Java 语言类似,每个语句以 ;结束,语句块用 {...};

  • JavaScript 不强制要求在每个语句的结尾加 ;,浏览器中负责执行JavaScript 代码的引擎,会自动在每个语句的结尾补上 ;

  • 完整的赋值语句:

    // 常规
    var x = 1;
    // 仍然可以视为一个完整的语句
    'Hello, world';
    // 不建议一行写多个语句
    var x = 1; var y = 2; 
    
  • 实例:

    
    
    
        
        基本语法
        
    
    
    
    
    
3. 数据类型简介

数值、文本、图形、音频、视频、网页等各种各样的数据,在 JavaScript 中定义了以下几种数据类型:

  • 变量

    • 大小写英文、数字、 $ 和 _ 的组合,且 不能用数字开头
    • 变量名也不能是 JavaScript 的关键字,如 if 、 while 等;
    • 申明一个变量用 var 语句,比如:
      var a; // 申明变量a,值为undefined
      var $b = 1; // 申明变量$b,同时给$b赋值,此时$b的值为1
      var s_007 = '007'; // s_007是一个字符串
      var Answer = true; // Answer是一个布尔值true
      var t = null; // t的值是null
      
    • 变量名也可以用中文,一般不这样用。
  • Number:JavaScript 不区分整数和浮点数,统一用 Number 表示,以下都是合法的 Number 类型:

    123; // 整数
    0.456; // 浮点数
    1.2345e3; // 科学计数法 1.2345x1000
    -99; // 负数
    NaN; // NaN表示Not a Number,当无法计算结果时用NaN表示
    Infinity; // Infinity表示无限大,当数值超过了JavaScript的Number所能表示的最大值时,就表示为Infinity
    
  • 字符串:‘abc’ “abc”

  • 布尔值:true false

  • 逻辑运算符

    &&    // 两个都为真,结果为真
    ||    // 一个为真,结果为真
    ! // 真即假,假即真(取反)
    
  • 比较运算符:用 === 进行比较;

    = // 赋值预算法
    ==    // 等于(类型不一致,值一样,也会判断为true,即判断1=='1')
    ===   // 绝对等于(类型一样,值一样,结果为true,JS 的缺陷)
    

    须知:

    • NaN:与所有的数值都不相等,包括自己;
    • 只能通过 isNaN() 来判断这个数是不是 NaN。
  • 浮点数:

    console.log(1/3)===(1-2/3); // 结果为false
    
    // 尽量避免使用浮点数进行运算,存在精度问题
    Math.abs(1/3-(1-2/3))<0.00000001; // 结果为true
    
  • null 和 undefined

    • null:空;
    • undefined:不存在。
  • 数组:Java 的数组必须是相同类型的对象,JS 中不需要这样。

    //保证代码的可读性,尽量使用[]
    var arr = [1,2,3,'hello',null,true];
    //取数组下标:如果越界了,就会报undefined
    
  • 对象:对象是一组由 键-值 组成的无序集合

    • 对象是大括号,数组是中括号;
    • 每一个属性之间使用逗号隔开,最后一个不需要加逗号。
    // java对象 Person person = new Pperson();
    var person = {
    name: 'Bob',
    age: 20,
    tags: ['js', 'web', 'mobile'],
    city: 'Beijing',
    hasCar: true,
    zipcode: null
    };
    
    • 获取对象的属性,用 对象变量.属性名 的方式
      person.name
      < 'Bob'
      person.zipcode
      < null
      
4. 严格检查模式:'use strict'
  • 预防 JavaScript 随意性导致产生的一些问题;

  • 必须写在 script 标签内的第一行。

    
    
  • IDEA 设置支持 ES6 语法

三、数据类型

1. 字符串
  • 正常字符串:使用单引号、双引号包裹

    console.log('a');
    console.log("a");
    
  • 转义字符:\ 其它转义符

    \';   // '
    \\; // \
    \n;   // 换行
    \t;   // 制表位
    \u4e2d;   // \u#### Unicode 字符
    \x41; // Ascll字符
    
  • 多行字符串:ES6 新增,反引号 ``表示

    // tab 和 ESC 键中间的字符 `` 包裹
    let msg = `这是
              一个多行
              字符串`;
    
  • 模板字符串

    • ES6 新增
    let name = '小明';
    let age = 20;
    // let message = '你好, ' + name + ', 你今年' + age + '岁了!';
    // ES6 新增
    let message = `你好, ${name}, 你今年${age}岁了!`;
    console.log(message);
    
  • 字符串长度

    let s = 'Hello, world!';
    s.length; // 13
    
  • 获取字符串指定位置字符:

    • 使用类似 Array 的下标操作,索引号从0开始:
    let s = 'Hello, world!';
    s[0]; // 'H'
    s[6]; // ' '
    s[7]; // 'w'
    s[12]; // '!'
    s[13]; // undefined 超出范围的索引不会报错,但一律返回undefined
    
  • 字符串 不可变

    • 对字符串的某个索引赋值,不会有任何错误,但是,也没有任何效果:
    let s = 'Test';
    s[0] = 'X';
    console.log(s); // s仍然为'Test'
    
  • 大小写转换

    // 注意:这里是方法,不是属性
    s.toUpperCase();  // 转大写
    s.toLowerCase();  // 转小写
    
  • 查找指定字符串出现的位置:indexOf()

    let s = 'hello, world';
    s.indexOf('world'); // 返回7
    s.indexOf('Worldd'); // 没有找到指定的子串,返回-1
    
  • 返回指定索引区间的字符串:substring()

    let s = 'hello, world'
    s.substring(0, 5); // [0,5)从索引0开始到5,返回'hello'
    s.substring(7); // 从索引7开始到结束,返回'world'
    
2. 数组
  • Array 可以包含任意的数据类型:(下标索引从0开始)

    var arr = [1, 2, 3.14, 'Hello', null, true];
    arr.length; // 6
    
  • 注意:直接给 Array 的 length 赋一个新的值会导致 Array 大小的变化

    let arr = [1, 2, 3];
    arr.length; // 3
    arr.length = 6;
    arr; // arr变为[1, 2, 3, undefined, undefined, undefined]
    arr.length = 2;
    arr; // arr变为[1, 2]
    
  • 如果数组索引赋值变小,元素就会丢失

  • 通过索引修改为新的值:

    let arr = ['A', 'B', 'C'];
    arr[1] = 99;
    arr; // arr现在变为['A', 99, 'C']
    
  • 通过索引赋值时,索引超过了范围,同样会引起 Array 大小的变化:

    let arr = [1, 2, 3];
    arr[5] = 'x';
    arr; // arr变为[1, 2, 3, undefined, undefined, 'x']
    
  • 不建议直接修改 Array 的大小,访问索引时要确保索引不会越界

数组常用方法

  1. indexOf():通过元素获得下标索引
    var arr = [10, 20, '30', 'xyz'];
    arr.indexOf(10); // 元素10的索引为0
    arr.indexOf(20); // 元素20的索引为1
    arr.indexOf(30); // 元素30没有找到,返回-1
    arr.indexOf('30'); // 元素'30'的索引为2
    
  • 注意:数字 30 和字符串 '30' 是不同的元素。
  1. slice() :类似于 String 中的substring()

    • 截取 Array 的一部分,返回一个新的数组。
    let arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
    arr.slice(0, 3); // 从索引0开始,到索引3结束,但不包括索引3: ['A', 'B', 'C']
    arr.slice(3); // 从索引3开始到结束: ['D', 'E', 'F', 'G']
    // 不传递任何参数,它就会从头到尾截取所有元素(相当于复制一个新数组)
    let aCopy = arr.slice();
    aCopy; // ['A', 'B', 'C', 'D', 'E', 'F', 'G']
    aCopy === arr; // false
    
  2. push()和pop()

    • push():向 Array 的末尾添加若干元素;
    • pop():把 Array 的最后一个元素删除掉。
    var arr = [1, 2];
    arr.push('A', 'B'); // 返回Array新的长度: 4
    arr; // [1, 2, 'A', 'B']
    arr.pop(); // pop()返回'B'
    arr; // [1, 2, 'A']
    arr.pop(); arr.pop(); arr.pop(); // 连续pop 3次
    arr; // []
    arr.pop(); // 空数组继续pop不会报错,而是返回undefined
    arr; // []
    
  3. unshift()和shift()

    • unshift():往 Array 的头部添加若干元素;
    • shift():把 Array 的第一个元素删掉。
    let arr = [1, 2];
    arr.unshift('A', 'B'); // 返回Array新的长度: 4
    arr; // ['A', 'B', 1, 2]
    arr.shift(); // 'A'
    arr; // ['B', 1, 2]
    arr.shift(); arr.shift(); arr.shift(); // 连续shift 3次
    arr; // []
    arr.shift(); // 空数组继续shift不会报错,而是返回undefined
    arr; // []
    
  4. sort() 排序

    • 直接修改当前 Array 的元素位置,直接调用时,按照默认顺序排序。
    let arr = ['B', 'C', 'A'];
    arr.sort();
    arr; // ['A', 'B', 'C']
    
  5. reverse() 元素反转

    let arr = ['one', 'two', 'three'];
    arr.reverse();
    arr; // ['three', 'two', 'one']
    
  6. splice()

    • 是修改 Array 的 万能方法
    • 可以从指定的索引开始删除若干元素,然后再从该位置添加若干元素。
    let arr = ['Microsoft', 'Apple', 'Yahoo', 'AOL', 'Excite', 'Oracle'];
    // 从索引2开始删除3个元素,然后再添加两个元素:
    arr.splice(2, 3, 'Google', 'Facebook'); // 返回删除的元素 ['Yahoo', 'AOL','Excite']
    arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
    // 只删除,不添加:
    arr.splice(2, 2); // ['Google', 'Facebook']
    arr; // ['Microsoft', 'Apple', 'Oracle']
    // 只添加,不删除:
    arr.splice(2, 0, 'Google', 'Facebook'); // 返回[],因为没有删除任何元素
    arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
    
  7. concat()

    • 当前的 Array 和另一个 Array 连接起来,并返回一个 新的 Array
    let arr = ['A', 'B', 'C'];
    let added = arr.concat([1, 2, 3]);
    added; // ['A', 'B', 'C', 1, 2, 3]
    arr; // ['A', 'B', 'C']
    
  8. join()

    • 把当前 Array 的每个元素,用指定的字符串连接起来,返回连接后的字符串。
    // 如果 Array 的元素不是字符串,将自动转换为字符串后再连接
    let arr = ['A', 'B', 'C', 1, 2, 3];
    arr.join('-'); // 'A-B-C-1-2-3'
    
  9. 多维数组

    let arr = [[1, 2, 3], [400, 500, 600], '-'];
    let x = arr[1][1];
    console.log(x); // 500
    
3. 对象
  • 无序的集合数据类型,由若干键值对组成。

    var 对象名 = {
      key: 'value',
      key: 'value',
      key: 'value'
    };
    // 多个属性之间使用逗号隔开,最后一个属性不加逗号!
    
  • JavaScript 中的所有键都是字符串,值是任意对象!

  1. 定义一个对象

    var person = {
     name: '小明',
     birth: 1990,
     school: 'No.1 Middle School',
     height: 1.70,
     weight: 65,
     score: null
    };
    
  2. 获取对象的属性:对象.属性对象["属性"]

    • 使用一个不存在的对象属性,不会报错!undefined
    var person = {
     name: '小明'
    };
    
    person["name"];  // '小明'
    person.age; // undefined
    
  3. 动态的给对象删除属性(delete)

    var person = {
     name: '小明'
    };
    delete person['name']; // 删除name属性
    person.name; // undefined
    delete person.id; // 删除一个不存在的id属性也不会报错
    
  4. 动态给新的属性添加值

    var person = {
     name: '小明'
    };
    person.age = 18; // 新增一个age属性
    person.age; // 18
    
  5. 判断对象是否拥有某一属性:in 操作符

    var person = {
     name: '小明',
     birth: 1990,
     school: 'No.1 Middle School',
     height: 1.70,
     weight: 65,
     score: null
    };
    'name' in person;    // true
    'id' in person;  // false
    
    // 继承的属性也为true
    'toString' in person;    // true
    
  6. hasOwnProperty():判断是否是对象自身拥有的属性

    var person = {
     name: '小明'
    };
    person.hasOwnProperty("toString");   // false 继承属性非自身
    person.hasOwnProperty("name");   // true
    
4. 流程控制
  • if 判断

    let age = 8;
    if (age >= 18) {
        alert('adult');
    } else if (age >= 13) {
        alert('teenager');
    } else {
        alert('kid');
    }
    
  • while 循环:避免程序死循环 while(true)

    let x = 0;
    let n = 99;
    while (n > 0) {
        x += n; // x = x + n;
        n -= 2; // n = n - 2;
    }
    console.log(x); // 2500 计算奇数和
    
  • do...while:无论条件是否为真,都先执行一遍

    let n = 0;
    do {
        n += 1; // n = n + 1;
    } while (n < 100);
    console.log(n); // 100
    
  • for 循环

    // 基础for
    let x = 0;
    let i;
    for (i = 1; i <= 100; i++) {
        x += i; // x = x + i;
    }
    console.log(x); // 5050
    
    // 遍历数组
    let arr = ['Apple', 'Google', 'Microsoft'];
    let i, x;
    for (i = 0; i < arr.length; i++) {
        x = arr[i];
        console.log(x);
    }
    
  • 编写循环代码时,务必小心编写 初始条件判断条件,尤其是 边界值

  • 特别注意 i < 100i <= 100 是不同的判断逻辑。

  • 无限循环

    let x = 0;
    for (; ;) { // 未设置判断条件,将无限循环
        if (x > 100) {
            break; // 通过if判断来退出循环
        }
        x++;
    }
    
  • for ... in:遍历属性 (数组为索引)

    let person = {
        name: 'Jack',
        age: 20,
        city: 'Beijing'
    };
    // 遍历属性
    for (let key in person) {
        // 判断属性是否为自身属性
        if (person.hasOwnProperty(key))
            console.log(`${key}:${person[key]}`);
    }
    
    // Array也是对象,元素的索引视为对象的属性,遍历出来是下标
    let a = ['A', 'B', 'C'];
    for (let i in a) {
        console.log(i); // '0', '1', '2'
        console.log(a[i]);  // 'A', 'B', 'C'
    }
    
  • 注意:for ... in 对 Array 的循环得到的是 String 而不是 Number

  • for ... of(ES6):遍历属性值 (数组为元素)

    const cars = ["BMW", "Volvo", "Mini"];
    for (let x of cars) {
        console.log(x);   // "BMW", "Volvo", "Mini"
    }
    
    // 遍历字符串
    let language = "JavaScript";
    for (let x of language) {
        console.log(x);   // 'J','a'...'t'
    }
    
    // 遍历数组(获取为元素,非下标)
    let a = ['A', 'B', 'C'];
    for (let i of a) {
        console.log(i); // 'A', 'B', 'C'
    }
    
  • forEach 循环(ES5.1):遍历数组元素及索引,不能遍历对象

    • 对象转数组:Object.keys(person);
     // 遍历元素及索引
    let age = [12, 3, 4, 22, 23, 55];
    // 传递函数(lambda简化)
    age.forEach((value, index) => {
        console.log(`${index}:${value}`);
    });
    // age.forEach(function (value, index) {
    //     console.log(`${index}:${value}`);
    // });
    
5. Map 和 Set
  • 默认对象表示方式 { } ,类似其他语言中的 Map 或 Dictionary 的数据结构,即一组键值对;
  • JavaScript 的对象,键必须是字符串,无法用 Number 或者其他数据类型作为键;
  • ES6 引入了新的数据类型 Map。

Map:K-VK不能重复

// 学生姓名,学生成绩
// let names = ['Michael', 'Bob', 'Tracy'];
// let scores = [95, 75, 85];

// Map 实现
let map = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
let name = map.get('Michael'); // 95 通过key获取value
map.set('admin', 100);  // 添加元素
map.delete('Bob');  // 删除元素

Set:无序不重复的集合

  • Set 和 Map 类似,也是一组 key 的集合,但不存储 value;
  • 由于 key 不能重复,所以,在 Set 中,没有重复的 key。
let set = new Set([3, 2, 1]);
set.add(5); // 增加元素
set.delete(3);  // 删除元素
set.has(1); // 判断是否包含元素
set.size;   // 长度
6. iterator
  • 遍历 Array 可以采用下标循环,遍历 Map 和 Set 就无法使用下标;

  • 为了统一集合类型,ES6 标准引入了新的 iterable 类型(Array、Map、Set);

  • 具有 iterable 类型的集合可以通过新的 for ... of 循环来遍历。

  • 遍历数组

    // 通过for...of遍历值 / for...in 遍历下标
    let array = [1, 2, 3];
    for (let x of array) {
        console.log(x);
    }
    
  • 遍历 Map

    let map = new Map([['tom', 100], ['jack', 90], ['tim', 80]]);
    for (let x of map) {
        console.log(x);
    }
    
  • 遍历 Set

    let set = new Set([3, 2, 1]);
    for (let x of set) {
        console.log(x);
    }
    
  • forEach 方法:

    • ES5.1 同时获得 K-V
    • 它接收一个函数,每次迭代就自动回调该函数。
    // Array
    let a = ['A', 'B', 'C'];
    // element: 当前元素值  index: 当前索引  array: Array对象本身
    a.forEach((element, index, array) => {
        console.log(`index = ${index}:${element}`);
    });
    
    // Set
    let s = new Set(['set1', 'set2', 'set3']);
    // 没有索引,前两个参数都是元素本身
    s.forEach((element, sameElement, set) => {
        console.log(element);
    });
    
    // Map
    let m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
    m.forEach((value, key, map) => {
        console.log(`${key}:${value}`);
    });
    

四、函数

1. 定义函数

定义方式一 :推荐

  • function 函数名(参数){...}
// 求一个数的绝对值
function abs(x) {
    if (x >= 0) {
        return x;
    } else {
        return -x;
    }
}
  • 一旦执行到 return 时,函数就执行完毕,并将结果返回;
  • 如果没有 return 语句,函数执行完毕后也会返回结果,只是结果为 undefined 。

定义方式二:

  • var 函数名 = function (参数){...};
let abs = function (x) {
    if (x >= 0) {
        return x;
    } else {
        return -x;
    }
};
  • function(x){….} 这是一个匿名函数,把结果赋值给 abs,通过 abs 调用函数;
  • 两种定义完全等价;
  • 注意:第二种方式,按照完整语法需要在函数体末尾加一个 ;,表示赋值语句结束。
2. 调用函数
  • 调用函数时,按顺序传入参数即可:

    abs(10); // 返回10
    abs(-9); // 返回9
    
  • 参数问题:JavaScript 可以传任意多个参数;

    abs(10, 'blablabla'); // 返回10
    abs(-9, 'haha', 'hehe', null); // 返回9
    
  • 传入的参数比定义的少也没有问题,也可以不传:

    abs(); // 返回NaN
    
  • 此时 abs(x) 函数的参数 x 将收到 undefined ,计算结果为 NaN。

  • 要避免收到 undefined ,可以对参数进行检查:

function abs(x) {
    // 判断参数是否为 number 类型
    if (typeof x !== 'number') {
        throw 'Not a number';
    }
    if (x >= 0) {
        return x;
    } else {
        return -x;
    }
}

arguments

  • arguments 是关键字,只在函数内部起作用;

  • 永远指向当前函数的调用者传入的所有参数;

  • 是一个数组。

    function foo(x) {
        console.log(`x = ${x}`); // 10
        for (let i = 0; i < arguments.length; i++) {
            console.log(`arg[${i}]=${arguments[i]}`);
        }
    }
    
    foo(10, 20, 30);
    
  • 利用 arguments ,即使函数不定义任何参数,还是可以拿到参数的值:

    function abs() {
        if (arguments.length === 0) {
            return 0;
        }
        let x = arguments[0];
        return x >= 0 ? x : -x;
    }
    
    abs(); // 0
    abs(10); // 10
    abs(-9); // 9
    
  • arguments 最常用于判断传入参数的个数:

    // foo(a[, b], c)
    // 接收2~3个参数,b是可选参数,如果只传2个参数,b默认为null:
    function foo(a, b, c) {
        if (arguments.length === 2) {
            // 实际拿到的参数是a和b,c为undefined
            c = b; // 把b赋给c
            b = null; // b变为默认值
        }
        // ...
    }
    
  • arguments 包含所有的参数,想使用多余的参数操作,需要排除已有参数。

rest 参数

  • ES6 以前,获取参数:

    function foo(a, b) {
        let i, rest = [];
        if (arguments.length > 2) {
            // 将多出来的参数,放入数组
            for (i = 2; i < arguments.length; i++) {
                rest.push(arguments[i]);
            }
        }
        console.log('a = ' + a);
        console.log('b = ' + b);
        console.log(rest);
    }
    
  • ES6 标准引入了rest 参数,获取除了已定义的参数之外所有的参数

    function foo(a, b, ...rest) {
        console.log('a = ' + a);
        console.log('b = ' + b);
        console.log(rest);
    }
    
    foo(1, 2, 3, 4, 5);
    // 结果:
    // a = 1
    // b = 2
    // Array [ 3, 4, 5 ]
    foo(1);
    // 结果:
    // a = 1
    // b = undefined
    // Array []
    
  • 注意:rest 参数只能写在最后,前面必须用 ... 标识

  • 从运行结果可知,传入的参数先绑定 a 、 b ,多余的参数以数组形式交给变量 rest ,不再需要 arguments 就获取全部参数。

3. 变量的作用域
  1. 变量作用域
  • 在 JavaScript 中, var 定义的变量,是有作用域的;

  • 如果在 函数体内 声明,则在 函数体外 不可以使用。

    function foo() {
        var x = 1;    
    }
    
    x = x + 2;  // Uncaught ReferenceError: x is not defined
    
  • 不同函数内部的同名变量,互不影响:

    function foo() {
        var x = 1;
        x = x + 1;
    }
    
    function bar() {
        var x = 'A';
        x = x + 'B';
    }
    
  • 内部函数可以访问外部函数的成员,反之则不行:

    function foo() {
        var x = 1;
    
        function bar() {
            var y = x + 1; // bar可以访问foo的变量x!
        }
    
        var z = y + 1; // ReferenceError! foo不可以访问bar的变量y!
    }
    
  • 内部函数和外部函数的变量名重名

    function foo() {
        var x = 1;
    
        function bar() {
            var x = 'A';
            console.log('x in bar() = ' + x); // 'A'
        }
    
        console.log('x in foo() = ' + x); // 1
        bar();
    }
    
    foo();
    

    总结:

    • JavaScript 的函数,查找变量从自身函数开始,从 查找。
    • 如果内部函数、外部函数有 重名的变量,则内部函数的变量将 屏蔽 外部函数的变量。
  1. 提升变量作用域
  • JavaScript 的函数定义特点,先扫描整个函数体的语句,把所有声明的变量 自动提升到函数顶部

    function foo() {
        var x = 'Hello, ' + y;
        console.log(x);
        var y = 'Bob';
    }
    
    foo();
    
  • 结果:Hello, undefined , 说明 y 的值为 undefined;

  • 自动提升了变量 y 的声明,但不提升变量 y 的赋值;

  • JavaScript 引擎看到的代码相当于:

    function foo() {
        var y; // 提升变量y的声明,此时y为undefined
        var x = 'Hello, ' + y;
        console.log(x);
        y = 'Bob';
    }
    
  • 在函数内部定义变量时,要严格遵守 在函数内部,首先申明所有变量

  • 常见做法:在函数头部,先声明,再使用:

    function foo() {
        var x = 1,  // x初始化为1
            y = x + 1,  // y初始化为2
            z, i;   // z和i为undefined
        // 其他语句:
    }
    
  1. 全局函数
  • 不在任何函数内定义的变量,就具有全局作用域。
    // 全局变量
    var a = 1;
    function f() {
        console.log(a);
    }
    f();
    console.log(a);
    

全局对象 window

  • JavaScript 默认有一个全局对象 window ,全局作用域的变量,实际上是被绑定到 window 的一个属性:

    `use strict`
    var course = 'JavaScript';
    alert(course);  // 'JavaScript'
    alert(window.course);   // 'JavaScript
    
  • 直接访问全局变量 course 和访问 window.course 是完全一样;

  • 顶层函数的定义,也被视为一个全局变量,并绑定到 window 对象:

    function foo() {
        alert('foo');
    }
    
    foo();  // 直接调用foo()
    window.foo();   // 通过window.foo()调用
    
  • alert() 函数其实也是 window 的一个变量:

window.alert('调用window.alert()');
// 把alert保存到另一个变量:
var old_alert = window.alert;
// 给alert赋一个新函数:
window.alert = function () {
}
alert('无法用alert()显示了!');
// 恢复alert:
window.alert = old_alert;
alert('又可以用alert()了!');

说明:

  • JavaScript 实际上只有一个全局作用域。
  • 任何变量(函数也视为变量),如果没有在当前函数作用域中找到,就会继续往上查找,最后如果在全局作用域中也没有找到,则报 ReferenceError 错误。

规范:

  • 不同的 JavaScript 文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现;
  • 减少冲突:把所有变量和函数,全部绑定到一个自定义全局变量中。
// 唯一的全局变量MYAPP:
var MYAPP = {};
// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;
// 其他函数:
MYAPP.foo = function () {
    return 'foo';
};
  • 把自己的代码全部放入唯一的名字空间 MYAPP 中,会大大减少全局变量冲突的可能。
  • 很多 JavaScript 库都也是这样的:jQuery,YUI,underscore等等。
  1. 局部作用域:let
  • 在 for 循环等语句块中,是无法定义具有局部作用域的变量的:

    function foo() {
        for (var i = 0; i < 100; i++) {
            // ...
        }
        i += 100;   // 问题:出了作用域,仍然可以引用变量i
        console.log(i);   // 200
    }
    
  • ES6 用 let 替代 var ,解决局部作用域冲突问题

    function foo() {
        for (let i = 0; i < 100; i++) {
            // ...
        }
        i += 100;   // Uncaught ReferenceError: i is not defined
        console.log(i);
    }
    
  • 建议:用 let 定义局部作用域变量

  1. 常量 const
  • ES6 之前:用全部大写的变量来表示 常量,建议不要修改它的值:

    var PI = 3.14
    console.log(PI);
    PI = 123;   // 值可以修改
    console.log(PI);
    
  • ES6 引入了关键字 const 定义常量, constlet 都具有块级作用域:

    const PI = 3.14;    // 只读变量
    console.log(PI);
    // TypeError: Assignment to constant variable
    PI = 123;   // 某些浏览器不报错,但是无效果
    console.log(PI);    // 3.14
    
4. 方法

定义方法

  • 方法:对象中的函数,称为这个对象的方法;

  • 对象只包含:属性、方法;

  • 方法调用:对象.方法名();,方法名后,一定要带()

  • 实例:

    var person = {
        name: '小明',
        birth: 1990,
        age: function () {
            // 获取当前年份
            let y = new Date().getFullYear();
            // 返回年龄
            // this 当前调用它的对象
            return y - this.birth;
        }
    };
    person.age; // 返回值:方法 person.age()
    person.age();   // 返回具体数值
    
  • 在方法内部,this 始终指向当前调用它对象,如:person 调用,this.birth 则指向 person 的 birth 属性。

  • 把上述代码拆分:

    // 把年龄方法拆分出来(函数)
    function getAge() {
        let y = new Date().getFullYear();
        return y - this.birth;
    }
    
    var person = {
        name: '小明',
        birth: 1990,
        age: getAge
    };
    person.age();   // 正常结果
    getAge();   // NaN
    

    分析:

    • person.age();:以对象的方法形式调用,this 指向对象 person,所以返回 person 的属性;
    • getAge();:单独调用函数,this 指向全局对象(window),没有对应的属性,所以返回 NaN。

apply:控制 this 的指向

  • 函数名.apply(对象,[]);
  • JavaScript 的所有函数都可以调用;
  • 接收两个参数:
    1. this 指向的变量;
    2. Array :数组对象,表示函数本身。
    function getAge() {
        let y = new Date().getFullYear();
        return y - this.birth;
    }
    
    var person = {
        name: '小明',
        birth: 1990,
        age: getAge
    };
    
    // person:this指向的对象; []:空参,数组对象
    getAge.apply(person,[]);
    

五、内部对象

1. 标准对象
  • JavaScript 里,一切都是对象;
  • typeof:获取对象的类型,返回字符串:
typeof 123; // 'number'
typeof NaN; // 'number'
typeof 'str'; // 'string'
typeof true; // 'boolean'
typeof undefined; // 'undefined'
typeof Math.abs; // 'function'
typeof null; // 'object'
typeof []; // 'object'
typeof {}; // 'object'
2. Date
  • Date:表示日期和时间
var now = new Date();
now;   // Mon Feb 07 2022 10:44:36 GMT+0800 (中国标准时间)
now.getFullYear();  // 年份
now.getMonth(); // 月份:范围是0~11,值需要 +1
now.getDate();  // 日
now.getDay();   // 星期
now.getHours(); // 小时, 24小时制
now.getMinutes();   // 分钟
now.getSeconds();   // 秒
now.getMilliseconds();  // 毫秒数
now.getTime();  // 时间戳 全世界统一 1970 1.1 00:00:00 毫秒数
console.log(new Date(1644204579172));   // 时间戳转为时间
  • 注意:当前时间是浏览器从本机操作系统获取的时间,不一定准确,当前时间设定为任何值。

  • 转换

    var d = new Date(1644204579172);
    d.toLocaleString;   // 注意:调用的是一个方法,不是一个属性
    d.toLocaleString(); // 显示的字符串与操作系统设定的格式有关
    d.toUTCString();    // UTC时间,与本地时间相差8小时
    
  • 时区转换:只需要传递时间戳,或者从数据库里读时间戳,再让 JavaScript 自动转换为当地时间。

  • 获取当前时间戳:

    if (Date.now) {
      // 老版本IE没有now()方法
        console.log(Date.now()); 
    } else {
        console.log(new Date().getTime());
    }
    
3. JSON
  • 早期,所有的数据传输习惯都使用 XML 文件
  • JSON(JavaScript Object Notation,JS 对象标记)轻量级数据交换格式;
  • 简洁、结构清晰,使 JSON 成为理想的数据交换语言;
  • 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率;
  • 在 JavaScript 中,一切皆为对象,任何 JS 支持的类型,都可以用 JSON 来表示。

    格式:

    • 对象:{}
    • 数组:[]
    • 键值对:key:value
  • JSON 键值对:

    {"name":"Tom"}
    {"age":"20"}
    {"sex":"男"}
    
  • JSON 是 JavaScript 对象的字符串表示法,使用文本表示一个 JS 对象的信息,本质是一个字符串。

    // 对象:键名也可以使用引号包裹
    var obj = {a: 'Hello', b: 'World'};
    // JSON:本质是一个字符串
    var json = '{"a": "Hello", "b": "World"}';
    
  • JSON 和 JavaScript 对象互转:

    • JSON.parse();:JSON 转 JavaScript 对象
    // JSON 转 JavaScript 对象
    var obj = JSON.parse('{"a": "Hello", "b": "World"}');
    // 结果是 {a: 'Hello', b: 'World'}
    
    • JSON.stringify();:JavaScript 对象转 JSON
    // JavaScript  对象转 JSON
    var json = JSON.stringify({a: 'Hello', b: 'World'});
    // 结果是 '{"a": "Hello", "b": "World"}'
    
  • 实例:




    
    JSON
    





4. Ajax
  • 原生的 JS 写法,xhr 异步请求;
  • jQuery 封装好的方法 $(“#name”).ajax(“”)
  • axios 请求。

六、面向对象编程

1. 原型对象
  • 面向对象的两个基本概念:

    • 类:对象的类型模板,原型对象;
    • 对象:根据类创建的具体实例。
  • JavaScript 的面向对象,和其他语言,如:Java、C# 的面向对象,有区别

    • JavaScript 不区分 实例 的概念;
    • 通过 原型 (prototype)来实现面向对象编程。
  • 原型:可以理解为父类(但不同),B 对象,通过指向,使用 A 对象的属性和方法,A 就是 B 的原型对象;

    // user 的原型对象
    var student = {
        name: 'Robot',
        age: 20,
        run: function () {
            console.log(this.name + ' is running...');
        }
    };
    var user = {
        name: '小明'
    };
    // user的原型对象是student
    user.__proto__ = student;
    
2. class
  • JavaScript 的对象模型是基于原型实现的;

  • 优点:简单;

  • 缺点:

    • 比传统的 类-实例 模型困难;
    • 继承的实现需要编写大量代码;
    • 需要正确实现原型链。
    // 了解:函数实现 Student 的方法
    function Student(name) {
        this.name = name;
    }
    
    // 通过原型,给Student新增一个方法
    Student.prototype.hello = function () {
        alert('Hello, ' + this.name + '!');
    };
    var user = new Student("user").hello();
    
  • ES6 引入 class

    // ES6 定义一个学生类
    class Student {
        // constructor 构造函数
        constructor(name) {
            this.name = name;
        }
    
        hello() {
            alert(`Hello,${this.name}`);
        }
    }
    
    // 创建实例
    let user1 = new Student("user1");
    let user2 = new Student("user2");
    // 调用
    user1.hello();
    user2.hello();
    
3. 继承 extends
// 学生类
class Student {
    constructor(name) {
        this.name = name;
    }

    hello() {
        alert('hello')
    }
}

// 继承学生类
class PrimaryStudent extends Student {
    // 本类的构造函数
    constructor(name, grade) {
        // 用super调用父类的构造方法!
        super(name);
        this.grade = grade;
    }

    // 本类的方法
    myGrade() {
        alert(`I am at grade ${this.grade}`);
    }
}

// 调用父类的方法
let student1 = new PrimaryStudent("小明", 5).hello();
// 调用本类的方法
let student2 = new PrimaryStudent("小明", 5).myGrade();
  • 本质:对象原型
4. 原型链

七、操作 BOM 对象(重点)

1. 浏览器介绍
  • JavaScript 和浏览器的关系:为了能在浏览器中运行;
  • BOM:浏览器对象模型;
  • IE 6~11;
  • Chrome;
  • Safari;
  • FireFox;
  • Opera;

第三方

  • QQ 浏览器;
  • 360;
2. window(重要)
  • window 对象不但充当全局作用域,而且表示浏览器窗口;
  • innerWidth、innerHeight:浏览器窗口的 内部 宽度和高度;
    • 内部宽高:除去菜单栏、工具栏、边框等占位元素后,用于显示网页的净宽高;
  • outerWidth、outerHeight:浏览器窗口的整个宽高;
  • 兼容性:IE<= 8 不支持。
    window.alert(1);
    window.innerHeight;   // 内部高度
    window.innerwidth;    // 内部宽度
    window.outerHeight;   // 外部高度
    window.outerWidth;    // 外部宽度
    //调整游览器窗口,值会改变
    
3. navigator 浏览器信息(不建议使用)
  • navigator 对象表示浏览器的信息,最常用的属性包括:

    • navigator.appName:浏览器名称;
    • navigator.appVersion:浏览器版本;
    • navigator.language:浏览器设置的语言;
    • navigator.platform:操作系统类型;
    • navigator.userAgent:浏览器设定的 User-Agent 字符串。
  • 注意:navigator 的信息可以被用户修改,所以 JavaScript 读取的值不一定是正确的。不建议直接使用,例如:

    let width;
    if (getIEVersion(navigator.userAgent) < 9) {
        width = document.body.clientWidth;
    } else {
        width = window.innerWidth;
    }
    
  • 建议:利用 JavaScript 对不存在属性返回 undefined 的特性,直接用短路运算符 || 计算:

    let width = window.innerWidth || document.body.clientWidth;
    
4. screen 屏幕信息
  • screen 对象表示屏幕的信息;
    screen.width; // 屏幕宽度,以像素为单位
    screen.height;    // 屏幕高度,以像素为单位
    
5. location(重要)
  • location 对象表示当前页面的 URL 信息
    location.protocol;    // 'http'
    location.host;    // "www.baidu.com"
    location.port;    // '8080'
    location.href;    // "https://www.baidu.com/"
    location.pathname;    // '/path/index.html'
    location.reload();    //刷新网页
    location.search;  // '?a=1&b=2'
    location.assign('新的网站');  // 设置新的地址
    
6. document(内容 DOM)
  • document 当前的页面,HTML DOM文档树;

    document.title='test';    // 改变页面标题
    
  • 获取具体的文档树节点:

    
    
    Java
    Java SE
    Java EE
  • 获取 cookie:

    document.cookie; // 'v=123; remember=true; prefer=zh'
    
  • 劫持 cookie 原理:

    
    
    
  • 为了确保安全,服务器端可以设置 cookie:httpOnly。

7. history(不建议使用)
  • 浏览器的历史记录
    // 不建议使用
    history.back();   // 后退
    history.forward();    // 前进
    

八、操作 DOM 对象(重点)

1. 核心(选择器)
  • 网页 HTML 就是 DOM 树形结构,JavaScript 常用操作:
  • 更新:更新 DOM 节点表示的 HTML 内容;
  • 遍历:遍历 DOM 节点下的子节点,以便进行进一步操作;
  • 添加:在 DOM 节点下新增一个子节点,相当于动态增加了一个 HTML 节点;
  • 删除:将该节点从 HTML 中删除,相当于删除该 DOM 节点,以及它所有的子节点。(先获取该节点的父节点,通过父节点删除)

获得 DOM 节点

  • 操作一个 DOM 节点前,需要先获取该 DOM 节点:
    • id 选择器(唯一 DOM):document.getElementById();
    • class 选择器:document.getElementsByClassName();
    • 标签选择器:document.getElementsByTagName();
  • 除 id 选择器外,其它是返回一组 DOM 节点,要精确地选择 DOM,可以先定位父节点,再从父节点开始选择,以缩小范围。
// 对应 css选择器
var h1 = document.getElementsByTagName('h1');
var p1 = document.getElementById('p1');
var p2 = document.getElementsByClassName('p2');
var father = document.getElementById('father');

//获取父节点下所有的子节点
var childrens = father.children;
var childrens = father.children[index]; // 某个节点
// father.firstChild;
// father.lastChild;
  • CSS 选择器:使用条件来获取节点
    • document.querySelector();:符合条件的第一个 DOM;
    • document.querySelectorAll();:符合条件的所有 DOM;
// 通过querySelector获取ID为q1的节点:
var q1 = document.querySelector('#q1');
// 通过querySelectorAll获取q1节点内的符合条件的所有节点:
var ps = q1.querySelectorAll('div.highlighted > p');
// 获取第一个带target属性的a元素
var node = document.querySelector("a[target]");
  • 注意:低版本的IE<8不支持 querySelector 和 querySelectorAll , IE8仅有限支持
2. 更新 DOM
  • 修改节点的文本:覆盖

    • innerHTML:可以解析 HTML 文本标签;
    • innerText:只是文本。
    // 获取

    ...

    var p = document.getElementById('p-id'); // 设置文本为ABC: p.innerText = 'ABC'; //

    ABC

    p.innerText = 'ABC'; // ABC // 设置HTML: p.innerHTML = 'ABC RED XYZ'; //

    ...

    的内部结构已修改
  • 用 innerHTML 时要注意,对字符编码,避免 XSS 攻击;

  • 修改 CSS:DOM 节点的 style 属性对应所有的 CSS,可以直接获取或设置:

    // 获取

    ...

    var p = document.getElementById('p-id'); // 设置CSS: p.style.color = '#ff0000'; // 属性使用''包裹 p.style.fontSize = '20px'; // font-size 转驼峰命名 p.style.paddingTop = '2em';
3. 插入 DOM:

插入节点

  • DOM 节点为空:
    • 通过 innerHTML 可以直接修改,相当于增加一个元素了;
  • DOM节点已存在元素:直接替换原来的所有子节点。

追加(末尾):appendChild


JavaScript

Java

Python

Scheme

创建新的标签,实现插入(末尾追加)

var
    list = document.getElementById('list'),
    haskell = document.createElement('p');  // 创建新的节点 

haskell.id = 'haskell'; // 新标签添加 id haskell.innerText = 'Haskell'; // 新标签添加内容 list.appendChild(haskell); // 追加

  • 动态创建一个节点,添加到 DOM 树中
// 可以创建style、script标签
var d = document.createElement('style');
var a = document.createElement('script');
a.innerHTML = 'alert("hi!!");'
// 设置标签属性:键值对
a.setAttribute('src','js/test.js'); // 动态加载js文件
d.setAttribute('type', 'text/css');
d.innerHTML = 'body { background: red }';
// head 头部标签
document.getElementsByTagName('head')[0].appendChild(d);
document.getElementsByTagName('head')[0].appendChild(a);

节点前插入:insertBefore

var ee = document.getElementById('ee');
var py = document.getElementById('py');
var list = document.getElementById('list');
// list 插入位置的父节点,insertBefore(new,target)
list.insertBefore(a, py);
4. 删除 DOM
  • 删除步骤:先获取父节点,再调用父节点的 removeChild 把自己删掉:

    // 拿到待删除节点:
    var self = document.getElementById('to-be-removed');
    // 拿到父节点:
    var parent = self.parentElement;
    // 删除:
    var removed = parent.removeChild(self);
    removed === self; // true
    // 删除父元素的第一个子元素
    parent.removeChild(parent.children[0]);
    
  • 注意:

    • 删除后的节点虽然不在文档树中了,但其实它还在内存中,可以随时再次被添加到别的位置。
    • 遍历删除多个节点时,children 值是实时更新的,一定要注意!
    var parent = document.getElementById('parent');
    // 删除:是动态过程
    parent.removeChild(parent.children[0]);
    // 浏览器报错:节点数从2变为1,索引 [1] 已不存在
    parent.removeChild(parent.children[1]);
    

九、操作表单(验证)

1. 回顾
  • JavaScript 操作表单和操作 DOM 是类似的,表单本身也是 DOM 树;
  • 表单输入框、下拉框等可以接收用户输入, JavaScript 可以获得用户输入的内容,或者设置新的内容。
  • HTML 表单的输入控件:
    • 文本框:,用于输入文本;
    • 密码框:,用于输入密码;
    • 单选框:,用于选择一项;
    • 复选框:,用于选择多项;
    • 下拉框:,用户不可见,但表单提交时,会把隐藏文本发送到服务器。
2. 获取值
  • 通过获取 input 节点,直接调用 value 获得对应的用户输入值:

    // 
    let input = document.getElementById('email');
    input.value; // 用户输入的值
    
  • 以上方式可以应用于 text 、password 、hidden 以及 select;

  • 单选框、复选框 value 属性返回的是 HTML 预设的值,需要用 checked 判断:

3. 设置值
  • 设置值和获取值类似,对于 text 、password 、hidden 以及 select ,直接设置 value 就可以:

    // 
    let input = document.getElementById('email');
    input.value = '[email protected]';// 文本框的内容已更新
    
  • 单选框和复选框,设置 checked 为 true 或 false 即可。

4. 提交表单

JavaScript 提交表单的方式:

  • 方式 1(不推荐):onclick 事件响应;

    
    
  • 按钮提交表单:type="button"(会绕过onsubmit())

    // 1.submit()
    
    // 2.this.form.submit()
    
    // 3.document.表单名.submit()
    
    
  • 上述方式的缺点:扰乱了浏览器对 form 的正常提交,浏览器默认是点击提交按钮 type="submit",或在最后一个输入框按回车键;

  • 方式 2(推荐):表单绑定提交事件

    
    
  • 注意:

    • return true 浏览器继续提交,如果 return false ,通常对应用户输入有误(表单验证),提示用户错误信息后,终止提交 form;
    • 在检查和修改时,为保证安全,要利用隐藏文本 来传递数据;
    • 用户名和密码,提交表单时不要传输明文口令,而是口令的 MD5;
    • 表单元素,只有设置了 name 属性,提交表单时才能传值
    
    
    
    

十、jQuery (了解)

1. 什么是 jquery
  • jquery 是 JavaScript 中的一个库;
  • 理念:写更少的代码,完成更多的工作。
  • jQuery 只是一个 jquery-xxx.js 文件,有 compressed(已压缩)和 uncompressed(未压缩)两种版本,使用时完全一样。

获取 jquery

  • 官网
  • jQuery 中文文档
  • jQuery文档2

使用 jQuery




    
    Title
    


点我试试



  • 公式: $(selector).action()
    • 美元符号定义 jQuery;
    • 选择符(selector)"查询" 和 "查找" HTML 元素;
    • jQuery 的 action() 执行对元素的操作。
2. 选择器
// 原生态js,选择器少,麻烦不好记
// 标签
document.getElementsByTagName();
// id
document.getElementById();
// 类
document.getElementsByClassName();

// jquery css中的选择器通用
$('p').click(); // 标签选择器
$('#id').click(); // id选择器
$('.class1').click(); // class选择器
// 公式:
$(selector).action()
3. 操作 DOM
  • 节点文本操作
$('#test-ul li[name=python]').text();    // 获得值
$('#test-ul li[name=python]').text('设置值');// 设置值
$('#test-ul').html();    // 获得值
$('#test-ul').html('123');  // 设置值
  • css 的操作
$('#test-ul li[name=python]').css("color","red")
  • 元素的显示和隐藏,本质display:none;
$('#test-ul li[name=python]').show();
$('#test-ul li[name=python]').hide();
  • 娱乐测试
$(window).width();
$(window).height();
// 显示、隐藏切换
$('#test-ul li[name=python]').toggle();
4. 事件
  • 鼠标事件、键盘事件、其他事件



    
    Title
    
    



mouse: 
在这里移动鼠标试试

小技巧

  1. 巩固 JS(看游戏源码)
  2. 巩固 HTML,CSS(扒网站,全部 down 下来,然后对应修改看效果)
  3. Element-ui等

你可能感兴趣的:(前端基础 03、JavaScript 入门)