JavaScript 廖
===============
JavaScript代码可以直接嵌在网页的任何地方,不过通常我们都把JavaScript代码放到<head>
中
======注释
// 以双斜杠开头直到行末的是注释,注释是给人看的,会被浏览器忽略 /* 在这中间的也是注释,将被浏览器忽略 */
===============
每个语句以;
结束,语句块用{...},JavaScript严格区分大小写 ;
申明一个变量用var
语句,注意只能用var
申明一次;如果一个变量没有通过var
申明就被使用,那么该变量就自动被申明为全局变量:
在同一个页面的不同的JavaScript文件中,如果都不用var
申明,恰好都使用了变量i
,将造成变量i
互相影响,产生难以调试的错误结果。所以建议加上var
======数据类型=====JavaScript 是动态语言,定义变量时,无需指定数据类型
number:不区分整数和浮点数,统一用Number表示
字符串:字符串是以单引号'或双引号"括起来的任意文本
由于JavaScript这个设计缺陷,不要使用==
比较,始终坚持使用===
比较
唯一能判断NaN
的方法是通过isNaN()
函数null
表示一个“空”的值,它和0
以及空字符串''
不同,0
是一个数值,''
表示长度为0的字符串,而null
表示“空”。
JavaScript的数组可以包括任意数据类型
数组用[]
表示,另一种创建数组的方法是通过Array()
函数实现new Array(1, 2, 3); // 创建了数组[1, 2, 3]
JavaScript对象的键都是字符串类型,值可以是任意数据类型。要获取一个对象的属性,我们用对象变量.属性名
的方式person.name; // 'Bob'
==================
如果字符串内部既包含'
又包含"
怎么办?可以用转义字符\
来标识,比如:'I\'m \"OK\"!';
======================
由于多行字符串用\n
写起来比较费事,所以最新的ES6标准新增了一种多行字符串的表示方法,用反引号表示:
`这是一个
多行
字符串`;
注意:反引号在键盘的ESC下方,数字键1的左边
=================
要把多个字符串连接起来,可以用+
号连接
var name = '小明'; var age = 20; var message = '你好, ' + name + ', 你今年' + age + '岁了!'; ===== var message = `你好, ${name}, 你今年${age}岁了!`;
==========
需要特别注意的是,字符串是不可变的,如果对字符串的某个索引赋值,不会有任何错误,但是,也没有任何效果:
================
请注意,直接给Array
的length
赋一个新的值会导致Array
大小的变化:
var 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]
============
如果不给slice()
传递任何参数,它就会从头到尾截取所有元素。利用这一点,我们可以很容易地复制一个Array
splice()
方法是修改Array
的“万能方法”,它可以从指定的索引开始删除若干元素,然后再从该位置添加若干元素:
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'] // 只删除,不添加: arr.splice(2, 2); // ['Google', 'Facebook'] arr; // ['Microsoft', 'Apple', 'Oracle'] // 只添加,不删除: arr.splice(2, 0, 'Google', 'Facebook'); // 返回[],因为没有删除任何元素 arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
push()
向Array
的末尾添加若干元素,pop()
则把Array
的最后一个元素删除掉
如果要往Array
的头部添加若干元素,使用unshift()
方法,shift()
方法则把Array
的第一个元素删掉
多维数组:var arr = [[1, 2, 3], [400, 500, 600], '-'];
===========
如果属性名包含特殊字符,就必须用''
括起来:
var xiaohong = { name: '小红', 'middle-school': 'No.1 Middle School' };
访问这个属性也无法使用.
操作符,必须用['xxx']
来访问:
xiaohong['middle-school']; // 'No.1 Middle School' xiaohong['name']; // '小红' xiaohong.name; // '小红'
删除元素 delete xiaoming['name'];
是否拥有某一属性:
xiaoming.hasOwnProperty('name'); // true xiaoming.hasOwnProperty('toString'); // false
=================
使用if () { ... } else { ... }
来进行条件判断
var age = 3; if (age >= 18) { alert('adult'); } else if (age >= 6) { alert('teenager'); } else { alert('kid'); }
======循环=======
var x = 0; var i; for (i=1; i<=10000; i++) { x = x + i; }
var o = { name: 'Jack', age: 20, city: 'Beijing' }; for (var key in o) { console.log(key); // 'name', 'age', 'city' }
var x = 0; var n = 99; while (n > 0) { x = x + n; n = n - 2; } var n = 0; do { n = n + 1; } while (n < 100);
======={} 的key只能是字符串,但map去掉了这个限制
var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
var m = new Map(); // 空Map m.set('Adam', 67); // 添加新的key-value
================
Set是没有重复元素的数组
var s2 = new Set([1, 2, 3]); // 含1, 2, 3
s.add(4);
===========遍历set或map不能用for in,要用for of ===========
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]); }
======函数定义======
由于JavaScript允许传入任意个参数而不影响调用,因此传入的参数比定义的参数多也没有问题,传入的参数比定义的少也没有问题
function abs(x) { if (x >= 0) { return x; } else { return -x; } }
var abs = function (x) { if (x >= 0) { return x; } else { return -x; } };
========
JavaScript还有一个免费赠送的关键字arguments
,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。
利用arguments
,你可以获得调用者传入的所有参数。也就是说,即使函数不定义任何参数,还是可以拿到参数的值。
实际上arguments
最常用于判断传入参数的个数:
function foo(x) { console.log('x = ' + x); // 10 for (var i=0; i<arguments.length; i++) { console.log('arg ' + i + ' = ' + arguments[i]); // 10, 20, 30 } }
============
rest参数只能写在最后,前面用...
标识,从运行结果可知,传入的参数先绑定a
、b
,多余的参数以数组形式交给变量rest
,所以,不再需要arguments
我们就获取了全部参数。
如果传入的参数连正常定义的参数都没填满,也不要紧,rest参数会接收一个空数组(注意不是undefined
)。
==================
由于JavaScript的函数可以嵌套,此时,内部函数可以访问外部函数定义的变量,反过来则不行
JavaScript的函数在查找变量时从自身函数定义开始,从“内”向“外”查找。如果内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量
们在函数内部定义变量时,请严格遵守“在函数内部首先申明所有变量”这一规则。最常见的做法是用一个var
申明函数内部用到的所有变量:
function foo() { var x = 1, // x初始化为1 y = x + 1, // y初始化为2 z, i; // z和i为undefined // 其他语句: for (i=0; i<100; i++) { ... } }
====================
JavaScript默认有一个全局对象window
,全局作用域的变量实际上被绑定到window
的一个属性:
var course = 'Learn JavaScript'; alert(course); // 'Learn JavaScript' alert(window.course); // 'Learn JavaScript'
直接访问全局变量course
和访问window.course
是完全一样的。
============
全局变量会绑定到window
上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。
减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中
// 唯一的全局变量MYAPP: var MYAPP = {}; // 其他变量: MYAPP.name = 'myapp'; MYAPP.version = 1.0; // 其他函数: MYAPP.foo = function () { return 'foo'; };
把自己的代码全部放入唯一的名字空间MYAPP
中,会大大减少全局变量冲突的可能。
============
const
与let
都具有块级作用域,var 则没有
=============
apply
方法接收两个参数,第一个参数就是需要绑定的this
变量,第二个参数是Array
,表示函数本身的参数
对普通函数调用,我们通常把this
绑定为null
另一个与apply()
类似的方法是call()
,唯一区别是:
-
apply()
把参数打包成Array
再传入; -
call()
把参数按顺序传入。
==============
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]
var arr = [1, 3, 5, 7, 9]; arr.reduce(function (x, y) { return x + y; }); // 25
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]
================
sort()
方法会直接对Array
进行修改,它返回的结果仍是当前Array
:
var a1 = ['B', 'A', 'C']; var a2 = a1.sort(); a1; // ['A', 'B', 'C'] a2; // ['A', 'B', 'C'] a1 === a2; // true, a1和a2是同一对象
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]
===========
数组特有的方法:every()
方法可以判断数组的所有元素是否满足测试条件。
find()
方法用于查找符合条件的第一个元素,如果找到了,返回这个元素,否则,返回undefined
findIndex()
和find()
类似,也是查找符合条件的第一个元素,不同之处在于findIndex()
会返回这个元素的索引,如果没有找到,返回-1
forEach()
和map()
类似,它也把每个元素依次作用于传入的函数,但不会返回新的数组。forEach()
常用于遍历数组,因此,传入的函数不需要返回值
=================
unction count() { var arr = []; for (var i=1; i<=3; i++) { arr.push(function () { return i * i; }); } return arr; } var results = count(); var f1 = results[0]; var f2 = results[1]; var f3 = results[2];
你可能认为调用f1()
,f2()
和f3()
结果应该是1
,4
,9
,但实际结果是:
f1(); // 16
f2(); // 16
f3(); // 16
==============闭包
function* fib(max) { var t, a = 0, b = 1, n = 0; while (n < max) { yield a; [a, b] = [b, a + b]; n ++; } return; }
for (var x of fib(10)) { //遍历闭包
console.log(x); // 依次输出0, 1, 1, 2, 3, ...
}
=======================
判断null
请使用myVar === null
判断某个全局变量是否存在用typeof window.myVar === 'undefined'
函数内部判断某个变量是否存在用typeof myVar === 'undefined'
=====================
JavaScript的Date对象月份值从0开始,牢记0=1月,1=2月,2=3月,……,11=12月。
=====================
JSON的字符串规定必须用双引号""
,Object的键也必须用双引号""
在JavaScript中,我们可以直接使用JSON,因为JavaScript内置了JSON的解析。
//序列化var xiaoming = { name: '小明', age: 14, gender: true, height: 1.65, grade: null, 'middle-school': '\"W3C\" Middle School', skills: ['JavaScript', 'Java', 'Python', 'Lisp'] }; var s = JSON.stringify(xiaoming); console.log(s);
反序列化:
JSON.parse('[1,2,3,true]'); // [1, 2, 3, true]
JSON.parse('{"name":"小明","age":14}'); // Object {name: '小明', age: 14}
================新的关键字class
从ES6开始正式被引入到JavaScript中
class Student { constructor(name) { this.name = name; } hello() { alert('Hello, ' + this.name + '!'); } } ===========继承 class PrimaryStudent extends Student { constructor(name, grade) { super(name); // 记得用super调用父类的构造方法! this.grade = grade; } myGrade() { alert('I am at grade ' + this.grade); } }
===============
window
对象不但充当全局作用域,而且表示浏览器窗口。
location
对象表示当前页面的URL信息
要加载一个新页面,可以调用location.assign()
。如果要重新加载当前页面,调用location.reload()
=================
document
对象表示当前页面,始终记住DOM是一个树形结构。
document
的title
属性是从HTML文档中的<title>xxx</title>
用document
对象提供的getElementById()
和getElementsByTagName()
可以获得一组DOM节点,,以及CSS选择器document.getElementsByClassName()
<dl id="drink-menu" style="border:solid 1px #ccc;padding:6px;"> <dt>摩卡</dt> <dd>热摩卡咖啡</dd> <dt>酸奶</dt> <dd>北京老酸奶</dd> <dt>果汁</dt> <dd>鲜榨苹果汁</dd> </dl> var drinks = document.getElementsByTagName('dt'); var i, s; s = '提供的饮料有:'; for (i=0; i<drinks.length; i++) { s = s + drinks[i].innerHTML + ','; } console.log(s); 结果: 提供的饮料有:摩卡,酸奶,果汁,
=============
document
对象还有一个cookie
属性,可以获取当前页面的Cookie
服务器在设置Cookie时可以使用httpOnly
,设定了httpOnly
的Cookie将不能被JavaScript读取。这个行为由浏览器实现,主流浏览器均支持httpOnly
选项
==================
// 获取节点test下的所有直属子节点:
var cs = test.children; // 获取节点test下第一个、最后一个子节点: var first = test.firstElementChild; var last = test.lastElementChild;
=============innerHTML
属性,不但可以修改一个DOM节点的文本内容,还可以直接通过HTML片段修改DOM节点内部的子树
// 获取<p id="p-id">...</p> var p = document.getElementById('p-id'); // 设置文本为abc: p.innerHTML = 'ABC'; // <p id="p-id">ABC</p> // 设置HTML: p.innerHTML = 'ABC <span style="color:red">RED</span> XYZ'; // <p>...</p>的内部结构已修改
修改innerText
或textContent
属性,这样可以自动对字符串进行HTML编码,保证无法设置任何HTML标签
两者的区别在于读取属性时,innerText
不返回隐藏元素的文本,而textContent
返回所有文本
// 获取<p id="p-id">...</p> var p = document.getElementById('p-id'); // 设置文本: p.innerText = '<script>alert("Hi")</script>'; // HTML被自动编码,无法设置一个<script>节点: // <p id="p-id"><script>alert("Hi")</script></p>
DOM节点的style
属性对应所有的CSS,可以直接获取或设置。因为CSS允许font-size
这样的名称,但它并非JavaScript有效的属性名,所以需要在JavaScript中改写为驼峰式命名fontSize
:
// 获取<p id="p-id">...</p> var p = document.getElementById('p-id'); // 设置CSS: p.style.color = '#ff0000'; p.style.fontSize = '20px'; p.style.paddingTop = '2em';
===========
有两个办法可以插入新的节点。一个是使用appendChild
,把一个子节点添加到父节点的最后一个子节点
var
list = document.getElementById('list'),
haskell = document.createElement('p');
haskell.id = 'haskell';
haskell.innerText = 'Haskell';
list.appendChild(haskell);
===================
如果我们要把子节点插入到指定的位置怎么办?可以使用parentElement.insertBefore(newElement, referenceElement);
,子节点会插入到referenceElement
之前。
多时候,需要循环一个父节点的所有子节点,可以通过迭代children
属性实现:
var i, c, list = document.getElementById('list'); for (i = 0; i < list.children.length; i++) { c = list.children[i]; // 拿到第i个子节点 }
=======删除自己=========
要删除一个节点,首先要获得该节点本身以及它的父节点,然后,调用父节点的removeChild
把自己删掉:
// 拿到待删除节点: var self = document.getElementById('to-be-removed'); // 拿到父节点: var parent = self.parentElement; // 删除: var removed = parent.removeChild(self);
============删除子节点======
var parent = document.getElementById('parent'); parent.removeChild(parent.children[0]); parent.removeChild(parent.children[1]); // <-- 浏览器报错
原因就在于,当<p>First</p>
节点被删除后,parent.children
的节点数量已经从2变为了1,索引[1]
已经不存在了。
因此,删除多个节点时,要注意children
属性时刻都在变化。
=========================