JavaScript面向对象 1、面向对象
面向对象的特性:
封装性、继承性、多态性
2、ES6中的类和对象 面向对象的思维特点:
抽取(抽象)对象共用的属性和行为组织(封装)成一个类(模板)
对类进行实例化, 获取类的对象
面向对象编程我们考虑的是有哪些对象,按照面向对象的思维特点,不断的创建对象,使用对象,指挥对象做事情
1、对象 在 JavaScript 中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,例如字符串、数值、数组、函数等。
对象是由属性和方法组成的
属性:事物的特征,在对象中用属性 来表示
方法:事物的行为,在对象中用方法 来表示
2、类 在 ES6 中新增加了类的概念,可以使用 class 关键字声明一个类,之后以这个类来实例化对象。
类抽象了对象的公共部分,它泛指某一大类(class)
对象特指某一个,通过类实例化一个具体的对象
1、创建类 1 2 3 class name { // class body }
实例
注意:类必须使用new 实例化对象
2、构造函数 constructor() 方法是类的构造函数(默认方法),用于传递参数,返回实例对象 ,通过 new 命令生成对象实例时,自动调用该方法。如果没有显示定义, 类内部会自动给我们创建一个constructor()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <script > class Star { constructor (uname, age ) { this .uname = uname; this .age = age; } } var ldh = new Star ('刘德华' , 18 ); var zxy = new Star ('张学友' , 20 ); console .log (ldh); console .log (zxy); </script >
通过 class 关键字创建类,类名我们还是习惯性定义首字母大写
类里面有个 constructor函数,可以接收传递过来的参数,同时返回实例对象
constructor函数只要 new 生成实例时,就会自动调用这个函数,如果我们不写这个函数,类也会自动生成这个函数
最后注意语法规范
创建类——类名后面不要加小括号
生成实例——类名后面加小括号
构造函数不需要加 function 关键字
3、类添加方法 1 2 3 4 5 6 7 8 9 10 11 12 class Person { constructor (name,age ) { this .name = name; this .age = age; } say ( ) { console .log (this .name + '你好' ); } } var ldh = new Person ('刘德华' , 18 ); ldh.say ()
注意 : 方法之间不需要加逗号分隔,同时方法不需要添加 function 关键字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <script> class Star { constructor (uname, age ) { this .uname = uname; this .age = age; } sing (song ) { console .log (this .uname + song); } } var ldh = new Star ('刘德华' , 18 ); var zxy = new Star ('张学友' , 20 ); console .log (ldh); console .log (zxy); ldh.sing ('冰雨' ); zxy.sing ('李香兰' ); </script>
类的共有属性放到constructor 里面
类里面的函数都不需要写 function 关键字
3、继承
子类可以继承父类的一些属性和方法。
1 2 3 4 5 6 7 8 class Father { }class Son extends Father { }
实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <script > class Father { constructor (x, y ) { this .x = x; this .y = y; } sum ( ) { console .log (this .x + this .y ); } } class Son extends Father { constructor (x, y ) { super (x, y); this .x = x; this .y = y; } subtract ( ) { console .log (this .x - this .y ); } } var son = new Son (5 , 3 ); son.subtract (); son.sum (); </script >
4、super关键字
super 关键字用于访问和调用对象父类上的函数,可以调用父类的构造函数,也可以调用父类的普通函数
1、调用父类的构造函数 1 2 3 4 5 6 7 8 9 10 11 12 13 class Person { constructor (surname ){ this .surname = surname; } }class Student entends Person { constructor (surname,firstname ) { super (surname); this .firstname = firstname; } }
注意:子类在构造函数中使用super,必须放到this前面(必须先调用父类的构造方法,在使用子类构造方法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Father { constructor (surname ){ this .surname = surname; } saySurname ( ) { console .log ('我的姓是' + this .surname ); } }class Son entends Father { constructor (surname,firstname ) { super (surname); this .firstname = firstname; } sayFirstname ( ) { console .log ('我的名字是:' + this .firstname ); } }var damao = new Son ('刘' ,'德华' ); damao.saySurname (); damao.sayFirstname ();
2、调用父类的普通函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Father { say ( ) { return '我是爸爸' ; } }class Son extends Father { say ( ){ return super .say () + '的儿子' ; } }var damao = new Son ();console .log (damao.say ());
多个方法之间不需要添加逗号分隔
继承中属性和方法的查找原则:就近原则,先看子类,再看父类
5、注意点
在ES6中类没有变量提升,所以必须先定义类,才能通过类实例化对象
类里面的共有属性和方法一定要加 this使用
类里面的this指向:
constructor 里面的 this指向实例对象
方法里面的this指向这个方法的调用者
也就说,谁调用这个方法,this就指向谁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <body > <button > 点击</button > <script > var that; var _that; class Star { constructor (uname, age ) { that = this ; this .uname = uname; this .age = age; this .btn = document .querySelector ('button' ); this .btn .onclick = this .sing ; } sing ( ) { console .log (that.uname ); } dance ( ) { _that = this ; console .log (this ); } } var ldh = new Star ('刘德华' ); console .log (that === ldh); ldh.dance (); console .log (_that === ldh); </script > </body >
3、构造函数和原型 1、概述 在典型的 OOP 的语言中(如 Java),都存在类的概念,类就是对象的模板,对象就是类的实例,但在 ES6之前, JS 中并没用引入类的概念。
ES6, 全称 ECMAScript 6.0 ,2015.06 发版。但是目前浏览器的 JavaScript 是 ES5 版本,大多数高版本的浏览器也支持 ES6,不过只实现了 ES6 的部分特性和功能。
在 ES6之前 ,对象不是基于类创建的,而是用一种称为构建函数的特殊函数来定义对象和它们的特征。
创建对象有三种方式
对象字面量
new Object()
自定义构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var obj1 = new Object ();var obj2 = {};function Star (uname,age ) { this .uname = uname; this .age = age; this .sing = function ( ) { console .log ('我会唱歌' ); } }var ldh = new Star ('刘德华' ,18 );
注意:
构造函数用于创建某一类对象,其首字母要大写
构造函数要和new一起使用才有意义
2、构造函数
构造函数是一种特殊的函数,主要用来初始化对象(为对象成员变量赋初始值),它总与new一起使用
我们可以把对象中的一些公共的属性和方法抽取出来,然后封装到这个函数里面
new 在执行时会做四件事
在内存中创建一个新的空对象。
让 this 指向这个新的对象。
执行构造函数里面的代码,给这个新对象添加属性和方法。
返回这个新对象(所以构造函数里面不需要 return )。
1、静态成员和实例成员 JavaScript 的构造函数中可以添加一些成员,可以在构造函数本身上添加,也可以在构造函数内部的this上添加。通过这两种方式添加的成员,就分别称为静态成员和实例成员 。
静态成员: 在构造函数本身上添加的成员为静态成员,只能由构造函数本身来访问
实例成员: 在构造函数内部创建的对象成员称为实例成员,只能由实例化的对象来访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function Star (uname,age ) { this .uname = uname; this .age = age; this .sing = function ( ) { console .log ('我会唱歌' ); } }var ldh = new Star ('刘德华' ,18 ); ldh.sing ();Star .uname ; Star .sex = '男' ;Star .sex ; ldh.sex ;
2、构造函数的问题 构造函数方法很好用,但是存在浪费内存的问题。
我们希望所有的对象使用同一个函数,这样就比较节省内存
3、构造函数原型 prototype
构造函数通过原型分配的函数是所有对象所共享 的,这样就解决了内存浪费问题
JavaScript 规定,每一个构造函数都有一个prototype属性,指向另一个对象,注意这个prototype就是一个对象 ,这个对象的所有属性和方法,都会被构造函数所拥有
我们可以把那些不变的方法 ,直接定义在prototype 对象上,这样所有对象的实例就可以共享这些方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <body > <script > function Star (uname, age ) { this .uname = uname; this .age = age; } Star .prototype .sing = function ( ) { console .log ('我会唱歌' ); } var ldh = new Star ('刘德华' , 18 ); var zxy = new Star ('张学友' , 19 ); console .log (ldh.sing === zxy.sing ); ldh.sing (); zxy.sing (); </script > </body >
一般情况下,我们的公共属性定义到构造函数里面, 公共的方法我们放到原型对象身上
原型是什么?
一个对象,我们也称为 prototype 为原型对象
原型的作用是什么?
4、对象原型 __ proto __
对象都会有一个属性 _proto_ 指向构造函数的prototype原型对象,之所以我们对象可以使用构造函数prototype 原型对象的属性和方法,就是因为对象有_proto_原型的存在。
_proto_对象原型和原型对象 prototype 是等价 的
_proto_对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线 ,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype
Star.prototype 和 ldh._proto_ 指向相同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <body > <script > function Star (uname, age ) { this .uname = uname; this .age = age; } Star .prototype .sing = function ( ) { console .log ('我会唱歌' ); } var ldh = new Star ('刘德华' , 18 ); var zxy = new Star ('张学友' , 19 ); ldh.sing (); console .log (ldh); console .log (ldh.__proto__ === Star .prototype ); </script > </body >
5、constructor 构造函数
对象原型(__ proto __) 和构造函数(prototype)原型对象 里面都有一个属性 constructor 属性, constructor 我们称为构造函数,因为它指回构造函数本身。
constructor主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数
一般情况下,对象的方法都在构造函数(prototype)的原型对象中设置
如果有多个对象的方法,我们可以给原型对象prototype采取对象形式赋值,但是这样会覆盖构造函数原型对象原来的内容,这样修改后的原型对象constructor就不再指向当前构造函数了。此时,我们可以在修改后的原型对象中,添加一个constructor指向原来的构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <body > <script > function Star (uname, age ) { this .uname = uname; this .age = age; } Star .prototype = { constructor : Star , sing : function ( ) { console .log ('我会唱歌' ); }, movie : function ( ) { console .log ('我会演电影' ); } } var ldh = new Star ('刘德华' , 18 ); var zxy = new Star ('张学友' , 19 ); </script > </body >
6、三者关系
7、原型链查找规则
当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性
如果没有就查找它的原型(也就是_proto_指向的prototype原型对象)
如果还没有就查找原型对象的原型(Object的原型对象)
依次类推一直找到Object为止(null)
__ proto __对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <body > <script > function Star (uname, age ) { this .uname = uname; this .age = age; } Star .prototype .sing = function ( ) { console .log ('我会唱歌' ); } var ldh = new Star ('刘德华' , 18 ); console .log (Star .prototype ); console .log (Star .prototype .__proto__ === Object .prototype ); console .log (Object .prototype .__proto__ ); </script > </body >
8、原型对象this指向
构造函数中的 this指向我们的实例对象
原型对象里面放的是方法,这个方法里面的this指向的是这个方法的调用者,也就是这个实例对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <body > <script > function Star (uname, age ) { this .uname = uname; this .age = age; } var that; Star .prototype .sing = function ( ) { console .log ('我会唱歌' ); that = this ; } var ldh = new Star ('刘德华' , 18 ); ldh.sing (); console .log (that === ldh); </script > </body >
9、扩展内置对象
可以通过原型对象,对原来的内置对象进行扩展自定义的方法
比如给数组增加自定义求偶数和的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <body > <script > Array .prototype .sum = function ( ) { var sum = 0 ; for (var i = 0 ; i < this .length ; i++) { sum += this [i]; } return sum; }; var arr = [1 , 2 , 3 ]; console .log (arr.sum ()); console .log (Array .prototype ); var arr1 = new Array (11 , 22 , 33 ); console .log (arr1.sum ()); </script > </body >
注意:
数组和字符串内置对象不能给原型对象覆盖操作Array.prototype = {},只能是Array.prototype.xxx = function(){}的方式
4、继承 ES6 之前并没有给我们提供extends继承
我们可以通过构造函数+原型对象模拟实现继承,被称为组合继承
1、call()
调用这个函数,并且修改函数运行时的 this 指向
1 fun.call(thisArg,arg1,arg2,... ... )
thisArg:当前调用函数 this 的指向对象
arg1,arg2: 传递的其他参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <body > <script > function fn (x, y ) { console .log ('我希望我的希望有希望' ); console .log (this ); console .log (x + y); } var o = { name : 'andy' }; fn.call (o, 1 , 2 ); </script > </body >
2、借用构造函数继承父类型属性
核心原理: 通过 call() 把父类型的 this 指向子类型的 this,这样就可以实现子类型继承父类型的属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <body > <script > function Father (uname, age ) { this .uname = uname; this .age = age; } function Son (uname, age, score ) { Father .call (this , uname, age); this .score = score; } var son = new Son ('刘德华' , 18 , 100 ); console .log (son); </script > </body >
3、借用原型对象继承父类型方法
一般情况下,对象的方法都在构造函数的原型对象中设置,通过构造函数无法继承父类方法
核心原理:
将子类所共享的方法提取出来,让子类的 prototype 原型对象 = new 父类()
本质: 子类原型对象等于是实例化父类,因为父类实例化之后另外开辟空间,就不会影响原来父类原型对象
将子类的constructor重新指向子类的构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <body > <script > function Father (uname, age ) { this .uname = uname; this .age = age; } Father .prototype .money = function ( ) { console .log (100000 ); }; function Son (uname, age, score ) { Father .call (this , uname, age); this .score = score; } Son .prototype = new Father (); Son .prototype .constructor = Son ; Son .prototype .exam = function ( ) { console .log ('孩子要考试' ); } var son = new Son ('刘德华' , 18 , 100 ); console .log (son); console .log (Father .prototype ); console .log (Son .prototype .constructor ); </script > </body >
4、类的本质
class 本质还是 function
类的所有方法都定义在类的 prototype属性上
类创建的实例,里面也有_proto_指向类的prototype原型对象
所以 ES6 的类它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
所以 ES6 的类其实就是语法糖
语法糖:语法糖就是一种便捷写法,简单理解。有两种方法可以实现同样的功能,但是一种写法更加清晰、方便,那么这个方法就是语法糖
5、ES5新增方法 ES5 给我们新增了一些方法,可以很方便的操作数组或者字符串
1、数组方法
迭代(遍历)方法:foreach() ,map(),filter(),some() ,every() ;
1、forEach() 1 array .for Each(function (currentValue ,index ,arr ) )
currentValue : 数组当前项的值
index: 数组当前项的索引
arr: 数组对象本身
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <body > <script > var arr = [1 , 2 , 3 ]; var sum = 0 ; arr.forEach (function (value, index, array ) { console .log ('每个数组元素' + value); console .log ('每个数组元素的索引号' + index); console .log ('数组本身' + array); sum += value; }) console .log (sum); </script > </body >
2、filter()筛选数组 1 array .filter (function (currentValue,index,arr ))
filter()方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素,主要用于筛选数组
注意它直接返回一个新数组
1 2 3 4 5 6 7 8 9 10 11 <body > <script > var arr = [12 , 66 , 4 , 88 , 3 , 7 ]; var newArr = arr.filter (function (value, index ) { return value % 2 === 0 ; }); console .log (newArr); </script > </body >
3、some()
some()方法用于检测数组中的元素是否满足指定条件(查找数组中是否有满足条件的元素)
注意它返回的是布尔值 ,如果查找到这个元素,就返回true,如果查找不到就返回false
如果找到第一个满足条件的元素,则终止循环,不再继续查找
1 2 3 4 5 6 7 8 9 10 11 12 <body > <script > var arr1 = ['red' , 'pink' , 'blue' ]; var flag1 = arr1.some (function (value ) { return value == 'pink' ; }); console .log (flag1); </script > </body >
2、字符串方法
trim()方法会从一个字符串的两端删除空白字符,但中间的空格不会去掉
trim()方法并不影响原字符串本身,它返回的是一个新的字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <body > <input type ="text" > <button > 点击</button > <div > </div > <script > var str = ' an dy ' ; console .log (str); var str1 = str.trim (); console .log (str1); var input = document .querySelector ('input' ); var btn = document .querySelector ('button' ); var div = document .querySelector ('div' ); btn.onclick = function ( ) { var str = input.value .trim (); if (str === '' ) { alert ('请输入内容' ); } else { console .log (str); console .log (str.length ); div.innerHTML = str; } } </script > </body >
3、对象方法 1、Object.keys()
Object.keys()用于获取对象自身所有的属性
效果类似for...in
返回一个由属性名组成的数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <body > <script > var obj = { id : 1 , pname : '小米' , price : 1999 , num : 2000 }; var arr = Object .keys (obj); console .log (arr); arr.forEach (function (value ) { console .log (value); }) </script > </body >
2、Object.defineProperty()
Object.defineProperty()定义对象中新属性或修改原有的属性(了解)
1 Object . defineProperty(obj ,prop ,descriptor )
obj : 目标对象
prop : 需定义或修改的属性的名字
descriptor : 目标属性所拥有的特性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 <body > <script > var obj = { id : 1 , pname : '小米' , price : 1999 }; Object .defineProperty (obj, 'num' , { value : 1000 , enumerable : true }); console .log (obj); Object .defineProperty (obj, 'price' , { value : 9.9 }); console .log (obj); Object .defineProperty (obj, 'id' , { writable : false , }); obj.id = 2 ; console .log (obj); Object .defineProperty (obj, 'address' , { value : '中国山东蓝翔技校xx单元' , writable : false , enumerable : false , configurable : false }); console .log (obj); console .log (Object .keys (obj)); delete obj.address ; console .log (obj); delete obj.pname ; console .log (obj); Object .defineProperty (obj, 'address' , { value : '中国山东蓝翔技校xx单元' , writable : true , enumerable : true , configurable : true }); console .log (obj.address ); </script > </body >
第三个参数 descriptor 说明:以对象形式{ }书写
value:设置属性的值,默认为undefined
writeable: 值是否可以重写 true | false 默认为false
enumerable: 目标属性是否可以被枚举 true | false 默认为false
configurable: 目标属性是否可以被删除或是否可以再次修改特性 true | false 默认为false
6、函数进阶 1、函数的定义方式
函数声明方式 function 关键字(命名函数)
函数表达式(匿名函数)
new Function()
1 var fn = new Function ('参数1' ,'参数2' ,.....,'函数体' );
Function 里面参数都必须是字符串格式
第三种方式执行效率低,也不方便书写,因此较少使用
所有函数都是 Function 的实例(对象)
函数也属于对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <body > <script > function fn ( ) {}; var fun = function ( ) {}; var f = new Function ('a' , 'b' , 'console.log(a + b)' ); f (1 , 2 ); console .dir (f); console .log (f instanceof Object ); </script > </body >
2、函数的调用方式
普通函数
对象的方法
构造函数
绑定事件函数
定时器函数
立即执行函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <body > <script > function fn ( ) { console .log ('人生的巅峰' ); } var o = { sayHi : function ( ) { console .log ('人生的巅峰' ); } } o.sayHi (); function Star ( ) {}; new Star (); (function ( ) { console .log ('人生的巅峰' ); })(); </script > </body >
3、函数内this的指向
this指向,是当我们调用函数的时候确定的,调用方式的不同决定了this的指向不同,一般我们指向我们的调用者
调用方式
this指向
普通函数调用
window
构造函数调用
实例对象,原型对象里面的方法也指向实例对象
对象方法调用
该方法所属对象
事件绑定方法
绑定事件对象
定时器函数
window
立即执行函数
window
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <body > <button > 点击</button > <script > function fn ( ) { console .log ('普通函数的this' + this ); } window .fn (); var o = { sayHi : function ( ) { console .log ('对象方法的this:' + this ); } } o.sayHi (); function Star ( ) {}; Star .prototype .sing = function ( ) { } var ldh = new Star (); var btn = document .querySelector ('button' ); btn.onclick = function ( ) { console .log ('绑定时间函数的this:' + this ); }; window .setTimeout (function ( ) { console .log ('定时器的this:' + this ); }, 1000 ); (function ( ) { console .log ('立即执行函数的this' + this ); })(); </script > </body >
4、改变函数内部this指向
JavaScript 为我们专门提供了一些函数方法来帮我们处理函数内部 this 的指向问题,常用的有 bind(),call(),apply()三种方法
1、call() 方法
call()方法调用一个对象,简单理解为调用函数的方式,但是它可以改变函数的this指向
fun.call(thisArg,arg1,arg2,.....)
thisArg: 在 fun 函数运行时指定的 this 值
arg1,arg2: 传递的其他参数
返回值就是函数的返回值,因为它就是调用函数
因此当我们想改变 this 指向,同时想调用这个函数的时候,可以使用 call,比如继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <body > <script > var o = { name : 'andy' } function fn (a, b ) { console .log (this ); console .log (a + b); }; fn.call (o, 1 , 2 ); function Father (uname, age, sex ) { this .uname = uname; this .age = age; this .sex = sex; } function Son (uname, age, sex ) { Father .call (this , uname, age, sex); } var son = new Son ('刘德华' , 18 , '男' ); console .log (son); </script > </body >
2、apply()方法
apply()方法调用一个函数,简单理解为调用函数的方式,但是它可以改变函数的 this指向
1 fun .apply (thisArg,[argsArray])
thisArg: 在 fun 函数运行时指定的 this 值
argsArray : 传递的值,必须包含在数组里面
返回值就是函数的返回值,因为它就是调用函数
因此 apply 主要跟数组有关系,比如使用 Math.max() 求数组的最大值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <body > <script > var o = { name : 'andy' }; function fn (arr ) { console .log (this ); console .log (arr); }; fn.apply (o, ['pink' ]); var arr = [1 , 66 , 3 , 99 , 4 ]; var arr1 = ['red' , 'pink' ]; var max = Math .max .apply (Math , arr); var min = Math .min .apply (Math , arr); console .log (max, min); </script > </body >
3、bind()方法
bind()方法不会调用函数。但是能改变函数内部 this指向
1 fun .bind(thisArg,arg1,arg2,....)
返回由指定的 this值和初始化参数改造的 原函数拷贝
因此当我们只是想改变 this 指向 ,并且不想调用这个函数的时候,可以使用bind
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <body > <button > 点击</button > <button > 点击</button > <button > 点击</button > <script > var o = { name : 'andy' }; function fn (a, b ) { console .log (this ); console .log (a + b); }; var f = fn.bind (o, 1 , 2 ); f (); var btns = document .querySelectorAll ('button' ); for (var i = 0 ; i < btns.length ; i++) { btns[i].onclick = function ( ) { this .disabled = true ; setTimeout (function ( ) { this .disabled = false ; }.bind (this ), 2000 ); } } </script > </body >
4、总结 call apply bind 总结:
相同点:
区别点:
call和apply会调用函数,并且改变函数内部的this指向
call和apply传递的参数不一样,call 传递参数,apply 必须数组形式
bind不会调用函数,可以改变函数内部this指向
主要应用场景
call经常做继承
apply经常跟数组有关系,比如借助于数学对线实现数组最大值与最小值
bind不调用函数,但是还想改变this指向,比如改变定时器内部的this指向
7、严格模式
JavaScript 除了提供正常模式外,还提供了严格模式
ES5 的严格模式是采用具有限制性 JavaScript 变体的一种方式,即在严格的条件下运行 JS 代码
严格模式在IE10 以上版本的浏览器才会被支持,旧版本浏览器会被忽略
严格模式对正常的JavaScript语义做了一些更改:
消除了Javascript 语法的一些不合理、不严谨之处,减少了一些怪异行为
消除代码运行的一些不安全之处,保证代码运行的安全
提高编译器效率,增加运行速度
禁用了在 ECMAScript 的未来版本中可能会定义的一些语法,为未来新版本的 Javascript 做好铺垫。比如一些保留字如:class, enum, export, extends, import, super 不能做变量名
1、开启严格模式 1、为脚本开启严格模式
为整个脚本文件开启严格模式,需要在所有语句之前放一个特定语句
"use strict" 或'use strict'
1 2 3 4 <script > 'user strict' ; console .log ("这是严格模式。" ); </script >
因为"use strict"加了引号,所以老版本的浏览器会把它当作一行普通字符串而忽略。
有的 script 基本是严格模式,有的 script 脚本是正常模式,这样不利于文件合并,所以可以将整个脚本文件放在一个立即执行的匿名函数之中。这样独立创建一个作用域而不影响其他 script 脚本文件。
1 2 3 4 5 6 7 <script > (function ( ){ 'use strict' ; var num = 10 ; function fn ( ) {} })(); </script >
2、为函数开启严格模式
若要给某个函数开启严格模式,需要把"use strict"或'use strict'声明放在函数体所有语句之前
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <body > <script > 'use strict' ; </script > <script > (function ( ) { 'use strict' ; })(); </script > <script > function fn ( ) { 'use strict' ; } function fun ( ) { } </script > </body >
将"use strict" 放在函数体的第一行,则整个函数以 “严格模式”运行。
2、严格模式中的变化
严格模式对JavaScript的语法和行为,都做了一些改变
1、变量规定
在正常模式中,如果一个变量没有声明就赋值,默认是全局变量
严格模式禁止这种用法,变量都必须先用var 命令声明,然后再使用
严禁删除已经声明变量,例如,``delete x` 语法是错误的
1 2 3 4 5 6 7 8 9 10 11 12 <body > <script > 'use strict' ; var num = 10 ; console .log (num); </script > </body >
2、严格模式下this指向问题
以前在全局作用域函数中的this指向window对象
严格模式下全局作用域中函数中的this 是 undefined
以前构造函数时不加 new 也可以调用,当普通函数,this指向全局对象
严格模式下,如果构造函数不加 new 调用,this指向的是 undefined ,如果给它赋值,会报错
new 实例化的构造函数指向创建的对象实例
定时器this 还是指向window
事件、对象还是指向调用者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <body > <script > 'use strict' ; function fn ( ) { console .log (this ); } fn (); function Star ( ) { this .sex = '男' ; } var ldh = new Star (); console .log (ldh.sex ); setTimeout (function ( ) { console .log (this ); }, 2000 ); </script > </body >
3、函数变化
函数不能有重名的参数
函数必须声明在顶层,新版本的JavaScript会引入“块级作用域”(ES6中已引入)。为了与新版本接轨,不允许在非函数的代码块内声明函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <body > <script > 'use strict' ; function fn (a, a ) { console .log (a + a); }; function fn ( ) {} if () { function fn ( ) {} } </script > </body >
8、高阶函数
高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出
接收函数作为参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <body > <div > </div > <script > function fn (a, b, callback ) { console .log (a + b); callback && callback (); } fn (1 , 2 , function ( ) { console .log ('我是最后调用的' ); }); </script > </body >
将函数作为返回值
1 2 3 4 5 <script > function fn ( ){ return function ( ) {} } </script >
此时 fn 就是一个高阶函数
函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用。最典型的就是作为回调函数
同理函数也可以作为返回值传递回来
9、闭包 1、变量作用域 变量根据作用域的不同分为两种:全局变量和局部变量
函数内部可以使用全局变量
函数外部不可以使用局部变量
当函数执行完毕,本作用域内的局部变量会销毁。
2、什么是闭包 闭包指有权访问另一个函数作用域中的变量的函数
简单理解:一个作用域可以访问另外一个函数内部的局部变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <body > <script > function fn1 ( ) { var num = 10 ; function fn2 ( ) { console .log (num); } fn2 (); } fn1 (); </script > </body >
3、在chrome中调试闭包
打开浏览器,按 F12 键启动 chrome 调试工具。
设置断点。
找到 Scope 选项(Scope 作用域的意思)。
当我们重新刷新页面,会进入断点调试,Scope 里面会有两个参数(global 全局作用域、local 局部作用域)。
当执行到 fn2() 时,Scope 里面会多一个 Closure 参数 ,这就表明产生了闭包。
4、闭包的作用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <body > <script > function fn ( ) { var num = 10 ; return function ( ) { console .log (num); } } var f = fn (); f (); </script > </body >
练习:
点击li输出索引号:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <body > <ul class ="nav" > <li > 榴莲</li > <li > 臭豆腐</li > <li > 鲱鱼罐头</li > <li > 大猪蹄子</li > </ul > <script > var lis = document .querySelector ('.nav' ).querySelectorAll ('li' ); for (var i = 0 ; i < lis.length ; i++) { lis[i].index = i; lis[i].onclick = function ( ) { console .log (this .index ); } } for (var i = 0 ; i < lis.length ; i++) { (function (i ) { lis[i].onclick = function ( ) { console .log (i); } })(i); } </script > </body >
定时器中的闭包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <body > <ul class ="nav" > <li > 榴莲</li > <li > 臭豆腐</li > <li > 鲱鱼罐头</li > <li > 大猪蹄子</li > </ul > <script > var lis = document .querySelector ('.nav' ).querySelectorAll ('li' ); for (var i = 0 ; i < lis.length ; i++) { (function (i ) { setTimeout (function ( ) { console .log (lis[i].innerHTML ); }, 3000 ) })(i); } </script > </body >
10、递归 如果一个函数在内部可以调用其本身,那么这个函数就是递归函数
简单理解: 函数内部自己调用自己,这个函数就是递归函数
由于递归很容易发生”栈溢出”错误,所以必须要加退出条件 return
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <body > <script > var num = 1 ; function fn ( ) { console .log ('我要打印6句话' ); if (num == 6 ) { return ; } num++; fn (); } fn (); </script > </body >
11、浅拷贝和深拷贝
浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用
深拷贝拷贝多层,每一级别的数据都会拷贝
Object.assign(target,....sources) ES6新增方法可以浅拷贝
1、浅拷贝 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var obj = { id : 1 , name : 'andy' , msg : { age : 18 } };var o = {}for (var k in obj){ o[k] = obj.[k]; }console .log (o);Object .assign (o,obj);
2、深拷贝 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 var obj = { id : 1 , name : 'andy' , msg : { age : 18 } color : ['pink' ,'red' ] };var o = {};function deepCopy (newobj,oldobj ){ for (var k in oldobj){ var item = obldobj[k]; if (item instanceof Array ){ newobj[k] = []; deepCopy (newobj[k],item) }else if (item instanceof Object ){ newobj[k] = {}; deepCopy (newobj[k],item) }else { newobj[k] = item; } } }deepCopy (o,obj);
12、正则表达式 正则表达式是用于匹配字符串中字符组合的模式。在JavaScript中,正则表达式也是对象。
正则表通常被用来检索、替换那些符合某个模式(规则)的文本,例如验证表单:用户名表单只能输入英文字母、数字或者下划线, 昵称输入框中可以输入中文(匹配)。此外,正则表达式还常用于过滤掉页面内容中的一些敏感词(替换),或从字符串中获取我们想要的特定部分(提取)等 。
1、特点
实际开发,一般都是直接复制写好的正则表达式
但是要求会使用正则表达式并且根据自身实际情况修改正则表达式
2、创建正则表达式 在JavaScript中,可以通过两种方式创建正则表达式
通过调用 RegExp 对象的构造函数创建
通过字面量创建
1、通过调用 RegExp 对象的构造函数创建
1 var 变量名 = new RegExp (/表达式/ );
2、通过字面量创建
注释中间放表达式就是正则字面量
3、测试正则表达式 test
test()正则对象方法,用于检测字符串是否符合该规则,该对象会返回true或false,其参数是测试字符串
regexObj 写的是正则表达式
str 我们要测试的文本
就是检测str文本是否符合我们写的正则表达式规范
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <body > <script > var regexp = new RegExp (/123/ ); console .log (regexp); var rg = /123/ ; console .log (rg.test (123 )); console .log (rg.test ('abc' )); </script > </body >
3、正则表达式中的特殊在字符 1、边界符 正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符
边界符
说明
^
表示匹配行首的文本(以谁开始)
$
表示匹配行尾的文本(以谁结束)
如果^ 和 $ 在一起,表示必须是精确匹配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var rg = /abc/ ; console .log (rg.test ('abc' ));console .log (rg.test ('abcd' ));console .log (rg.test ('aabcd' ));var reg = /^abc/ ;console .log (reg.test ('abc' )); console .log (reg.test ('abcd' )); console .log (reg.test ('aabcd' )); var reg1 = /^abc$/
2、字符类
字符类表示有一系列字符可供选择,只要匹配其中一个就可以了
所有可供选择的字符都放在方括号内
1、[] 方括号 1 /[abc]/ .test('andy' ); // true
后面的字符串只要包含 abc 中任意一个字符,都返回true
2、[-]方括号内部 范围符
方括号内部加上 - 表示范围,这里表示 a - z 26个英文字母都可以
3、[^] 方括号内部 取反符 ^ 1 /[^abc]/ .test('andy' ) // false
方括号内部加上 ^ 表示取反,只要包含方括号内的字符,都返回 false
注意和边界符 ^ 区别,边界符写到方括号外面
4、字符组合 1 /[a-z1-9]/ .test('andy' ) // true
方括号内部可以使用字符组合,这里表示包含 a 到 z的26个英文字母和1到9的数字都可以
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <body > <script > var rg = /[abc]/ ; console .log (rg.test ('andy' )); console .log (rg.test ('baby' )); console .log (rg.test ('color' )); console .log (rg.test ('red' )); var rg1 = /^[abc]$/ ; console .log (rg1.test ('aa' )); console .log (rg1.test ('a' )); console .log (rg1.test ('b' )); console .log (rg1.test ('c' )); console .log (rg1.test ('abc' )); console .log ('------------------' ); var reg = /^[a-z]$/ ; console .log (reg.test ('a' )); console .log (reg.test ('z' )); console .log (reg.test (1 )); console .log (reg.test ('A' )); var reg1 = /^[a-zA-Z0-9_-]$/ ; console .log (reg1.test ('a' )); console .log (reg1.test ('B' )); console .log (reg1.test (8 )); console .log (reg1.test ('-' )); console .log (reg1.test ('_' )); console .log (reg1.test ('!' )); console .log ('----------------' ); var reg2 = /^[^a-zA-Z0-9_-]$/ ; console .log (reg2.test ('a' )); console .log (reg2.test ('B' )); console .log (reg2.test (8 )); console .log (reg2.test ('-' )); console .log (reg2.test ('_' )); console .log (reg2.test ('!' )); </script > </body >
3、量词符 量词符用来设定某个模式出现的次数
量词
说明
*
重复零次或更多次
+
重复一次或更多次
?
重复零次或一次
{n}
重复n次
{n,}
重复n次或更多次
{n,m}
重复n到m次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 <body > <script > var reg = /^a{3,}$/ ; console .log (reg.test ('' )); console .log (reg.test ('a' )); console .log (reg.test ('aaaa' )); console .log (reg.test ('aaa' )); var reg = /^a{3,6}$/ ; console .log (reg.test ('' )); console .log (reg.test ('a' )); console .log (reg.test ('aaaa' )); console .log (reg.test ('aaa' )); console .log (reg.test ('aaaaaaa' )); </script > </body >
4、用户名验证 功能需求:
如果用户名输入合法, 则后面提示信息为 : 用户名合法,并且颜色为绿色
如果用户名输入不合法, 则后面提示信息为: 用户名不符合规范, 并且颜色为绿色
分析:
用户名只能为英文字母,数字,下划线或者短横线组成, 并且用户名长度为 6~16位.
首先准备好这种正则表达式模式 /$[a-zA-Z0-9-_]{6,16}^/
当表单失去焦点就开始验证.
如果符合正则规范, 则让后面的span标签添加 right 类.
如果不符合正则规范, 则让后面的span标签添加 wrong 类.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <body > <input type ="text" class ="uname" > <span > 请输入用户名</span > <script > var reg = /^[a-zA-Z0-9_-]{6,16}$/ ; var uname = document .querySelector ('.uname' ); var span = document .querySelector ('span' ); uname.onblur = function ( ) { if (reg.test (this .value )) { console .log ('正确的' ); span.className = 'right' ; span.innerHTML = '用户名格式输入正确' ; } else { console .log ('错误的' ); span.className = 'wrong' ; span.innerHTML = '用户名格式输入不正确' ; } } </script > </body >
4、括号总结
大括号 量词符 里面面表示重复次数
中括号 字符集合 匹配方括号中的任意字符
小括号 表示优先级
1 2 3 4 5 6 7 var reg = /^[abc]$/ ;var reg = /^abc{3}$/ ; var reg = /^(abc){3}$/ ;
在线测试正则表达式:https://c.runoob.com/
5、预定义类 预定义类指的是 某些常见模式的简写写法
预定类
说明
\d
匹配0-9之间的任一数字,相当于[0-9]
\D
匹配所有0-9以外的字符,相当于[ ^ 0-9]
\w
匹配任意的字母、数字和下划线,相当于[A-Za-z0-9_ ]
\W
除所有字母、数字、和下划线以外的字符,相当于[ ^A-Za-z0-9_ ]
\s
匹配空格(包括换行符,制表符,空格符等),相当于[\t\t\n\v\f]
\S
匹配非空格的字符,相当于[ ^ \t\r\n\v\f]
1、表单验证 分析:
1.手机号码: /^1[3|4|5|7|8][0-9]{9}$/
2.QQ: [1-9][0-9]{4,} (腾讯QQ号从10000开始)
3.昵称是中文: ^[\u4e00-\u9fa5]{2,8}$
1 2 3 4 5 6 7 8 <body > <script > var reg = /^\d{3,4}-\d{7,8}$/ ; </script > </body >
6、正则表达式中的替换 1、replace 替换
replace()方法可以实现替换字符串操作,用来替换的参数可以是一个字符串或是一个正则表达式
1 string Object.replace(regexp/substr,replacement)
第一个参数: 被替换的字符串或者正则表达式
第二个参数:替换为的字符串
返回值是一个替换完毕的新字符串
1 2 3 4 var str = 'andy和red' ;var newStr = str.replace ('andy' ,'baby' );var newStr = str.replace (/andy/ ,'baby' );
2、正则表达式参数
switch按照什么样的模式来匹配,有三种
g: 全局匹配
i:忽略大小写
gi: 全局匹配 + 忽略大小写
ES6
ES全称EcmaScript,是脚本语言的规范,而平时经常编写的JavaScript,是EcmaScript的一种实现,所以ES新特性其实就是指的是JavaScript的新特性
let变量
1 2 3 4 let a;let b,c,d;let e = 100;let f = 32, g = 'dad' , h = []
let 允许创建块级作用域 ,ES6 推荐在函数中使用 let 定义变量,而非 var
let 是在代码块内有效,var 是在全局范围内有效
特点:
变量不能重复声明,只能声明一次
1 2 let star = '妮露' ;let star = '胡桃' ;
块级作用域 全局,函数 eval
1 2 3 4 { let star= '妮露' ; } consloe.log (star);
不存在变量提升
1 2 console .log (star); let star = '妮露' ;
不影响作用域链
1 2 3 4 5 6 7 { let star = '妮露' ; function fn () { console.log(star); } }fn () ;
for 循环计数器很适合用 let
1 2 3 4 5 6 7 8 9 10 11 12 for (var i = 0 ; i < 10 ; i++) { setTimeout (function ( ){ console .log (i); }) }for (let j = 0 ; j < 10 ; j++) { setTimeout (function ( ){ console .log (j); }) }
变量 i 是用 var 声明的,在全局范围内有效,所以全局中只有一个变量 i, 每次循环时,setTimeout 定时器里面的 i 指的是全局变量 i ,而循环里的十个 setTimeout 是在循环结束后才执行,所以此时的 i 都是 10。
变量 j 是用 let 声明的,当前的 j 只在本轮循环中有效,每次循环的 j 其实都是一个新的变量,所以 setTimeout 定时器里面的 j 其实是不同的变量,即最后输出 12345。(若每次循环的变量 j 都是重新声明的,如何知道前一个循环的值?这是因为 JavaScript 引擎内部会记住前一个循环的值)。
const变量 同样在块级作用域有效的另一个变量声明方式是 const,它可以声明一个常量。ES6 中,const 声明的常量类似于指针,它指向某个引用,也就是说这个「常量」并非一成不变的 ,如:
1 2 3 4 5 6 { const ARR = [5 ,6 ]; ARR .push (7 ); console .log (ARR ); ARR = 10 ; }
有几个点需要注意:
let 和 const 声明只在最靠近的一个块中(花括号内)有效
当使用常量 const 声明时,请使用大写变量 ,如:CAPITAL_CASING(一般都是大写)
const 在声明时必须被赋值
常量的值不能修改
块级作用域
对于数组和对象的元素修改,不算做对常量的修改,不会报错
变量的解构赋值
1、数组的解构 1 2 3 4 5 6 const F4 = ['妮露' ,'申鹤' ,'胡桃' ,'甘雨' ]; let [ni, shen, hu, gan] = F4; console .log (ni);console .log (shen);console .log (hu);console .log (gan);
2、对象的解构 1 2 3 4 5 6 7 8 9 10 11 const ni = { name : '妮露' , age : 20 , tiaowu : function ( ) { console .log ("花神之舞" ); } } let {name, age, tiaowu} = ni; console .log (name);console .log (age);console .log (tiaowu);
模板字符串 ES6 中有一种十分简洁的方法组装一堆字符串和变量。
1、声明 1 2 let str = `字符串!`; console.log (str , typeof str );
2、内容中可以直接出现换行符 1 2 3 4 5 6 let str = `<ul > <li > 妮露</li > <li > 申鹤</li > <li > 胡桃</li > </ul > `; console.log(str);
3、变量拼接 1 2 3 let lover = '妮露' ;let wife = `${lover} 是我的老婆` ;console .log (wife);
简化对象写法
ES6 允许声明在对象字面量时使用简写语法 ,来初始化属性变量和函数的定义方法,并且允许在对象属性中进行计算操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 function getCar (make, model, value ) { return { make, model, value, ['make' + make]: true , depreciate ( ) { this .value -= 2500 ; } }; } let car = getCar ('Barret' , 'Lee' , 40000 );
箭头函数 声明:
1 2 3 4 5 6 7 8 9 let fn = function () { } let fn = (a, b) => { return a + b; }let result = fn (1 ,2 ) console .log (result) ;
1、this是静态的
this 始终指向函数声明时所在作用域下的this的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function getName ( ){ console .log (this .name ); }let getName1 = ( ) => { console .log (this .name ); }window .name = '妮露' ;const wife = { name : '胡桃' }getName (); getName1 (); getName.call (wife); getName1.call (wife);
2、不能作为构造实例化对象 1 2 3 4 5 6 let Person = (name, age ) => { this .name = name; this .age = age; }let wife = new Person ('妮露' ,20 );console .log (wife);
3、不能使用 arguments 变量 1 2 3 4 let fn = ( ) => { console.log (arguments); }fn (1 ,2 ,3 ) ;
4、箭头函数的简写
1 2 3 4 let add = n => { return n + n; }console .log (9 );
省略花括号——当代码体只有一条语句的时候,此时 return必须省略,而且语句的执行结果就是函数的返回值
1 2 let pow = n => n * n;console .log (pow (8 ));
函数参数默认值
1 2 3 4 5 6 7 function add (a,b,c=10 ) { return a + b + c; } let result = add(1 ,2 ); let result1 = add(1 ,2 ,3 );console .log (result); console .log (result1);
1 2 3 4 5 6 7 8 function connect ({name ='胡桃' ,age}){ console.log(name ); console.log(age); }connect ({ name : '妮露' , // 如果没有name 这个属性,那么输出显示的name 就是初始化的胡桃了,有了就会替换掉 原有的 age: 20 })
Spread、rest Spread / Rest 操作符指的是 …,具体是 Spread 还是 Rest 需要看上下文语境。
当被用于迭代器中时,它是一个 Spread 操作符
1 2 3 4 5 6 function foo (x,y,z ) { console .log (x,y,z); } let arr = [1 ,2 ,3 ];foo (...arr);
1 2 3 4 function foo (...args ) { console.log(args); } foo( 1 , 2 , 3 , 4 , 5 );
1 2 3 4 5 6 function foo (a,b,...args ) { console.log(a); console.log(b); console.log(args); } foo( 1 , 2 , 3 , 4 , 5 );
扩展运算符
1 2 3 4 5 6 const wives = ['妮露' ,'申鹤' ,'胡桃' ,'宵宫' ];function chu ( ){ console .log (arguments ); }chu (...wives);
应用 1、数组的合并 1 2 3 4 const wife1 = ['妮露' ,'申鹤' ];const wife2 = ['胡桃' ,'宵宫' ];const wife = [...wife1,...wife2];console .log (wife);
2、数组的克隆 浅拷贝 1 2 3 const wife1 = ['妮露' ,'申鹤' ];const wife = [...wife1];console .log (wife);
3、将伪数组转为真正的数组 1 2 3 const divs = document .querySelectorAll ('div' );const divArr = [...divs];console .log (divArr);
symbol
1、创建 1 2 3 let s = Symbol ();let s1 = Symbol ('妮露' );let s2 = Symbol .for ('胡桃' );
2、使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 let game = { up : function ( ) { console .log ("上升!" ); }, name : '妮露' , down : function ( ) { console .log ("下降!" ); } }let methods = { up : Symbol (), down : Symbol () } game[methods.up ] = function ( ) { console .log ("上升~!" ); } game[methods.down ] = function ( ) { console .log ("下降~!" ); }console .log (game);
1 2 3 4 5 6 7 8 9 10 let game = { name : '妮露' , [Symbol('up' )]: function ( ) { console .log ("上升~" ); }, [Symbol('down' )]: function ( ) { console .log ("下降~" ); } }console .log (game);
3、内置属性 迭代器iterator
for-of和for-in区别
for-of——保存的键值
for-in——保存的键名
工作原理
1 2 3 4 5 6 7 8 const wife = ['妮露' ,'胡桃' ,'宵宫' ,'申鹤' ] let iterator = wife[Symbol.iterator]() // 调用对象的next 方法 console.log (iterator.next ()) console.log (iterator.next ()) console.log (iterator.next ()) console.log (iterator.next ()) console.log (iterator.next ())
实例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 const wife = { name : "原神" , stus :['妮露' ,'胡桃' ,'宵宫' ,'申鹤' ], [Symbol .iterator ](){ let index = 0 ; let _this = this ; return { next : function ( ){ if (index <_this.stus .length ) { const result = {value : _this.stus [index], done : false }; index++; return result; }else { return {value : undefined , done : true }; } } } } };for (let v of wife) { console .log (v); } 结果: 妮露 胡桃 宵宫 申鹤
生成器Generator 传统的编程语言,早有异步编程的解决方案(其实是多任务的解决方案)。其中有一种叫做”协程”(coroutine),意思是多个线程互相协作,完成异步任务。
协程有点像函数,又有点像线程。它的运行流程大致如下。
第一步,协程A开始执行。
第二步,协程A执行到一半,进入暂停,执行权转移到协程B。
第三步,(一段时间后)协程B交还执行权。
第四步,协程A恢复执行。
上面流程的协程A,就是异步任务,因为它分成两段(或多段)执行。
Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)
整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用yield语句注明。Generator 函数的执行方法如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function * gen ( ) { yield 'qeer' ; yield 'asdf' ; yield 'zxcv' ; }let iterator = gen (); iterator.next (); iterator.next (); iterator.next (); iterator.next (); for (const v of gen ()) { console .log (v); }
1 2 3 4 5 6 7 8 function * gen (x ) { var y = yield x + 2 ; return y; };var g = gen (1 );console .log (g.next ()); console .log (g.next ());
上面代码中,调用 Generator 函数,会返回一个内部指针(即遍历器)g。这是 Generator 函数不同于普通函数的另一个地方,即执行它不会返回结果,返回的是指针对象。调用指针g的next方法,会移动内部指针(即执行异步任务的第一段),指向第一个遇到的yield语句,上例是执行到x + 2为止。
换言之,next方法的作用是分阶段执行Generator函数。每次调用next方法,会返回一个对象,表示当前阶段的信息(value属性和done属性)。value属性是yield语句后面表达式的值,表示当前阶段的值;done属性是一个布尔值,表示 Generator 函数是否执行完毕,即是否还有下一个阶段。
实例1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 // 异步编程 文件操作 网络操作(ajax,request) 数据库操作// 1 s后控制台输出111 2 s后输出222 3 秒后输出333 总共6 秒// 回调地狱 一直回调,一层套一层,不断往前缩进// setTimeout(() => {// console.log(111 );// setTimeout(() => {// console.log(222 );// setTimeout(() => {// console.log(333 );// },3000 )// }, 2000 )// }, 1000 ) // 解决方法 function one() { setTimeout(() => { console.log(111 ); iterator.next (); }, 1000 ) } function two() { setTimeout(() => { console.log(222 ); iterator.next (); }, 2000 ) } function three() { setTimeout(() => { console.log(333 ); iterator.next (); }, 3000 ) } // 生成器 function * gen(){ yield one(); yield two(); yield three(); } // 调用生成器函数 let iterator = gen(); // 这样只会执行第一个,但后面的不能执行,因为有 yiled 它会停止生成器的运行 // 所以需要在每个方法里面添加 iterator.next (); 以确保生成器运行 iterator.next ();
实例2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 function getUsers ( ) { setTimeout (() => { let data = '用户数据' ; iterator.next (data); }, 1000 ) } function getOrders ( ) { setTimeout (() => { let data = '订单数据' ; iterator.next (data); }, 1000 ) } function getGoods ( ) { setTimeout (() => { let data = '商品数据' ; iterator.next (data); }, 1000 ) } function * gen ( ){ let users = yield getUsers (); console .log (users); let orders = yield getOrders (); console .log (orders); let goods = yield getGoods (); console .log (goods); } let iterator = gen (); iterator.next ();
Promise
Promise是ES6引入的异步编程的新解决方案
Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)
语法上,Promise是一个构造函数 ,用来封装异步操作并可以获取其成功或失败的结果
1 2 3 Promise 构造函数:Promise (excutor) {}Promise .prototype .then 方法Promise .prototype .catch 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const p = new Promise (function (resolve,reject ) { setTimeout (function ( ) { let err = '读取数据失败' ; reject (err); },1000 ) }); p.then (function (value ){ console .log (value); },function (reason ){ console .error (reason); })
resolve——将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功 时调用,并将异步操作的结果,作为参数传递出去
reject——将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败 时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。then方法可以接受两个回调函数 作为参数
第一个回调函数是Promise对象的状态变为resolved时调用。默认写value
二个回调函数是Promise对象的状态变为rejected时调用。默认写reason
读取文件1 1 2 3 4 5 6 7 8 9 10 const fs = require ('fs' ); fs.readFile ('为学.md' ,(err, data ) => { if (err) throw err; console .log (data.toString ()); });
Promise封装获取文件2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const fs = require ('fs' );const p = new Promise (function (resolve,reject ){ fs.readFile ('为学.md' ,(err, data ) => { if (err) reject (err); resolve (data); }) }); p.then (function (value ){ console .log (value.toString ()); }, function (reason ){ console .log ("读取失败" ); })
ajax请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const xhr = new XMLHttpRequest (); xhr.open ("GET" ,"http://api.apiopen.top/getJoke" ); xhr.send (); xhr.onreadystatechange = function ( ) { if (xhr.readyState == 4 ){ if (xhr.status >= 200 && xhr.status < 300 ) { console .log (xhr.response ); }else { console .error (xhr.status ); } } }
Promise封装ajax请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 const p = new Promise ((resolve, reject ) => { const xhr = new XMLHttpRequest (); xhr.open ("GET" , "https://api.apiopen.top/getJoke" ); xhr.send (); xhr.onreadystatechange = function ( ) { if (xhr.readyState == 4 ) { if (xhr.status >= 200 && xhr.status < 300 ) { resolve (xhr.response ); } else { reject (xhr.status ); } } } }) p.then (function (value ){ console .log (value); }, function (reason ){ console .error (reason); })
Promise.prototype.then() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const p = new Promise ((resolve,reject ) => { setTimeout (() => { resolve ('用户数据' ); }, 1000 ); });const result = p.then (value => { console .log (value); throw '错误~' ; }, reason => { console .error (reason); })console .log (result);
返回的是 非Promise 类型,fulfilled就是成功
链式调用 1 2 3 4 5 p.then (value => { }).then (value => { });
读取多个文件 1 2 3 4 5 6 7 8 9 10 11 12 const fs = require ("fs" ); fs.readFile ('为学.md' ,(err, data1 ) => { fs.readFile ('李白.md' ,(err, data2 ) => { fs.readFile ('杜甫.md' ,(err, data3 ) => { let result = data1 + '\r\n' + data2 + '\r\n' + data3; console .log (result); }); }); });
Promise多个文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 const fs = require ("fs" );const p = new Promise ((reslove, reject ) => { fs.readFile ('为学.md' , (err, data ) => { reslove (data); }); }); p.then (value => { return new Promise ((reslove, reject ) => { fs.readFile ('李白.md' , (err, data ) => { reslove ([value, data]); }); }) }).then (value => { return new Promise ((reslove, reject ) => { fs.readFile ('杜甫.md' , (err, data ) => { value.push (data); reslove (value); }); }) }).then (value => { console .log (value.join ('\r\n' )); });
catch方法 1 2 3 4 5 6 7 8 9 10 11 12 const p = new Promise ((resolve, reject ) => { setTimeout (() => { reject ("错误~" ); }, 1000 ); }) p.then (function (value ){}, function (reason ){ console .error (reason); }) p.catch (function (reason ){ console .warn (reason); });
集合Set
Set本身是一个构造函数,用来生成 Set 数据结构
Set 执行完返回一个对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let s = new Set ();let s2 = new Set (['妮露' ,'胡桃' ,'申鹤' ,'宵宫' ,'胡桃' ]);console .log (s2.size ); s2.add ('莫娜' ); s2.delete ('莫娜' ); console .log (s2.has ('妮露' )); s2.clear (); console .log (s2); console .log (s, typeof s);
Map
它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 let m = new Map (); m.set ('name' ,'妮露' ); m.set ('change' , function ( ){ console .log ("改变~" ); });let key = { school : '提瓦特' }; m.set (key,['蒙德' ,'璃月' ,'稻妻' ]);console .log (m.size ); console .log (m.get ('change' )); console .log (m.get (key)); for (let v of m) { console .log (v); }
数值扩展 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 function equal (a, b ) { if (Math .abs (a - b) < Number .EPSILON ){ return true ; }else { return false ; } }console .log (0.1 + 0.2 === 0.3 ); console .log (equal (0.1 + 0.2 , 0.3 )); let a = 0b1010 ; let b = 0o222 ; console .log (a); console .log (b); console .log (Number .isFinite (100 )); console .log (Number .isFinite (100 /0 )); console .log (Number .isFinite (Infinity )); console .log (Number .isNaN (123 )); console .log (Number .parseInt ('1223da' )); console .log (Number .parseFloat ('3.214深' )); console .log (Number .isInteger (4 )); console .log (Number .isInteger (2.3 )); console .log (Math .trunc (2.1 )); console .log (Math .sign (19 )); console .log (Math .sign (0 )); console .log (Math .sign (-21 ));
对象方法扩展 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 console .log (Object .is (120 ,120 )); console .log (NaN === NaN ); console .log (Object .is (NaN ,NaN )); const wife1 = { name : '妮露' , age : 20 , test : 'test' }const wife2 = { name : '胡桃' }console .log (Object .assign (wife1,wife2)); const wife = { name : '妮露' }const ele = { yuanshu : ['火' ,'水' ,'雷' ] }Object .setPrototypeOf (wife,ele);console .log (Object .getPrototypeOf (wife));console .log (ele);
模块化 模块化是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来
优势:
模块功能主要由两个命令构成:export和import
export——用于规定模块的对外接口
import——用于输入其他模块提供的功能
使用 m1.js //文件名
1 2 3 4 5 export let wife = '妮露' ;export function dance ( ) { console .log ("花神之舞~~~" ); }
index.htm //文件名
1 2 3 4 5 6 7 <body > <script type ="module" > import * as m1 from "./src/js/m1.js" ; console .log (m1); </script > </body >
暴露数据语法 统一暴露
1 2 3 4 5 let wife = '妮露' ;function dance ( ) { console .log ("花神之舞~~~" ); }export {wife,dance};
默认暴露
1 2 3 4 5 6 export default { wife : '妮露' , dance : function ( ) { console .log ("花神之舞~~~" ); } }
引入 1 2 3 4 5 6 7 8 9 10 11 12 13 import * as m1 from "./src/js/m1.js" ;import {name, dance} from "./src/js/m1.js" ;import {name as nilu, dance as tiaowu} from "./src/js/m2.js" ;import {default as m3} from "./src/js/m3.js" ;import m3 from "../src/js/m3.js" ;
入口文件 当引入过多时,我们可以单独写一个js文件作为模块引入的入口文件,这样我们只需要引入一个js文件即可
1 <script src="./src/js/app.js" type="module" ></script>
app.js //文件名
1 2 3 import * as m1 from "./src/js/m1.js" ;import * as m2 from "./src/js/m2.js" ;import m3 from "./src/js/m3.js" ;
项目实际