90年代早期,浏览器只能进行文字图片的阅读,不具备交互功能。95年,火狐前身-即网景公司内部的工程师(布兰登里奇)花了10天时间设计出了JavaScript去实现动态页面,原本叫livescipt。当时是因为蹭java的热度,同时JavaScript研发方之一也是java的开发商sun。网景公司因JavaScript迅速打入市场。IE不甘寂寞,设计出了功能类似的网页编程语言Jscript来应对JavaScript,即后来的typescript。
90年代后期,网景公司为了寻求更好的发展,把JavaScript1.1版本草案提交给了欧洲计算机制造协会(ECMA)来指导语言规范及标准,专门设立了ECMA-262来对应JavaScript的技术领域,并成立TC39技术委员会来管理及制作JavaScript的标准。
到了09年,ECMA发布了ECMAScript 第5版,规范了JavaScript的整个体系,由此打开了前端市场。
15年发布了ECMAScript 2015,更新了很多新特性,因为新版本是ECMAScript 第5版之后,所以又称为ECMAscript 第6版,简称ES6。
15年之后每年中旬发布一个新版本。
目前主流版本是ES5及ES6,未来主流是ES6。未来发展方向是vr 、万物互联、界面交互、前端应用一体化。
浏览器环境
JavaScript代码会作为页面的一部分代码,一个网页可以包含3种语言的代码:HTML(负责页面的内容结构)、css(负责美化页面)、JavaScript:(负责页面的所有交互及功能)。
Nodejs环境
能够单独运行js代码的服务器环境。
在HTML页面中新增<script> </script>
标签,在标签中间书写js代码。一个页面可以有多个<script> </script>
标签,理论上,<script> </script>
标签可以放在任何一个地方,处于性能以及执行方面,建议js代码放在</body>
之前。
<script src="js文件的地址"></script>
支持相对及绝对路径。JavaScript代码可以放在以.js
为后缀的文件,可以通过<script> </script>
标签引入HTML页面中执行。JavaScript代码的执行顺序跟引入(先后)顺序是一致的。
document.write
:要输出的内容需要被一对引号所包围,引号可以是单引号或双引号,无区别。一般情况下,需要再每句代码后加一个;
表示代码结束。如果输出的是HTML格式的代码,那么会直接进行解析,变成一个真正的HTML标签显示在页面上。
document.write("要输出的内容"); document.write('<a href="http://www.baidu.com">百度一下</a>'); //页面上会显示一个超链接
console.log
:内容会在开发者工具(f12)中的控制台console
中进行输出。
console.log("要输出的内容");
alert
:内容会以弹窗的形式输出,如果页面上同时也有document.write
,那么Chrome会在弹窗消失后才显示出来,FireFox是都可以看到,没有影响。输出内容时不加引号,JavaScript会把输出的内容当成代码来执行。一对双引号中,可以继续使用单引号.也可以单引号里使用双引号。
alert("弹窗要输出的内容"); document.write(/* 不加引号会作为代码来执行 */);
JavaScript提供了prompt
可以获取用户通过键盘输入的内容。页面上会出来一个输入框,方便用户进行输入。prompt
获取的是一个字符串。
prompt("提示性文字");
单行注释
// 注释内容
多行注释
/* 注释内容 */
文档注释
文档注释是一个特殊的多行注释,是对整个js代码、或者一个js函数进行说明
/** 注释内容 */ 例子: /** * 该文件主要用于定义常用变量 */ var name = "zs"; /** * 传入两个数字,用于进行加法计算 * @param {Number} num1 * @param {Number} num2 * @returns */ function add(num1, num2) { return num1 + num2; }
JavaScript 中的变量是无类形的,值才有类型。在JavaScript 中有三种声明变量方式:
const
:用于声明一个或多个常量,声明时必须进行初始化,且初始化后值不可再修改var
:var定义的变量可以修改,声明但不赋值的变量的值是undefined
,不会报错。let
:相较于var,let声明的变量仅作用于块作用域。变量名有以下要求:
可以参见知乎各位大神的讨论:JavaScript 语句后应该加分号么? - 知乎 ,实际上大多凭借个人喜好,我是加分号党。
Number
:数字
isNaN()
:判定是否为非数字,NaN即not a number。
isNaN(/* 待检测的数据 */);
String
:字符串,被引号所包围。
字符串可使用+
、-
拼接
"123"+"456" //123456 "123" -"100" //23 "123" - 50 //73 "123" -"abc" //NaN "123"- "a123" //NaN
+
、-
拼接繁琐时,可使用ES6新增的模板字符串
/* 字符串用一对返单引号来表示 */ var str = `es6的字符串`; /* 用${}在字符串中嵌入变量或表达式 */ var username = "张三"; var str1 = `我的名字是${username}`; var str2 = `1+1的结果是${1+1}`; document.write(str1); //输出 我的名字是张三 document.write(str2); //输出 1+1的结果是2
Boolean
:布尔类型,取值 true 或 false。
Object
:对象,表示一个复合数据。
undefined
:一个特殊数据,表示未定义。一般是指定义了变量,但没有数据的时候,变量就是undefined,也可以理解为变量的默认值。
null
:一个特殊值,表示对象为空。
Symbol
:ES6新增数据类型,表示唯一值。
typeof
:可检测数据类型,返回值为字符串。
typeof /* 待检测的数据 */
Number()
:字符串转数字parseInt()
:字符串取整转为数字(直接舍去)parseFloat()
:类似于Number()
数字.toString()
:数字转字符串数字.toFixed(小数位)
:数字转字符串并保留指定的小数位(四舍五入)和其他语言差不多
+
:加法-
:减法*
:乘法/
:除法%
:取余=
:赋值+=
、-=
、*=
、/=
、%=
:赋值并运算++
、--
:自增自减>
:大于<
:小于==
:等于只比较数值,不考虑数据类型!=
:不等于>=
:大于或等于<=
:小于或等于===
:完全等于,会比较数值以及数据类型&&
:与||
:或!
:取反和其它语言一样
if(/*条件1*/){ /*条件1为True时要执行的代码*/ }else if(/*条件2*/){ /*条件1为false、条件2为true时要执行的代码*/ }else if(/*条件n*/){ /*之前的条件不满的,条件n为true时要执行的代码*/ }else{ /*所有条件都为false时要执行的代码*/ }
和其他语言一样
switch(/*条件*/){ case /*值1*/: /*当条件为值1时要执行的代码*/;break; case /*值2*/: /*当条件为值2时要执行的代码*/;break; case /*值3*/: /*当条件为值3时要执行的代码*/;break; case /*值n*/: /*当条件为值n时要执行的代码*/;break; default: /*当所有分支都不满足时要执行的代码*/;break; }
和其它语言一样
for(/*当前循环次数*/; /*循环满足的条件*/; /*循环次数自增*/){ /*每次循环要执行的代码*/ }
和其它语言一样
while(/*循环继续条件*/){ /*每次循环要执行的代码*/ }
和其它语言一样
do{ /*要循环的代码*/ }while(/*循环继续条件*/);
定义数组
var 变量名 = [];// 定义了一个空的数组并把数组保存到一个变量中 var 变量名 = new Array();//定义一个新的数组对象并保存到变量中 var 变量名 =[/*数据1*/, /*数据2*/, /*数据3*/, /*...*/, /*数据n*/]; var 变量名 = new Array(/*数据1*/, /*数据2*/, /*数据3*/, /*...*/, /*数据n*/);
.push()
:数组末尾添加数据.pop()
:删除数组末尾数据,并返回被删除的数据.unshift()
:数组开头添加一个多个数据.splice(插入/删除位置, 删除数量, 插入数据1, 插入数据2, 插入数据n)
:数组指定位置进行插入或删除数据.join()
:能够将数组里的数据拼接转为字符串,可添加分隔符.reverse()
:数组倒序存放.concat ()
:数组合并.slice()
:根据下标获取数组中的部分数据.indexOf()
:获取某数据在数组中的第一次出现的下标,没有的话返回-1.sort()
:数组排序,可指定排序函数.lastIndexOf()
:获取某数据在数组中的最后一次出现的下标,没有的话返回-1定义函数
function 函数名(){ //包含的一段代码 }
调用、参数、返回值和其他语言一样,不再赘述。
arguments
是JavaScript针对每个函数内部的一个类数组对象。会在函数调用时保存所有的实参。arguments
只能是在函数体里使用。
所谓类数组指 的是可以进行遍历操作,或通过下标来使用arguments
里的数据,但是不能进行添加、修改等操作。实参在arguments
里的顺序跟在函数调用时的书写顺序一致。
如果函数实参数量不固定,那么可以使用arguments
及遍历来获取每一个实际参数并进行处理。
ES6所提供的新的定义函数的语法,合理利用可使代码更加简洁
//语法 var 函数名 = (形式参数)=>{ //函数体 } //旧的function语法 function 函数名(形式参数){ //函数体 }
{}
()
可以省略不写//例子: function add(a){ return a+5; } add(1,2); //箭头函数优化 var ArrowAdd = a=>a+b;
JavaScript专属的一种用于表示复合数据的数据结构,类似结构体。
//定义一个空的原生对象 var 对象变量名 = {}; //定义一个带有默认数据的原生对象 var 对象变量名 = { 属性名1: 属性值1, 属性名2: 属性值2, 属性名n: 属性值n }; //获取 对象名.属性名 //设置 对象名.属性名= 新数据; //原生对象可作为数组元素 var arr =[对象1,对象2,对象n];
解构赋值为ES6新增,是对赋值运算符的扩展,就是赋值符左右两边结构保持一致,将右边的数据赋值给左边的变量。
数组的解构是根据数据在数组中的位置来进行赋值:
var [a, b, c] = [100, 200, 300]; console.log(a); // 100 console.log(b); // 200 console.log(c); // 300
对象的解构是根据数据的属性名是否相同来进行赋值:
var { a: num1, b: num2, c: num3 } = {a: 100, b: 200, c: 300}; console.log(num1); // 100 console.log(num2); // 200 console.log(num3); // 300
ES6 中规定,对象的属性名和属性值为同一个单词时,对象的冒号和属性值可以省略:
var { a, b, c } = {a: 100, b: 200, c: 300}; //等同于 var { a: a, b: b, c: c } = {a: 100, b: 200, c: 300}; console.log(a); // 100 console.log(b); // 200 console.log(c); // 300
以上解构赋值的规则同样适用于函数的实参和形参之间:
// 数组 function foo([a, b, c]) { console.log(a, b, c); } foo([100, 200, 300]); // 对象 function bar({ a, b, c }) { console.log(a, b, c); } bar({ a: 100, b: 200, c: 300 })
当我们在对数组或对象进行解构时,我们可以给变量一个默认值:
const [a = 100, b = 200] = [1]; console.log(a, b); // 1 200
以上代码中,我们分别给变量 a 和变量 b 设置了一个默认值:100 和 200,也就意味着,当右侧的数组中没有对应的数据时,变量 a 和变量 b 就会使用默认值,如果右侧的数组中有对应的数据,变量 a 和变量 b 就会使用对应的数据。
函数的参数中,解构赋值同样可以设置默认值:
function foo({ a = 100, b = 200 } = {}) { console.log(a, b); } foo();
以上代码中,{ a = 100, b = 200 } = {}
中的 {}
表示当函数没有传参时,默认值为 {}
;{ a = 100, b = 200 }
表示对传递的参数(或参数的默认值 {}
)进行解构。
ES6 中新增了两种对象简写的语法。
当对象的属性名和属性值为同一个单词时,冒号和属性名可以省略。
var name = "zhangsan"; var student = { name: name } // 简写 var student = { name }
对象的方法在简写时,可以将冒号和 function 关键字省略。
var student = { sayHello: function() { console.log('hello'); } } // 简写 var student = { sayHello() { console.log('hello'); } }
相当于拷贝,扩展运算符的符号:...
扩展运算符可以扩展的数据类型有:
const arr1 = [1, 2, 3]; const obj1 = { a: 1, b: 2 }; const arr2 = [...arr1]; const obj2 = { ...obj1 };
const arr1 = [1, 2, 3]; const obj1 = { a: 1, b: 2 }; const arr2 = [0, ...arr1, 4, 5, 6]; const obj2 = { ...obj1, c: 3, d: 4, f: 5 };
const arr1 = [1, 2, 3]; const arr2 = [4, 5, 6]; const arr3 = [...arr1, ...arr2]; const obj1 = { a: 1, b: 2 }; const obj2 = { name: 'zhangsan', age: 20 }; const obj3 = { ...obj1, ...obj2 };
ajax技术使JavaScript可以向服务器发送数据的请求,并接受服务器返回的数据。
ajax({ url: '服务器的网络地址,即获取数据的地址', success(data) { //当接受到服务器数据时自动触发该函数 //返回的数据存在data中 } })
为了避免复杂性,从一诞生,js就是单线程,这已经成了这门语言的核心特征,目前来看,这个特征将不会改变。我们常常用子线程来完成一些可能消耗时间足够长以至于被用户察觉的事情,比如读取一个大文件或者发出一个网络请求。因为子线程独立于主线程,所以即使出现阻塞也不会影响主线程的运行。但是子线程有一个局限:一旦发射了以后就会与主线程失去同步,我们无法确定它的结束,如果结束之后需要处理一些事情,比如处理来自服务器的信息,我们是无法将它合并到主线程中去的。
为了解决这个问题,JavaScript 中的异步操作函数往往通过回调函数来实现异步任务的结果处理。
回调函数就是一个函数,它是在我们启动一个异步任务的时候就告诉它:等你完成了这个任务之后要干什么。这样一来主线程几乎不用关心异步任务的状态了,他自己会善始善终。例如:
function print() { document.getElementById("demo").innerHTML="RUNOOB!"; } setTimeout(print, 3000);
这段程序中的 setTimeout 就是一个消耗时间较长(3 秒)的过程,它的第一个参数是个回调函数,第二个参数是毫秒数,这个函数执行之后会产生一个子线程,子线程会等待 3 秒,然后执行回调函数 "print",在命令行输出 "RUNOOB!"。
当然,JavaScript 语法十分友好,我们不必单独定义一个函数 print ,我们常常将上面的程序写成:
setTimeout(function () { document.getElementById("demo").innerHTML="RUNOOB!"; }, 3000);
js的这种回调方式,对于处理异步的串行操作是很不优雅的,串行的异步越多,调用嵌套的越深,这种情况,我们称之为:回调地狱,例如:
ajax({ url: '获取学生信息', data: { name: '张三' }, success(data) { console.log(data.className); // 班级名称 ajax({ url: '获取班级信息', data: { className: className }, success(data) { console.log(data); ajax({ url: '获取教师信息', data: { className: className }, success(data) { console.log(data); } }) } }) } })
Promise是异步编程的一种解决方案,可以帮助我们摆脱回调地狱的问题,Promise最早是由技术社区里面的一些散户提出并实现,后来官方觉得这个东西很不错,就把它纳入es6的标准中。
new Promise((resolve, reject) => { // 异步代码 if(/* 异步请求成功*/) { resolve(data); } else { reject(data); } });
例子:
new Promise((resolve, reject) => { ajax({ url:'获取学生信息', data:'获取学生信息', url: '获取学生信息', data: { name: '张三' }, success(data) { resolve(data.className) } }) })
Promise可以认为是一个状态管理器,它有三种状态:
pending
:等待中(进行中),表示 Promise 中的异步处理还没有得到结果。fulfilled
:已成功,表示已经得到正确的结果,可以继续往后执行。rejected
:已失败,表示也得到了结果,但是并不是我们需要的。Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
,它们是两个函数,由js引擎内部提供,不用自己部署。
每一个 Promise 实例对象身上都有一个 then 方法, 当 resolve 被调用时,会触发 then 方法的参数中的第一个函数,该函数还可以设置一个形参来接收 resolve 传递的数据:
const p = new Promise((resolve, reject) => { resolve('web08'); }); p.then((data) => { console.log(data); // web08 })
如果在 then 方法参数中的第一个函数内部,继续返回一个 promise 对象,该对象会作为当前 then 方法的返回值:
const p2 = p.then((data) => { return new Promise((resolve, reject) => { // 异步代码 }) })
上例中,then 方法中返回的 Promise 对象,直接赋值给了变量 p2。
async / await 是 ES7 中新增的异步解决方案,号称“异步的终极解决方案”。
async 用来定义一个异步函数,所有异步函数的返回值,都是一个 Promise 对象。
async function foo() { } foo();
await 用于等待一个异步处理的结果,通常用来等待一个 Promise 对象。await 只能在 async 异步函数中使用。
async function foo() { const data = await new Promise((resolve) => { resolve('hello') }); console.log(data); // data } foo();
charAt
:返回字符串中指定下标的字符。
var str = "hello woniuxy"; //获取 w str.charAt(6); //w
charCodeAt
:返回字符串中指定下标字符的ASCII码。
var str = "hello woniuxy"; //获取 w的ASCII码 str.charCodeAt(6); //119
concat
:字符串拼接。
var str = "hello "; var str1 = "woniuxy"; var newStr = str.concat(str1); console.log(newStr); //"hello woniuxy"
indexOf
:返回某字符或某字符串在指定字符串中首次出现位置的下标,没有则返回-1。
字符串.indexOf(要查找的字符, 在字符串中查找的开始下标); var str = "hello woniuxy"; console.log(str.indexOf("ll")); // 2 console.log(str.indexOf("lln"));// -1
lastIndexOf
:返回某字符或某字符串在指定字符串中最后一次出现位置的下标,没有则返回-1。
var str = "hello woniuxy"; console.log(str.lastIndexOf("l"));// 3 console.log(str.lastIndexOf("lln"));// -1
replace
:将字符串中某个子字符串替换成另一个字符串,返回替换后的新字符串,对原字符串没有影响。如果要全局替换,那么使用replaceAll
。
var str = "hello woniuxy"; var newStr1 = str.replace('l', 'n');//将字符l替换成n var newStr2 = str.replaceAll('l', 'n');//将所有的字符l替换成n console.log(newStr1,newStr2);
slice
:获取字符串中的一部分,即截取字符串。左闭右开,返回的是新字符串。
字符串.slice(开始下标,结束下标); var str = "hello woniuxy"; var newStr = str.slice(1,5); //ello
split
:按照指定的符号,对字符串进行分割。将分割后的字符串全部放入到一个新数组中并返回该新数组。没有默认分隔符,如果不给那么直接返回一个数据的数组,该数据就是完整的字符串。
字符串.split("分隔符"); //例子1: var str = "hello woniuxy"; var newArr = str.split(" ");//['hello','woniuxy'];
toLowerCase
:获取指定字符串的全小写格式的字符串并返回,原字符串不影响。
var str = "Hello Woniuxy"; var newStr = str.toLowerCase(); console.log(newStr); // "hello woniuxy"
toUpperCase
:获取指定字符串的全大写格式的字符串并返回,原字符串不影响。
var str = "Hello Woniuxy"; var newStr = str.toUpperCase(); console.log(newStr); // "HELLO WONIUXY"
startsWith
:判断字符串是否以指定的字符串开头,返回布尔型数据。
var str = "11-字符串.mp4"; console.log(str.startsWith("11-")); //true console.log(str.startsWith("1-")); //false
endsWith
:判断字符串是否以指定的字符串结束,返回布尔型数据。
var str = "11-字符串.mp4"; console.log(str.endsWith(".mp4")); //true console.log(str.endsWith(".mp")); //false
trim
:去除字符串两端的空格,返回处理后的新字符串,不影响原字符串。
var str = " hello woniuxy "; console.log(str.trim()); //"hello woniuxy"
match
:用于判断某个字符串是否符合某个正则表达式,返回正则表达式匹配的数据,是一个对象,但是可以通过下标的方式去获取数据。
字符串.match(正则表达式); //例子:找到字符串中手机号并返回 var str = "fjdkljkldjlk18325678901jvkllk"; var reg = /1[3-9]\d{9}/; var phone = str.match(reg); console.log(phone[0]); //18325678901