JavaScript基础

83 阅读21分钟

JavaScript基础

主要记录和C、python的语法差异

前言

1、strict模式

1、进行变量声明的时候,可以不用var。但是如果不声明,这个变量就会变成全局变量。所以为了让变量在局部起作用,需要进入到strict模式,在strict模式下运行的JavaScript代码,强制通过var申明变量,未使用var申明变量就使用的,将导致运行错误。

2、启用strict模式的方法是在JavaScript代码的第一行写上:

'use strict';

2、js代码书写位置

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    
//js代码写在<script>标签中。<script>标签包含的js代码可以放在head\body\另外的js文件中。
// <script src="xx.js"></script> 引入外部的js文件
    <script>
        function myFunction()
        {
            document.getElementById("demo").innerHTML="yyx";
        } 
     </script>
</head>

<body>
    <h1>web</h1>
    <p id="demo">段落</p>
    <button type="button" onclick="myFunction()">尝试</button>
</body>
</html>

3、浏览器调试代码

(1)在网页当中打开开发者模式,在console中,类似终端的地方,可以直接输入脚本语言,即写在script当中的语句。

(2)或者可以创建一个小的脚本文件,在sources – snippets里面创建

4、输出

输出可以在实现前端代码的任意位置进行

(1)alert() — 以警告栏的形式输出

(2)document.write() – 向html文档中写入,即显示到页面上

(3)getElementById + innerHTML – 选中HTML标签并且取代其值,写入到HTML元素当中。

(4)console.log() – 主要用来调试

<!DOCTYPE html>
<html>
<head> 
<meta charset="utf-8"> 
<title>菜鸟教程(runoob.com)</title> 
</head>
<body>

<p id="demo"></p>
<script>
var x, y, z;
x = 5;
y = 6;
z = (x + y) * 10;
// 通常的在网页当中的输出的写法
document.getElementById("demo").innerHTML = z;
</script>

</body>
</html>

一、对象

JavaScript 提供多个内建对象,比如 String、Date、Array 等等。基本的{...}定义的对象。 对象只是带有属性和方法的特殊数据类型。

可以进行不同数据类型之间的转换,有以下规则:

  • 不要使用new Number()new Boolean()new String()创建包装对象;
  • parseInt()parseFloat()来转换任意类型到number
  • String()来转换任意类型到string,或者直接调用某个对象的toString()方法;
  • 通常不必把任意类型转换为boolean再判断,因为可以直接写if (myVar) {...}
  • typeof操作符可以判断出numberbooleanstringfunctionundefined
  • 判断Array要使用Array.isArray(arr)
  • 判断null请使用myVar === null
  • 判断某个全局变量是否存在用typeof window.myVar === 'undefined'
  • 函数内部判断某个变量是否存在用typeof myVar === 'undefined'
// 将字符串转换成整型
var n = Number('123'); // 一般不用这个
var n = parseInt('123')
typeof n; // 'number'

1、定义通常意义上的对象、属性、以及对象的方法

var person = {
    name: 'Bob',
    age: 20,
    zipcode: null,
    // 特殊的属性名
    'middle-school': 'No.1 Middle School',
    
    // 对象的方法
    fullName : function() 
	{
       return this.name + " " + this.age;
    }
};

注意:

(1)属性名其实都是字符串类型的,属性对应的值是任意数据类型.

(2)对于特殊的属性名,是需要用’ ’ 括起来的。

2、访问对象的属性以及方法

// 正常的属性名
person.name;
// 特殊的属性名
preson['middle-school'];

// 方法
person.fullName();

3、属性的有关操作

// 删除属性
delete person.age;

// 添加属性
person.class = one;

// 检查是否存在
# 包括继承的属性,注意属性是字符串形式
'name' in person;
// 不包括继承的属性
person.hasOwnProperty('name');

4、方法 – this特殊变量

  • 在方法中,this 表示该方法所属的对象。
  • 如果单独使用,this 表示全局对象。
  • 在函数中,this 表示全局对象。
  • 在函数中,在严格模式下,this 是未定义的(undefined)。
  • 在事件中,this 表示接收事件的元素。
  • 类似 call() 和 apply() 方法可以将 this 引用到任何对象。
(1)this变量的指向
function getAge() {
    var y = new Date().getFullYear();
    return y - this.birth;
}

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: getAge
};

// this指向被调用的对象xiaoming
xiaoming.age(); // 25, 正常结果

// this变量指向全局对象window(非strict模式) -- 不会报错
// this变量指向define(strict模式) -- 会报错
getAge(); // NaN
(2)改变this的指向 – apply() call()
apply方法 + call方法

它接收两个参数,第一个参数就是需要绑定的this变量,第二个参数是Array,表示函数本身的参数。

  • apply()把参数打包成Array再传入;
  • call()把参数按顺序传入。
// this指向xiaoming, 参数为空
getAge.APPLY(xiaoming,[])

// apply 和 call方法的差距
Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5
(3)闭包中使用this

this指针只在age方法的函数内指向xiaoming,在函数内部定义的函数,this又指向undefined了。

'use strict';

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: function () {
    // 把外部作用域中的 this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了。在方法内部一开始就捕获this.
        var that = this; 
        
        // 内部函数,是一个闭包
        function getAgeFromBirth() {
            var y = new Date().getFullYear();
            // this在严格模式下指向undefined,非严格模式下指向window
            // return y - this.birth;
            
            return y - that.birth;
        }
        return getAgeFromBirth();
    }
};

xiaoming.age(); // Uncaught TypeError: Cannot read property 'birth' of undefined

5、内建对象

(1)Data
a.时间戳

时间戳是一个自增的整数,它表示从1970年1月1日零时整的GMT时区开始的那一刻,到现在的毫秒数。假设浏览器所在电脑的时间是准确的,那么世界上无论哪个时区的电脑,它们此刻产生的时间戳数字都是一样的,所以,时间戳可以精确地表示一个时刻,并且与时区无关。

b.获取时间

注意:JavaScript的Date对象月份值从0开始,牢记0=1月,1=2月,2=3月,……,11=12月。

// 获取系统当前时间
var now = new Data();
now.getTime(); // 以number形式表示的时间戳

// 用时间戳转换成Data
var d = new Date(1435146562875);
d; // Wed Jun 24 2015 19:49:22 GMT+0800 (CST)
(2)RegExp正则表达式

注意:用一种描述性的语言给字符串定义一个规则,用这个规则取检查字符串是否合法。

// 固定字符
\d匹配数字 \w匹配字母或者数字  .匹配任意字符 \s可以匹配一个空格(也包括Tab等空白符)

// 变长字符
用*表示任意个字符(包括0个),用+表示至少一个字符,用?表示0个或1个字符,用{n}表示n个字符,用{n,m}表示n-m个字符

// 特殊字符
由于'-'是特殊字符,在正则表达式中,要用'\'转义

// 表示范围
可以用[]表示范围,[0-9a-zA-Z\_]可以匹配一个数字、字母或者下划线;

// 指定开头和结尾
^表示行的开头,^\d表示必须以数字开头。
$表示行的结束,\d$表示必须以数字结束。
^xxx$ -- 表示xxx这一个数字

\d{3}\s+\d{3,8} // 匹配以任意个空格隔开的带区号的电话号码
a. 使用正则表达式 (默认使用贪婪匹配)
//创建 -- 第一种方式是直接通过/正则表达式/写出来,第二种方式是通过new RegExp('正则表达式')创建一个RegExp对象。
var re1 = /ABC\-001/;
var re2 = new RegExp('ABC//-001'); // 字符串的转义问题,字符串的两个\\实际上是一个\

// 检查匹配 -- RegExp对象的test()方法用于测试给定的字符串是否符合条件。
re1.test('ABC-001') //true
b. 利用正则表达式切分字符串
// 传统的空格区分不足以识别连续的空格
'a b  c'.split(''); // ['a', 'b', '', '', 'c']

// 正则
'a b  c'.split(/\s+/); // ['a', 'b', 'c']
c. 利用正则表达式将字符串进行分组
// ()表示的就是要提取的分组(Group),用exec()方法提取出子串来
// ^(\d{3})-(\d{3,8})$分别定义了两个组,可以直接从匹配的字符串中提取出区号和本地号码

var re = /^(\d{3})-(\d{3,8})$/;
re.exec('010-12345'); // ['010-12345', '010', '12345']
re.exec('010 12345'); // null
(3)JSON

1、JSON(JavaScript Object Notation),这是一种数据交换格式,用于把任何JavaScript对象变成JSON,就是把这个对象序列化成一个JSON格式的字符串,这样才能够通过网络传递给其他计算机。

2、在JavaScript中,我们可以直接使用JSON,因为JavaScript内置了JSON的解析。JSON还定死了字符集必须是UTF-8,表示多语言就没有问题了。为了统一解析,JSON的字符串规定必须用双引号"",Object的键也必须用双引号""

3、J对象和JSON的相互转换

对象转JSON

var xioaming = {
    name: '小明',
    age: 14,
    gender: true    
}
// 第二个参数用于控制如何筛选对象的键值.可以传入数组也可以是函数
// 第三个参数用来控制输出的格式
var s = JSON.stringify(xiaoming, null, '  ');
console.log(s);
// JSON格式
{
  "name": "小明",
  "age": 14,
  "gender": true,
  "height": 1.65,
  "grade": null,
  "middle-school": "\"W3C\" Middle School",
  "skills": [
    "JavaScript",
    "Java",
    "Python",
    "Lisp"
  ]
}

JSON转对象

JSON.parse('[1,2,3,true]'); // [1,2,3,true]

二、面向对象编程

1、基于原型 prototype去创建一个新的对象 – 原型继承

(1)原型链

JS的一切事物都是对象,而且会对每一个对象都设置一个原型,指定它的原型对象。所有的 JavaScript 对象都会从一个 prototype(原型对象)中继承属性和方法。

一个对象的原型链:当前对象 – > 原型对象 --> Object.prototype对象 --> undefined

var arr = [1, 2, 3];
// arr ----> Array.prototype ----> Object.prototype ----> null

由于arr的原型Array.prototype定义了indexOf()shift()等方法,因此你可以在所有的Array对象上直接调用这些方法。

(2)使用Object.create()方法 – 一般不使用

JavaScript不区分类和实例的概念,而是通过原型(prototype)来实现面向对象编程。即当想要创建一个新的对象,而且这个对象没有相应的类型使用时,就可以修改这个新对象的原型,使用Object.create()方法可以传入一个原型对象。 (自己没有的,就继承别人的)

// 原型对象:
var Student = {
    name: 'Robot',
    height: 1.2,
    run: function () {
        console.log(this.name + ' is running...');
    }
};

function createStudent(name){
    //基于Student原型创建新对象:
    var s = Object.create(Student);
    // 初始化新对象
    a.name = name;
    return s;
}

// 创建基于Student原型的小明对象
var xiaoming = createStudent('小明')
xiaoming.run(); // 小明 is running...

// 小明对象的原型就是Student
xiaoming.__proto__ === Studnet; // true
(3)用new() 创建基于原型的JavaScript的对象
a.构造函数

用关键字new来调用这个函数,并返回一个对象。那么这个函数就是构造函数。

如果不使用new,那么Student里的this是指向undefine.当使用new的时候,它就变成了一个构造函数,它绑定的this指向新创建的对象,并默认返回this

// 使用函数构造对象
function Student(name){
    this.name = name;
    this.hello = function(){
        alert('Hello, ' + this.name + '!');
    }
}

// 使用关键字new去调用函数,创建对象的实例
var xiaoming = new Student('小明');
xiaoming.name;

// xiaoming的原型链:xiaoming ----> Student.prototype ----> Object.prototype ----> null。

new Student()创建的对象还从原型上获得了一个constructor属性,它指向函数Student本身。为了让创建的对象共享一个hello函数,把hello函数移动到xiaomingxiaohong这些对象共同的原型上就可以了,也就是`Student.prototype。

b.prototype

一般是不允许给一个已存在构造器的对象中是不能添加新的属性,要添加一个新的属性需要在在构造器函数中添加。

// 不允许
Student.nationality = "English";

但是使用 prototype 属性就可以给对象的构造函数添加新的属性

// 允许
Student.prototype.nationality = "English";
c.常用的编程模式

编写一个createStudent()函数,在内部封装所有的new操作。优点:一是不需要new来调用,二是参数非常灵活,可以不传。

在这里插入图片描述

// Student构造函数
function Student(props){
    this.name = props.name ||  'Unnamed';
    this.grade  props.grade || 1 ;
}
// 将hello函数移动到Student.prototype,使得由student创建的对象共享一个hello函数
Student.prototype.hello = function(){
    alert('Hello, ' + this.name + '!');
};

function createStudent(props){
    return new Student(props || {})
}

//调用,不需要new
var xixaoming = creatStudent({
    name: '小明'
})
(4)原型继承

在这里插入图片描述

// student构造函数 -- 同上
// 基于Studnet扩展出PrimaryStudent,即要继承Student。原型链变成new PrimaryStudent() ----> PrimaryStudent.prototype ----> (Student.prototype )----> Object.prototype ----> null

//PrimaryStudent构造函数
function PrimaryStudent(props){
    // 调用希望“继承”的构造函数Student,绑定this变量:
    Student.call(this,props);
    this.grade = props.grade || 1;
}

/*
// 空函数F,利用这个中间对象改变原型链.PrimaryStudent() ----> PrimaryStudent.prototype ----> F -- >Student.prototype----> Object.prototype ----> null
function F() {
}

// 把F的原型指向Student.prototype
F.prototype = Student.prototype;

//把PrimaryStudent的原型指向一个新的F对象
PrimaryStudent.prototype = new F();
// 把PrimaryStudent原型的构造函数修复为PrimaryStudent:
PrimaryStudent.prototype.constructor = PrimaryStudent;
*/

// 可以将继承这个动作进行封装
function inherits(Child,Parent){
    var F = funtion() {};
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;    
}

// 实现原型继承链
inherits(PrimaryStudent, Student);

// 继续在PrimaryStudent原型(就是new F()对象)上定义方法:
PrimaryStudent.prototype.getGrade = function () {
    return this.grade;
};


// xiaoming是基于原型PrimaryStudent创建的对象,PrimaryStudent是继承student的。
var xiaoming = new PrimaryStudent({
    name: '小明',
    grade: 2
});
xiaoming.name; // '小明'
xiaoming.grade; // 2

2、class继承

(1)类的写法

类是用于创建对象的模板。类声明和类表达式的主体都执行在严格模式下。所有变量都必须声明。

// 以前的写法
function Student(name){
    this.name = name;
}
Student.prototype.hello = function(){
    alert()
}
a.第一种
// 定义一个class类
class Student{
    //定义类的构造函数constructor(),用来初始化对象属性。
    constructor(name){
        this.name = name;
    }
    //定义在原型对象上的hello()方法
    hello(){
        alert('Hello, ' + this.name +'!');
    }
}

// 使用new关键字创建一个Student对象
var xiaoming = new Student('小明');
xiaoming.hello();
b.第二种 – 类表达式
// 匿名类
let Runoob = class{
    constructor(name,url){
        this.name = name;
        this.url = url;
    }
};

// 命名类
let Runoob = class Ruoob2{
    constructor(name,url){
        this.name = name;
        this.url = url;
    }    
}
(2)class继承 – extend

依据一个类来定义一个类。super() 方法用于调用父类的构造函数。

class PrinmaryStudent extends Student{
    // 重写构造方法
    constructor(name,grade){
        //通过super(name)来调用父类的构造函数,否则父类的name属性无法正常初始化
        super(name);
        this.grade = grade;
    }
    // PrimaryStudent已经自动获得了父类Student的hello方法,我们又在子类中定义了新的myGrade方法
    myGrade(){
        alert('I am at grade' + this.grade);
    }
}
(3)使用get和set获取或者改变属性
class Runoob{
    constructor(name){
        //在属性名称前使用下划线字符 _ 将 getter/setter 与实际属性分开
        this._sitename = name;
    }
    // 获取属性的值
    get sitname(){
        return this._sitname;
    }
    // 改变属性的值
    set sitname(x){
        this._sitname = x;
    }
}

let noob = new Runoob("菜鸟");
// 获取值,即使 getter 是一个方法,当你想获取属性值时也不要使用括号。
document.getElementById("demo").innerHTML = noob.sitename;

// 改变值
noob.sitname = "RUNOOB";
document.getElementById("demo").innerHTML = noob.sitename;

三、变量和常量

a. 变量 – var

1、声明

定义对象是用var关键字,而且变量本身类型不固定,属于动态语言(可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量,但是要注意只能用var申明一次。)

和其他语言的不同,静态语言如果赋值类型不匹配,就会报错。

// 不指定变量类型
var a = 123;
a = 'ABC';

// 非严格模式下给未声明变量赋值创建的全局变量,是全局对象window的可配置属性,可以删除。
carname="Volvo"; // 将声明 window 的一个属性 carname。
2、解构赋值

利用简单的做法直接对多个变量 / 数组进行同时赋值,而不是一个一个赋值。

(1) 单纯赋值
// 对x,y,z三个变量同时赋值,赋值的时候,可以忽略一些元素。
var [x,y,z] = ['hello','javascript','es6']
var [,,z] = ['hello','javascript','es6']

// 对数组进行赋值
let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];

// 从person对象当中快速取出若干属性name,age,passport
var {name,age,passport} = person;
(2) 使用默认值 =
// 如果person对象没有single属性,默认赋值为true:
var {name, single=true} = person;
(3) 将属性赋值给其他变量 :
let {name, passport:id} = person;
name; // '小明'
id; // 'G-12345678'
passport; // Uncaught ReferenceError: passport is not defined
// 注意: passport不是变量,而是为了让变量id获得passport属性:
(4) 快速交换值,不需要临时变量
[x,y] = [y,x]

b.常量 – const

类似C语言当中的define, 定义一些常量。

'use strict';

const PI = 3.14;
PI = 3; // 某些浏览器不报错,但是无效果!
PI; // 3.14

c、变量的作用域

(1)局部作用域
  • 在函数体内部申明的变量作用域是整个函数体

  • 当函数嵌套时,内部函数可以访问外部函数定义的变量,反过来则不行

  • 如果内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量。

    注意:JavaScript的变量作用域实际上是函数内部,我们在**for循环等语句块中是无法定义具有局部作用域的变量的**。通常使用let关键字

let关键字 – 块级作用域
function foo(){
	for (let i=0; i<100; i++){
        
    }
}
(2)变量提升 – 变量书写规则

JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部。即所有的变量在函数顶部默认先定义,然后再相应的位置进行赋值。

声明 – var x;不是指初始化var x = 5; JavaScript 只有声明的变量会提升,初始化的不会。

// 在函数内部定义变量时,遵守“在函数内部首先申明所有变量”这一规则。最常见的做法是用一个var申明函数内部用到的所有变量
function foo(){
    var 
    	x = 1,
        y = x + 1,
        z,i;
   // 其他语句
}
(3)全局作用域
全局变量
// 如果变量在函数内没有声明(没有使用 var 关键字),该变量为全局变量。
 function myFunction() {
    carName = "Volvo";
}


// 函数体外
var carName = " Volvo";
function myFunction() {
    
}
window全局对象

JavaScript默认有一个全局对象window(浏览器窗口),即所有的全局作用域的变量都绑定到这个对象。无论是拥有全局作用域的变量,还是函数。

var course;
alert(window.course); // alert(course);

function foo(){
}
window.foo(); // foo()

通常的做法是把自己的所有变量和函数全部绑定到一个全局变量中,把自己的代码全部放入唯一的名字空间MYAPP中,会大大减少全局变量冲突的可能

// 唯一的全局变量MYAPP:
var MYAPP = {};

// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;

// 其他函数:
MYAPP.foo = function () {
    return 'foo';
};

四、数组

1、定义

  • 包含任意的数据类型。 和其他语言的不同,可以改变数组的长度。
  • 数组也是一个对象,他的属性是每个元素的索引。

2、长度的计算以及改变

var arr = [1,2,3];
// 计算长度
arr.length;

// 通过赋值改变长度
arr.length = 6;

3、其他操作

// 通过值来确定元素的索引
arr.indexOf(1);

// 截取指定位置的元素,类似python中的切片.起止参数包括开始索引,不包括结束索引
arr.slice(0,3);

// 增加
// 每次在尾部插入
arr.push(5);

// 每次在头部插入
arr.unshift(5);

// 删除
// 每次在尾部删除
arr.pop(3);
// 每次在头部删除
arr.shift(3);


// 添加和删除任意位置的元素
var arr = ['Microsoft', 'Apple', 'Yahoo', 'AOL', 'Excite', 'Oracle'];
// 从索引2开始删除3个元素,然后再添加两个元素:
arr.splice(2, 3, 'Google', 'Facebook'); // 返回删除的元素 ['Yahoo', 'AOL', 'Excite']
arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']

4、排序和反转

// 排序
arr.sort();

// 反转
arr.reverse();

5、连接

// 用指定的字符串拼接元素
var arr = ['A', 'B', 'C', 1, 2, 3];
arr.join('-'); // 'A-B-C-1-2-3'

// 拼接两个数组
var arr = ['A', 'B', 'C'];
arr.concat(1, 2, [3, 4]); // ['A', 'B', 'C', 1, 2, 3, 4]

五、函数

1、函数的定义

(1)一般函数
// 第一种 funtion 函数名(参数).末尾没有;
function abs(x){
    if (x>0){
        return x;
    }
    else{
        return -x;
    }
}
(2)匿名函数
// 第二种:匿名函数,没有函数名称,以分号结尾。函数表达式存储在变量当中。
var abs = function(x){
    ……
};
// 此时变量abs也可以当作函数使用
var result = abs(5);
(3)自调用函数 – 匿名自我调用的函数
(function(){
    var x = "hello"; // 自己调用自己
})();
(4)Arrow Function(箭头函数)
// 参数 => 函数操作
function(x){
    return x * x;
}

// 第一种:只包含一个表达式,连{ ... }和return都省略掉了
x => x * x

// 第二种:包含多条语句,这时候就不能省略{ ... }和return
x => {
    if (x>0){
        return x * x;
    }
    else{
        return -x * x;
    }
}

// 多参数
(x, y) => x * x + y * y

// 返回一个对象
x => ({foo: x})
箭头函数修复了this的指向

this总是指向词法作用域,也就是外层调用者obj。箭头函数会默认帮我们绑定外层 this 的值,所以在箭头函数中 this 的值和外层的 this 是一样的。

// 原来的写法
var obj = {
    birth: 1990,
    getAge: function () {
        var b = this.birth; // 1990
        var fn = function () {
            return new Date().getFullYear() - this.birth; // this指向window或undefined
        };
        return fn();
    }
};

// 用箭头函数修复this指向
var obj = {
    birth: 1990,
    getAge: function () {
        var b = this.birth; // 1990
        // 箭头函数, 不需要var that = this;
        // call()或者apply()调用箭头函数时,无法对this进行绑定,即传入的第一个参数被忽略.
        var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
        return fn();
    }
};
obj.getAge(;)
(5)内嵌函数
// 函数可以嵌套
function foo(){
    var x=1;
    // 函数嵌套,内部函数可以访问外部函数定义的变量,反过来则不行
    function bar(){
        var y = x + 1;
    }
}
闭包
  • 闭包是指有权访问另一个函数作用域中变量的函数。创建闭包最常见的方式就是,在一个函数内部创建另一个函数。

  • 使用场景:

    ​ 1、读取函数内部的局部变量

    ​ 2、让这些变量的值始终保存在内存中。

// 函数外不能读取函数内的变量
function f1(){
    // 局部变量
    var n=999;
}
alert(n); //error


// f2函数就是闭包
function f1(){
    var n=999;
    function f2(){
        alter(n);
    }
    return f2;
}

var result = f1(); // result就是f2
result(); //999

// f1() 函数执行后,正常情况下 f1() 的整个内部作用域被销毁,占用的内存被回收。但是现在的 f1的内部作用域 f2() 还在使用,所以不会对其进行回收。f2() 依然持有对该作用域的引用,这个引用就叫做闭包。这个函数在定义的词法作用域以外的地方被调用。闭包使得函数可以继续访问定义时的词法作用域。

注意:

调用时的传入参数可以和定义的参数不一致,多或者少都可以。多的时候按照顺序传入,少的时候返回NaN.但是如果想得到传入的所有参数,就得用arugments关键字

2、arguments关键字

只在函数的内部起作用,并且永远指向当前函数的调用者传入的参数。**并且即使函数没有定义任何的参数,还是可以拿到参数的值。**类似一个Array,但不是数组。

// 函数不定义任何参数
function abs(){
    if (arguments.length == 0 ){
        return 0;
    }
    # 即便没有定义参数,也可以拿到函数的值。
 	var x = arguments[0];
    return x > 0 ? x : -x;  
}

// 传入参数
abs(10);

注意:

如果想得到除了定义的参数的剩余的传入的参数,就可以使用rest参数。这是ES6标准。

3、rest参数

function foo(a,b,……,rest){
    console.log('a = ' + a)
    console.log('b = ' + b)
    # 放在最后,拿到剩余的参数
    console.log(rest)
}

4、return语句

注意:return后面的语句换行的时候,用{}包起来,书写规范。

return {
	name:'foo'
};

5、高级函数

这是针对数组Array的高阶函数。高阶函数里面传入的是自定义的函数,对数组Array进行相应的操作。传入的函数都需要返回值,forEach()除外。

(1) map()/reduce()

这是Array的内置函数。 map用来将函数的功能作用在数组上,reduce用来进行累计操作。

// arr.map(函数名) -- 在数组上进行相关的操作,这个操作是自定义的函数或者是数据结构
function pow(x){
	return x * x;
}
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(pow);
// arr.map(String);

// arr.reduce(函数名) -- 函数必须接收两个参数,reduce()把结果继续和序列的下一个元素做累积计算
arr.reduce(function (x, y) {
    return x + y;
}); 
(2) filter
  • 这是Array的内置函数。这是一个筛选函数,filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素。
  • filter()接收的回调函数,其实可以有多个参数。通常我们仅使用第一个参数element,表示Array的某个元素。回调函数还可以接收另外两个参数,表示元素的位置和数组本身
// 筛选数组中的奇数偶数
var r = arr.filter(function(x){
    // 继续判断,返回的是布尔型数据.True的时候,返回。
    return x % 2 !== 0;
});
利用filter,可以巧妙地去除Array的重复元素:
var r,
    arr = ['apple', 'strawberry', 'banana', 'pear', 'apple', 'orange', 'orange', 'strawberry'];

// indexOf总是返回第一个元素的位置,后续的重复元素位置与indexOf返回的位置不相等,因此被filter滤掉了。 
// 回调函数
r = arr.filter(function(element,index.self){
    return self.indexOf(element) === index;
})
console.log(r.toString());
(3) sort

直接利用sort()对数字进行排序,会导致与意愿违背的结果。所以需要在自定义比较方法,传入到高级函数sort()中。

// 比较数字
arr.sort(function(x,y){
	if (x < y){
        return -1;
    }
    if (x > y){
        return 1;
    }
    return 0;
});

// 比较字母的时候,排序应该忽略大小写,按照字母序排序。就需要忽略大小写,即把所有的都转换成大写或者小写。
x1 = s1.toUpperCase();
x2 = s2.toUpperCase();
(4) 其他函数
  • every()方法可以判断数组的所有元素是否满足测试条件。

  • find()方法用于查找符合条件的第一个元素,如果找到了,返回这个元素,否则,返回undefined

  • findIndex()和find()类似,也是查找符合条件的第一个元素,不同之处在于findIndex()会返回这个元素的索引,如果没有找到,返回-1

  • forEach()map()类似,它也把每个元素依次作用于传入的函数,但不会返回新的数组。forEach()常用于遍历数组,因此,传入的函数不需要返回值。

    // 遍历数组 依次打印每个元素
    arr.forEach(cconsole.log);
    

6、generator

传统函数以return结束,只能返回一次。generator和函数不同的是,generator由function定义(注意多出的号),并且,除了return语句,还可以用yield返回多次.

// 定义
function* foo(x){
    yield x+1;
    return x+2;
}

// 调用
foo(3) // 仅仅是创建了一个generator对象,还没有去执行它

// 第一种,不断地调用generator对象的next()方法。next()方法会执行generator的代码,然后,每次遇到yield x;就返回一个对象{value: x, done: true/false},然后“暂停”。返回的value就是yield的返回值,done表示这个generator是否已经执行结束了。如果done为true,则value就是return的返回值。
// 调用的时候,会依次传参数0,1,2。然后每个参数u执行一次,得到结果
var f = foo(3);
f.next(); // {value: 1, done: false}

// 第二种方法,用for ... of循环迭代generator对象
for (var x of foo(3)){
    console.log(x);
}

六、字符串

1、单引号和双引号的使用

  • 如果'本身也是一个字符,那就可以用""括起来,比如"I'm OK"包含的字符是I'm,空格,OK这6个字符。
  • 如果字符串内部既包含'又包含"怎么办?可以用转义字符\来标识

2、多行字符串的表示 – 反引号

`这是一个
多行
字符串`;

七、条件判断和循环

1、条件判断

和C语言的用法大致相同,注意else的{}不要省略。省略的话,如果else下面有两条语句,那么只会影响一条。

var age = 20;
if (age >= 18)
    alert('adult');
else
    console.log('age < 18'); // 添加一行日志
    alert('teenager'); // <- 这行语句已经不在else的控制范围了

2、循环

(1)for循环
// 和C语言一样,用来遍历数组
var arr = ['Apple', 'Google', 'Microsoft'];
var i, x;
for (i=0; i<arr.length; i++) {
    x = arr[i];
    console.log(x);
}
    
// for …… in 用来将对象的所有属性依次打印出来. -- 用来遍历对象
var o = {
    name: 'Jack',
    age: 20,
    city: 'Beijing'
};
for (var key in o) {
    console.log(key); // 'name', 'age', 'city'
}

// 数组也是对象,也可以用这种方式表示
for (var i in arr){
    console.log(i);
    console.log(arr[i]);
}
(2)while循环

和C语言一样

八、map和set – ES6标准新增的数据类型

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

1、Map

map是由键值对组成的。类似字典。

// 初始化空的Map 或者 有键值对的Map --里面是数组的形式
var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
var m = new Map();

// 添加
m.set('Adam', 67);

// 存在
m.has('Adam');

// 删除
m.delete('Adam');

//取值
m.get('Adam');

2、Set

类似python里面的集合,也是一组key的集合,但不存储value。没有重复的key。

// 初始化空的 或者 有键值对的Set
var s = new Set();
var s = new Set([1, 2, 3]);

// 添加
s.add(4);

// 删除
s.delete(4);

九、iterable – ES6

1、Array Set Map都属于iterable,可以通过for …… of进行同一遍历。数组可以通过下标进行遍历,但是遍历MapSet就无法使用下标。

var a = ['A', 'B', 'C'];
var s = new Set(['A', 'B', 'C']);
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);

for (var x of a) { // 遍历Array
    console.log(x);
}
for (var x of s) { // 遍历Set
    console.log(x);
}
for (var x of m) { // 遍历Map
    console.log(x[0] + '=' + x[1]);
}

2、for ... of循环和for ... in循环有何区别?

  • for … in循环将把添加了额外的属性包括在内,但Array的length属性却不包括在内。
  • for … of循环则完全修复了这些问题,它只循环集合本身的元素

3、forEach()方法,它接收一个函数,每次迭代就自动回调该函数。函数调用不要求参数必须一致.

// 数组
var a = ['A', 'B', 'C'];
a.forEach(function(element,index,array)){
          console.log(elemet + ', index=' + index)
          });
// A, index = 0
// B, index = 1
// C, index = 2

//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);
});

十、注意的点

1、运算符 == 和 ===

== : 表达式的值和类型满足一个即可

===:表达式的值和类型需要同时满足。一般switch中用恒等运算符

2、加法和连接都用+

var x = 10;
var y = 5;
var z = x + y;           // z 的结果为 15

var x = 10;
var y = "5";
var z = x + y;           // z 的结果为 "105"

3、浮点型数据使用注意事项

var x = 0.1;
var y = 0.2;
// var z = x + y  // z 的结果为 0.30000000000000004
var z = (x * 10 + y * 10) / 10;       // z 的结果为 0.3

4、字符串换行

var x = "Hello \
World";

5、Undefined 不是 Null

在 JavaScript 中, null 用于对象, undefined 用于变量,属性和方法。对象只有被定义才有可能为 null,否则为 undefined。

6、href="#"与href="javascript:void(0)"的区别

# 包含了一个位置信息,默认的锚是**#top** 也就是网页的上端。也可以使用这个去定位页面中的位置。当用户链接时,void(0) 计算为 0,但 Javascript 上没有任何效果。

<a href="javascript:void(0);">这是一个死链接</a>
<a href="#pos">可以到达指定的位置</a>

额外的属性包括在内,但Array的length属性却不包括在内。

  • for … of循环则完全修复了这些问题,它只循环集合本身的元素

3、forEach()方法,它接收一个函数,每次迭代就自动回调该函数。函数调用不要求参数必须一致.

// 数组
var a = ['A', 'B', 'C'];
a.forEach(function(element,index,array)){
          console.log(elemet + ', index=' + index)
          });
// A, index = 0
// B, index = 1
// C, index = 2

//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);
});

十、注意的点

1、运算符 == 和 ===

== : 表达式的值和类型满足一个即可

===:表达式的值和类型需要同时满足。一般switch中用恒等运算符

2、加法和连接都用+

var x = 10;
var y = 5;
var z = x + y;           // z 的结果为 15

var x = 10;
var y = "5";
var z = x + y;           // z 的结果为 "105"

3、浮点型数据使用注意事项

var x = 0.1;
var y = 0.2;
// var z = x + y  // z 的结果为 0.30000000000000004
var z = (x * 10 + y * 10) / 10;       // z 的结果为 0.3

4、字符串换行

var x = "Hello \
World";

5、Undefined 不是 Null

在 JavaScript 中, null 用于对象, undefined 用于变量,属性和方法。对象只有被定义才有可能为 null,否则为 undefined。

6、href="#"与href="javascript:void(0)"的区别

# 包含了一个位置信息,默认的锚是**#top** 也就是网页的上端。也可以使用这个去定位页面中的位置。当用户链接时,void(0) 计算为 0,但 Javascript 上没有任何效果。

<a href="javascript:void(0);">这是一个死链接</a>
<a href="#pos">可以到达指定的位置</a>