JavaScript基础语法和函数

基础入门

与html结合方式

  • 内部JS:
    定义<script>,标签体内容就是js代码

  • 外部JS:
    定义<script src="xx.js">,通过src属性引入外部的js文件

数据类型

  • Number
    JavaScript不区分整数和浮点数,统一用Number表示:

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

    • 字符串模板
    var name = '小明';
    var age = 20;
    var message = '你好, ' + name + ', 你今年' + age + '岁了!';
    message = `你好, ${name}, 你今年${age}岁了!`;//反引号
    
    • 操作字符串
    var s = 'Hello, world!';
    s[0]; // 'H'
    s[6]; // ' '
    s[12]; // '!'
    s[13]; // undefined 超出范围的索引不会报错,但一律返回undefined
    
    • 注意:
    var s = 'Test';
    s[0] = 'X';
    alert(s); // s仍然为'Test'
    
  • 布尔值

  • null和undefined

    null表示一个“空”的值,undefined表示值未定义。
    区分两者的意义不大。大多数情况下,我们都应该用null。undefined仅仅在判断函数参数是否传递的情况下有用。

  • 数组

    • 创建数组
      var arr = [1, 2, 3];
      
    • JavaScript的数组可以包括任意数据类型。例如:
      [1, 2, 3.14, 'Hello', null, true];
      
  • 对象
    JavaScript的对象是一组由键-值组成的无序集合,例如:

    var person = {
    name: 'Bob',
    age: 20,
    tags: ['js', 'web', 'mobile'],
    city: 'Beijing',
    hasCar: true,
    zipcode: null
    };
    
    • 操作对象
    person.name//"Bob"
    person["name"]//"Bob"
    person.sex = "男";//添加属性
    delete person.sex;//删除属性
    
    

    如果我们要检测xiaoming是否拥有某一属性,可以用in操作符
    不过要小心,如果in判断一个属性存在,这个属性不一定是xiaoming的,它可能是xiaoming继承得到的:
    要判断一个属性是否是xiaoming自身拥有的,而不是继承得到的,可以用hasOwnProperty()方法:

变量

  • 定义变量
    var a = 1;//局部变量
    b = 2;	//全局变量
    
  • strict模式
    'use strict';
    var a = 1;//局部变量
    b = 2;	//报错
    

比较运算符

  • 注意:
    NaN == NaN;//false
    isNan(Nan);//true
    

Map和Set

JavaScript的默认对象表示方式{}可以视为其他语言中的MapDictionary的数据结构,即一组键值对
但是JavaScript的对象有个小问题,就是必须是字符串。但实际上Number或者其他数据类型作为也是非常合理的。为了解决这个问题,最新的ES6规范引入了新的数据类型Map

  • Map

    Map是一组键值对的结构,具有极快的查找速度。

    var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
    var m = new Map(); // 空Map
    m.set('Adam', 67); // 添加新的key-value
    m.set('Bob', 59);//多次对一个key放入value,后面的值会把前面的值覆盖掉
    m.has('Adam'); // 是否存在key 'Adam': true
    m.get('Adam'); // 67
    m.delete('Adam'); // 删除key 'Adam'
    m.get('Adam'); // undefined
    
  • Set

    Set和Map类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在Set中,没有重复的key。

    var s = new Set([1, 2, 3, 3, '3']);
    s; // Set {1, 2, 3, "3"}
    s.add(4);
    s; // Set {1, 2, 3, 4}
    s.add(4);
    s; // 仍然是 Set {1, 2, 3, 4}
    s.delete(3);
    s; // Set {1, 2, 4}
    

iterable

遍历Array可以采用下标循环,遍历Map和Set就无法使用下标。为了统一集合类型,ES6标准引入了新的iterable类型,ArrayMapSet都属于iterable类型。
具有iterable类型的集合可以通过新的for ... of循环来遍历。

  • for ... in
    var a = ['A', 'B', 'C'];
    a.name = 'Hello';
    a.length;//3
    for (var x in a) {
    	console.log(x); // '0', '1', '2', 'name'
    }
    
  • for ... of
    var a = ['A', 'B', 'C'];
    a.name = 'Hello';
    a.length;//3
    for (var x of a) {
    	console.log(x); // 'A', 'B', 'C'
    }
    
  • ForEach(推荐)
    //通用 由于JavaScript的函数调用不要求参数必须一致,所以可以省略后面的参数
    var a = ['A', 'B', 'C'];
    a.forEach(function (element) {
    	console.log(element);
    });
    
    //Set
    var s = new Set(['A', 'B', 'C']);
    s.forEach(function (element, sameElement, set) {
    	console.log(element);
    });
    
    //Map
    var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
    m.forEach(function (value, key, map) {
    	console.log(value);
    });
    

函数

函数定义

  • //方式一
    function func(){
    	
    }
    
    //方式二 其实是定义一个匿名函数,赋值给变量func
    var func = function(){
    	
    }
    
    //方式三 构造函数对象,赋值给变量func
    //函数本质也是一个对象
    var func = new Function(形式参数列表,方法体)
    

arguments 参数列表

  • JavaScript函数调用不要求参数一致,可以通过arguments获取参数
  • argument通常用来判断传入参数的个数 arguments.length
  • 通过下标访问获取参数,arguments[0]
  • 少传的参数值是 undefined
    function abs() {
    	//argument通常用来判断参数数量
    	if (arguments.length === 0) {
    		return 0;
    	}
    	//arguments类似Array,但不是Array
    	var x = arguments[0];
    	return x >= 0 ? x : -x;
    }
    
    abs(); // 0
    abs(10); // 10 
    abs(-9); // 9
    

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 []
    

变量作用域

  • var

    • 如果一个变量在函数体内部申明,作用域是整个函数内部
    function func(){
    	if(true){
    		var a = 1;
    	}
    	console.log(a)
    }
    func()//1
    
  • let

    • 块级作用域,作用域在{}
    function func(){
    	if(true){
    		let a = 1;
    	}
    	console.log(a)
    }
    func()// ReferenceError: a is not defined
    
  • const
    * 常量

  • 变量提升

    function foo() {
    	var x = 'Hello, ' + y; //y被提升了
    	console.log(x);
    	var y = 'Bob';
    }
    foo(); //不会报错 输出 Hello undefined
    
  • 全局作用域
    JavaScript默认有一个全局对象window,全局作用域的变量实际上被绑定到window的一个属性

    var course = 'Learn JavaScript';
    alert(course); // 'Learn JavaScript'
    alert(window.course); // 'Learn JavaScript'
    
    function foo() {
    	alert('foo');
    }
    foo(); // 直接调用foo()
    window.foo(); // 通过window.foo()调用
    
  • 命名空间
    直接把全局变量绑定到windows上容易出冲突,可以把变量和函数绑定到一个全局对象

    // 唯一的全局变量MYAPP:
    var MYAPP = {};
    
    // 其他变量:
    MYAPP.name = 'myapp';
    MYAPP.version = 1.0;
    
    // 其他函数:
    MYAPP.foo = function () {
    	return 'foo';
    };
    
  • 解构赋值

    • 数组
      把一个数组的元素分别赋值给几个变量
      var array = ['hello', 'JavaScript', 'ES6'];
      var [x, y, z] = array;
      
    • 对象
      从一个对象中取出若干属性,按属性名取,如果对应的属性不存在,返回undefined
      var person = {
      	name: '小明',
      	age: 20,
      	gender: 'male',
      	passport: 'G-12345678',
      	school: 'No.4 middle school'
      };
      var {name, age, passport} = person;
      

this

  • 对象方法(定义在对象中的函数),this指向对象

    var xiaoming = {
    	name: '小明',
    	birth: 1990,
    	age: function () {
    		var y = new Date().getFullYear();
    		return y - this.birth;
    	}
    };
    
    xiaoming.age; // function xiaoming.age()
    xiaoming.age(); // 今年调用是31,明年调用就变成32了
    
  • 全局的方法,this指向window,在strict模式下指向undefined

  • 内部嵌套的方法,this指向window,在strict模式下指向undefined

    var xiaoming = {
        name: '小明',
        birth: 1990,
        age: function () {
            var that = this; // 在方法内部一开始就捕获this
            function getAgeFromBirth() {
                var y = new Date().getFullYear();
                return y - that.birth; // 用that而不是this
            }
            return getAgeFromBirth();
        }
    };
    
  • 箭头方法(lambda表达式)
    this箭头函数内部的this是词法作用域,由上下文确定
    由于this在箭头函数中已经按照词法作用域绑定了,所以,用call()或者apply()调用箭头函数时,无法对this进行绑定,即传入的第一个参数被忽略

apply() & call()

  • 使用applay()或call()调用方法,可以控制this的值
    function getAge() {
        var y = new Date().getFullYear();
        return y - this.birth;
    }
    
    var xiaoming = {
        name: '小明',
        birth: 1990,
        age: getAge
    };
    
    xiaoming.age(); // 25
    getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空
    
  • apply()call()的区别是:
    • apply()把参数打包成Array再传入;
    • call()把参数按顺序传入。
      Math.max.apply(null, [3, 5, 4]); // 5
      Math.max.call(null, 3, 5, 4); // 5
      

装饰器

利用apply(),我们还可以动态改变函数的行为。

  • 统计一下代码一共调用了多少次parseInt()
    var count = 0;
    var oldParseInt = parseInt; // 保存原函数
    
    window.parseInt = function () {
        count += 1;
        return oldParseInt.apply(null, arguments); // 调用原函数
    };
    

高阶函数

接受函数作为参数的函数就是高阶函数,常用高阶函数:

  • map()
    // Array中的方法,把func作用在每个元素上
    function pow(x) {
        return x * x;
    }
    var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
    var results = arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81]
    results = arr.map(x => x*x);//lambda表达式
    
  • filter()
    // Array中的方法,把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素。
    var arr = [1, 2, 4, 5, 6, 9, 10, 15];
    var r = arr.filter(function (x) {
      return x % 2 !== 0;
    });
    r; // [1, 5, 9, 15]
    
    // self就是变量arr
    r = arr.filter(function (element, index, self) {
      return self.indexOf(element) === index;
    });
    
  • sort()
    var arr = [10, 20, 1, 2];
    arr.sort(function (x, y) {
        if (x < y) {
            return 1;
        }
        if (x > y) {
            return -1;
        }
        return 0;
    }); // [20, 10, 2, 1]
    

闭包

能够读取其他函数内部局部变量和参数的函数,称之为闭包;可以理解成定义在一个函数内部的函数就是闭包
闭包作为返回值,闭包引用的变量和参数都保存在函数中,即可以保存状态

  • 注意:
    闭包会延迟执行
    返回的函数(闭包)不要引用任何循环变量,或者后续会发生变化的变量(在闭包外发生变化),例如:
    function count() {
    	var arr = [];
    	for (var i=1; i<=3; i++) {
    		arr.push(function () {
    			return i * i;
    		});
    	}``
    	return arr;
    }
    
    //因为闭包引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了4,因此最终结果为16。
    var results = count();
    var f1 = results[0]; //输出16
    var f2 = results[1]; //输出16
    var f3 = results[2]; //输出16
    
  • 解决方案
    function count() {
    	var arr = [];
    	for (var i=1; i<=3; i++) {
    		arr.push((function () {
    			return i * i;
    		})(i));//创建匿名函数并立即执行
    	}
    	return arr;
    }
    
    var results = count();
    var f1 = results[0]; //输出1
    var f2 = results[1]; //输出4
    var f3 = results[2]; //输出9
    
    //创建匿名函数并立即执行
    (function (x) {
    	return x * x;
    })(3);//9
    
  • 生成一个自增长的计数器
    function next_id(){
      let current_id = 0;
      return function(){
          return ++current_id;
      }
    }
    let g= next_id();
    for(let i=0; i<100; i++){
      console.log(g());
    }
    

箭头函数(lambda表达式)

  • 如果要返回一个对象
    x => ({ foo: x })
    
  • this
    箭头函数内部的this是词法作用域,由上下文确定。

generator

generator(生成器)是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次。

  • generator跟函数很像,定义如下:

    function* foo(x) {
        yield x + 1;
        yield x + 2;
        return x + 3;
    }
    
    //foo(3)仅仅是创建了一个generator对象,还没有去执行它。
    var f = foo(3);//foo{[[GeneratorStatus]]: "suspended", [[GeneratorReceiver]]: Window}
    
    //调用generator对象方法一:
    f.next(); // {value: 4, done: false}
    f.next(); // {value: 5, done: false}
    f.next(); // {value: 6, done: true} 
    f.next(); // {value: undefined, done: true}
    
    //调用generator对象方法二:
    for (var x of f) {
        console.log(x); // 依次输出4,5; 6不输出
    }
    
  • yield语句

    yield语句的作用和return语句有些相似,但并非退出函数体,而是切出当前函数的运行时,同时可以将一个值带到主线程中
    yield语句可返回多次。

  • 例子

    //生成一个自增的ID
    function* next_id(){
      let current_id = 0;
      while(true){
          current_id++;
          yield current++;
      }
    }
    
    let g = next_id();
    for( leti = 0; i < 10; i++ ){
        console.log( g.next().value )
    }
    

学习资料