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的默认对象表示方式{}可以视为其他语言中的
Map
或Dictionary
的数据结构,即一组键值对。
但是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
类型,Array
、Map
和Set
都属于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;
- 对象
从一个对象中取出若干属性,按属性名取,如果对应的属性不存在,返回undefinedvar 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 ) }