JavaScript高级+ES6
一、面向过程和面向对象
- 面向过程
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了。
概括:面向过程,就是按照我们分析好了的步骤,按照步骤解决问题。
- 面向对象
面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作。
概括:面向对象是以对象功能来划分问题,而不是步骤。
在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工。 面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目。
面向对象的特性: 封装性 继承性 多态性
- 总结:
面向过程主要面向的是步骤,按照步骤一步一步的来实现。
面向对象,首先先把对象找出来,再看对象里面有什么功能,最后将对象的功能使用起来。
二、类和对象
- 对象
现实生活中:万物皆对象,对象是一个具体的事物,看得见摸得着的实物。例如,一本书、一辆汽车、一个人可以是“对象”,一个数据库、一张网页、一个与远程服务器的连接也可以是“对象”。
在 JavaScript 中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,例如字符串、数值、数组、函数等。
对象是由属性和方法组成的:
属性:事物的特征,在对象中用属性来表示(常用名词)
方法:事物的行为,在对象中用方法来表示(常用动词)
- 类
在 ES6 中新增加了类的概念,可以使用 class 关键字声明一个类,之后以这个类来实例化对象。
类抽象了对象的公共部分,它泛指某一大类(class) 对象特指某一个,通过类实例化一个具体的对象 。
- 总结:
类抽象了对象的公共部分,它泛指某一大类(class)。
对象特指某一个,通过类实例化一个具体的对象 。
- 面向对象的思维特点:
抽取(抽象)对象共用的属性和行为组织(封装)成一个类(模板)。
对类进行实例化, 获取类的对象。
三、创建类
语法:
class name {// class body
}
创建实例:
var xx = new name();
注意: 类必须使用 new 实例化对象
- 类 constructor 构造函数
constructor() 方法是类的构造函数(默认方法),用于传递参数,返回实例对象,通过 new 命令生成对象实例时,自动调用该方法。如果没有显示定义, 类内部会自动给我们创建一个constructor()
语法:
class Person {constructor(name,age) { // constructor 构造方法或者构造函数this.name = name;this.age = age;}
}
创建实例:
var ldh = new Person('刘德华', 18);
console.log(ldh.name);
- 创建类和对象
// 1. 创建类class 创建一个明星类
class Star {// 类的共有属性放到 constructor 里面constructor(uname, age) {this.uname = uname;this.age = age;}// 类中添加方法sing(song) {console.log(this.uname + song);}
}// 2. 利用类创建对象 new
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 20);
console.log(ldh); // Star {uname: "刘德华", age: 18}
console.log(zxy); // Star {uname: "张学友", age: 20}
ldh.sing('冰雨'); // 刘德华冰雨
ldh.sing('李香兰'); // 张学友李香兰
四、类的继承
- 继承
现实中的继承:子承父业,比如我们都继承了父亲的姓。
程序中的继承:子类可以继承父类的一些属性和方法。
语法:
class Father{ // 父类}
class Son extends Father { // 子类继承父类}
- super 关键字
super 关键字用于访问和调用对象父类上的函数。可以调用父类的构造函数,也可以调用父类的普通函数。
语法:
class Person { // 父类constructor(surname){this.surname = surname;}
}
class Student extends Person { // 子类继承父类constructor(surname,firstname){super(surname); // 调用父类的constructor(surname)this.firstname = firstname; // 定义子类独有的属性}
}
注意: 子类在构造函数中使用super, 必须放到 this 前面 (必须先调用父类的构造方法,再使用子类构造方法)。
// super关键字调用父类构造函数
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); // 调用了父类中的构造函数}
}
var son1 = new Son(1, 2);
var son2 = new Son(11, 22);
son1.sum(); // 3
son2.sum(); // 33
// super关键字调用父类普通函数
class Father {say() {return '我是爸爸';}
}class Son extends Father {say() {// console.log('我是儿子');// super.say() 就是调用父类的普通函数 say()console.log(super.say() + '的儿子');}
}
var son = new Son();
son.say(); // 我是爸爸的儿子
// 子类继承父类方法同时扩展自己的方法
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调用父类的构造函数,super必须在子类this之前调用super(x, y);this.x = x;this.y = y;}subtract() {console.log(this.x - this.y);}
}
var son = new Son(10, 5);
son.sum(); // 15
son.subtract(); // 5
- 注意点:
- 在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象.。
- 类里面的共有属性和方法一定要加this使用。
- 类里面的this指向问题。
- constructor 里面的this指向实例对象, 方法里面的this 指向这个方法的调用者。
五、 构造函数和原型
创建对象可以通过以下三种方式:
- 对象字面量
- new Object()
- 自定义构造函数
- 构造函数
构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与 new 一起使用。我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面。
在 JS 中,使用构造函数时要注意以下两点:
- 构造函数用于创建某一类对象,其首字母要大写
- 构造函数要和 new 一起使用才有意义
new 在执行时会做四件事情:
- 在内存中创建一个新的空对象。
- 让 this 指向这个新的对象。
- 执行构造函数里面的代码,给这个新对象添加属性和方法。
- 返回这个新对象(所以构造函数里面不需要 return )。
// 利用构造函数创建对象
function Star(uname, age) {this.uname = uname;this.age = age;this.sing = function () {console.log('我会唱歌');}
}
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 20);
console.log(ldh); // Star {uname: "刘德华", age: 18, sing: ƒ}
console.log(zxy); // Star {uname: "张学友", age: 20, sing: ƒ}
ldh.sing(); // 我会唱歌
zxy.sing(); // 我会唱歌
- 静态成员和实例成员
JavaScript 的构造函数中可以添加一些成员,可以在构造函数本身上添加,也可以在构造函数内部的 this 上添加。通过这两种方式添加的成员,就分别称为静态成员和实例成员。
- 静态成员:在构造函数本身上添加的成员称为静态成员,只能由构造函数本身来访问
- 实例成员:在构造函数内部创建的对象成员称为实例成员,只能由实例化的对象来访问
// 构造函数中的属性和方法称为成员,成员可以添加
function Star(uname, age) {this.uname = uname;this.age = age;this.sing = function () {console.log('我会唱歌');}
}
var ldh = new Star('刘德华', 18);
// 实例成员就是构造函数内部通过this添加的成员,uname age sing 就是实例成员
// 实例成员只能通过实例化的对象来访问
console.log(ldh.uname, ldh.age); // 刘德华 18
ldh.sing(); // 我会唱歌
// 静态成员是在构造函数本身上添加的成员,sex 就是静态成员
// 静态成员只能通过构造函数来访问
Star.sex = '男';
console.log(Star.sex); // 男
- 构造函数原型 prototype
构造函数通过原型分配的函数是所有对象所共享的。
JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象。注意这个 prototype 就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。
可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
问答?
1. 原型是什么 ?
一个对象,我们也称为 prototype 为原型对象。
2. 原型的作用是什么 ?
共享方法。
// 一般情况下,公共属性定义到构造函数里面,公共的方法放到原型对象身上
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(); // 我会唱歌
zxy.sing(); // 我会唱歌
console.log(ldh.sing === zxy.sing); // true
- 对象原型 __proto__
对象都会有一个属性 __proto__ 指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 __proto__ 原型的存在。
- __proto__对象原型和原型对象 prototype 是等价的
- __proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype
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();
// 对象身上系统自己添加一个 __proto__ 指向构造函数的原型对象 prototype
console.log(ldh);
console.log(ldh.__proto__ === Star.prototype); // true
/*方法的查找规则:首先先看ldh对象身上是否有sing方法,如果有就执行这个对象上的sing;如果没有sing这个方法,因为有__proto__的存在,就去构造函数原型对象prototype身上去查找sing这 个方法
*/
- constructor 构造函数
对象原型( __proto__)和构造函数(prototype)原型对象里面都有一个属性 constructor 属性 ,constructor 我们称为构造函数,因为它指回构造函数本身。
constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
一般情况下,对象的方法都在构造函数的原型对象中设置。如果有多个对象的方法,我们可以给原型对象采取对象形式赋值,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了。此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。
function Star(uname, age) {this.uname = uname;this.age = age;
}
// 很多情况下,需要手动的利用constructor这个属性指回原来的构造函数
// Star.prototype.sing = function () {
// console.log('我会唱歌');
// }
// Star.prototype.movie = function () {
// console.log('我会演电影');
// }
Star.prototype = {// 当修改了原来的原型对象的时候,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数constructor: Star,sing: function () {console.log('我会唱歌');},movie: function () {console.log('我会演电影');}
}
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
console.log(Star.prototype);
console.log(ldh.__proto__);
console.log(Star.prototype.constructor);
console.log(ldh.__proto__.constructor);
- 构造函数、实例、原型对象三者之间的关系
- 原型链
- JavaScript 的成员查找机制(规则)
- 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
- 如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)。
- 如果还没有就查找原型对象的原型(Object的原型对象)。
- 依此类推一直找到 Object 为止(null)。
- __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
function Star(uname, age) {this.uname = uname;this.age = age;
}
Star.prototype.sing = function () {console.log('我会唱歌');
}
// Star.prototype.sex = '女';
// Object.prototype.sex = '男';
var ldh = new Star('刘德华', 18);
ldh.sex = '男';
console.log(ldh.sex);
- 原型对象this指向
构造函数中的this 指向我们实例对象.。
原型对象里面放的是方法, 这个方法里面的this 指向的是 这个方法的调用者, 也就是这个实例对象。
- 扩展内置对象
可以通过原型对象,对原来的内置对象进行扩展自定义的方法。比如给数组增加自定义求偶数和的功能。
注意:数组和字符串内置对象不能给原型对象覆盖操作 Array.prototype = {} ,只能是 Array.prototype.xxx = function(){} 的方式。
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()); // 6
var arr1 = new Array(11, 22, 33);
console.log(arr1.sum()); // 66
六、继承
- call() 调用这个函数, 并且修改函数运行时的 this 指向
fun.call(thisArg, arg1, arg2, ...)
- thisArg :当前调用函数 this 的指向对象
- arg1,arg2:传递的其他参数
function fn(x, y) {console.log(this);console.log(x + y);
}
var person = {name: 'Andy'
}
// call() 可以改变这个函数的this指向,此时这个函数的this就指向了person这个对象
fn.call(person, 1, 2); // {name: "Andy"} 3
- 借用父构造函数继承父类型属性
核心原理: 通过 call() 把父类型的 this 指向子类型的 this ,这样就可以实现子类型继承父类型的属性。
// 1. 父构造函数
function Father(uname, age) {// this 指向父构造函数的对象实例this.uname = uname;this.age = age;
}
// 2. 子构造函数
function Son(uname, age, score) {// this 指向子构造函数的对象实例Father.call(this, uname, age);this.score = score;
}
var son = new Son('刘德华', 18, 100);
console.log(son); // Son {uname: "刘德华", age: 18, score: 100}
- 借用原型对象继承父类型方法
一般情况下,对象的方法都在构造函数的原型对象中设置,通过构造函数无法继承父类方法。
核心原理:
- 将子类所共享的方法提取出来,让子类的 prototype 原型对象 = new 父类()
- 本质:子类原型对象等于是实例化父类,因为父类实例化之后另外开辟空间,就不会影响原来父类原型对象
- 将子类的 constructor 从新指向子类的构造函数
// 1. 父构造函数
function Father(uname, age) {// this 指向父构造函数的对象实例this.uname = uname;this.age = age;
}
Father.prototype.money = function () {console.log(100000);
}
// 2. 子构造函数
function Son(uname, age, score) {// this 指向子构造函数的对象实例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); // Son {uname: "刘德华", age: 18, score: 100}
console.log(Father.prototype); // {money: ƒ, constructor: ƒ}
console.log(Son.prototype.constructor);
- 类的本质:
- class本质还是function。
- 类的所有方法都定义在类的prototype属性上。
- 类创建的实例,里面也有__proto__ 指向类的prototype原型对象。
- 所以ES6的类它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
- 所以ES6的类其实就是语法糖。
- 语法糖:语法糖就是一种便捷写法。简单理解, 有两种方法可以实现同样的功能, 但是一种写法更加清晰、方便,那么这个方法就是语法糖 。
七、ES5中的方法
- 数组方法
forEach 迭代(遍历)数组
array.forEach(function(currentValue, index, arr))
- currentValue:数组当前项的值
- index:数组当前项的索引
- arr:数组对象本身
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); // 6
filter 筛选数组
array.filter(function(currentValue, index, arr))
- filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素,主要用于筛选数组
- 注意它直接返回一个新数组
- currentValue: 数组当前项的值
- index:数组当前项的索引
- arr:数组对象本身
var arr = [12, 4, 33, 68, 57];
var newArr = arr.filter(function (value, index) {return value >= 20;
});
console.log(newArr); // [33, 68, 57]
some 查找数组中是否有满足条件的元素
array.some(function(currentValue, index, arr))
- some() 方法用于检测数组中的元素是否满足指定条件。通俗点,查找数组中是否有满足条件的元素。
- 注意它返回值是布尔值, 如果查找到这个元素, 就返回true , 如果查找不到就返回false。
- 如果找到第一个满足条件的元素,则终止循环,不在继续查找。
- currentValue: 数组当前项的值。
- index:数组当前项的索引。
- arr:数组对象本身。
var arr = [10, 30, 4];
var flag = arr.some(function (value) {return value >= 20;// return value < 3;
});
console.log(flag); // truevar arr2 = ['red', 'pink', 'blue'];
var flag2 = arr2.some(function (value) {return value == 'pink';
});
console.log(flag2); // true
- 字符串方法
trim() 方法会从一个字符串的两端删除空白字符。
str.trim()
trim() 方法并不影响原字符串本身,它返回的是一个新的字符串。
var str = ' an dy ';
console.log(str); // an dy
console.log(str.trim()); // an dy
- 对象方法
Object.keys() 用于获取对象自身所有的属性
Object.keys(obj)
- 效果类似 for…in
- 返回一个由属性名组成的数组
var obj = {id: 1,pname: '小米',price: 1999,num: 5000
}
var arr = Object.keys(obj);
console.log(arr); // ["id", "pname", "price", "num"]
arr.forEach(function (value) {console.log(value); // id pname price num
})
Object.defineProperty() 定义对象中新属性或修改原有的属性
Object.defineProperty(obj, prop, descriptor)
- obj:必需。目标对象
- prop:必需。需定义或修改的属性的名字
- descriptor:必需。目标属性所拥有的特性
Object.defineProperty() 第三个参数 descriptor 说明: 以对象形式 { } 书写
- value: 设置属性的值 默认为undefined
- writable: 值是否可以重写。true | false 默认为false
- enumerable: 目标属性是否可以被枚举。true | false 默认为 false
- configurable: 目标属性是否可以被删除或是否可以再次修改特性 true | false 默认为false
var obj = {id: 1,pname: '小米',price: 1999
}
Object.defineProperty(obj, 'num', {value: 1000,enumerable: true
})
console.log(obj); // {id: 1, pname: "小米", price: 1999, num: 1000}
Object.defineProperty(obj, 'price', {value: 9.9
})
console.log(obj); // {id: 1, pname: "小米", price: 9.9, num: 1000}
Object.defineProperty(obj, 'id', {writable: false
})
obj.id = 2;
console.log(obj); // {id: 1, pname: "小米", price: 9.9, num: 1000}
Object.defineProperty(obj, 'address', {value: "北京xxx单元",writable: true,enumerable: true,configurable: false
})
delete obj.address;
console.log(obj);
// {id: 1, pname: "小米", price: 9.9, num: 1000, address: "北京xxx单元"}
八、函数的定义和调用
- 函数的定义方式
- 函数声明方式 function 关键字 (命名函数) function fn(){ };
- 函数表达式 (匿名函数) var fun = function(){ };
- new Function()
var fn = new Function('参数1','参数2'..., '函数体')
- Function 里面参数都必须是字符串格式
- 第三种方式执行效率低,也不方便书写,因此较少使用
- 所有函数都是 Function 的实例(对象)
- 函数也属于对象
- 函数的调用方式
普通函数-----this指向:window
function fn() {console.log('人生的巅峰');
}
fn();
fn.call();
对象的方法-----this指向:该方法所属对象
var o = {sayHi: function(){console.log('人生的巅峰');}
}
o.sayHi();
构造函数-----this指向:实例对象,原型对象里面的方法也指向实例对象
function Star() { };
new Star();
绑定事件函数-----this指向:绑定事件对象
// 点击了按钮就可以调用这个函数了
btn.onclick = function() { };
定时器函数-----this指向:window
// 这个函数是定时器自动1秒钟调用一次
setInterval(function() { }, 1000);
立即执行函数-----this指向:window
// 立即执行函数是自动调用
(function() {console.log('人生的巅峰');
})();
- 改变函数内部 this 指向
call() 方法调用一个对象。简单理解为调用函数的方式,但是它可以改变函数的 this 指向。
fun.call(thisArg, arg1, arg2, ...)
- thisArg:在 fun 函数运行时指定的 this 值
- arg1,arg2:传递的其他参数
- 返回值就是函数的返回值,因为它就是调用函数
- 因此当我们想改变 this 指向,同时想调用这个函数的时候,可以使用 call,比如继承
var person = {name: 'Andy'
}
function fn(a, b) {console.log(this); // {name: "Andy"}console.log(a + b); // 4
}
// call 的作用:一是可以调用函数;二是可以改变函数内的this指向
fn.call(person, 1, 3);// call 的主要作用是可以实现继承
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); // Son {uname: "刘德华", age: 18, sex: "男"}
apply() 方法调用一个函数。简单理解为调用函数的方式,但是它可以改变函数的 this 指向。
fun.apply(thisArg, [argsArray])
- thisArg:在fun函数运行时指定的 this 值
- argsArray:传递的值,必须包含在数组里面
- 返回值就是函数的返回值,因为它就是调用函数
- 因此 apply 主要跟数组有关系,比如使用 Math.max() 求数组的最大值
var person = {name: 'Andy'
}
function fn(arr) {console.log(this); // {name: "Andy"}console.log(arr); // 'pink'
}
// apply 的作用:一是可以调用函数;二是可以改变函数内的this指向
fn.apply(person, ['pink']);// apply 的主要运用:可以利用apply借助于数学的内置对象求最大值
var arr = [1, 65, 3, 99, 4];
var max = Math.max.apply(Math, arr);
var min = Math.min.apply(Math, arr);
console.log(max, min); // 99 1
bind() 方法不会调用函数。但是能改变函数内部this 指向。
fun.bind(thisArg, arg1, arg2, ...)
- thisArg:在 fun 函数运行时指定的 this 值
- arg1,arg2:传递的其他参数
- 返回由指定的 this 值和初始化参数改造的原函数拷贝
- 因此当我们只是想改变 this 指向,并且不想调用这个函数的时候,可以使用 bind
// 不会调用原来的函数,可以改变原来函数内部的this指向
// 返回的是原函数改变this之后产生的新函数
// 如果有的函数不需要立即调用,但又想改变这个函数内部的this指向,此时用bind
var person = {name: 'Andy'
}
function fn(a, b) {console.log(this); // {name: "Andy"}console.log(a + b); // 3
}
var f = fn.bind(person, 1, 2);
f();
- call apply bind 总结
相同点: 都可以改变函数内部的this指向
区别点:
- call 和 apply 会调用函数, 并且改变函数内部this指向
- call 和 apply 传递的参数不一样, call 传递参数 aru1, aru2...形式 apply 必须数组形式[arg]
- bind 不会调用函数, 可以改变函数内部this指向
主要应用场景:
- call 经常做继承
- apply 经常跟数组有关系,比如借助于数学对象实现数组最大值最小值
- bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的this指向
九、严格模式
- 什么是严格模式
JavaScript 除了提供正常模式外,还提供了严格模式(strict mode)。ES5 的严格模式是采用具有限制性 JavaScript 变体的一种方式,即在严格的条件下运行 JS 代码。
严格模式在 IE10 以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略。
严格模式对正常的 JavaScript 语义做了一些更改:
- 消除了 Javascript 语法的一些不合理、不严谨之处,减少了一些怪异行为。
- 消除代码运行的一些不安全之处,保证代码运行的安全。
- 提高编译器效率,增加运行速度。
- 禁用了在 ECMAScript 的未来版本中可能会定义的一些语法,为未来新版本的 Javascript 做好铺垫。比如一些保留字如:class, enum, export, extends, import, super 不能做变量名。
- 开启严格模式
严格模式可以应用到整个脚本或个别函数中。因此在使用时,我们可以将严格模式分为为脚本开启严格模式和为函数开启严格模式两种情况。
为脚本开启严格模式:
为整个脚本文件开启严格模式,需要在所有语句之前放一个特定语句“use strict”;(或‘use strict’;)。
<script>"use strict";console.log("这是严格模式。");
</script>
因为"use strict"加了引号,所以老版本的浏览器会把它当作一行普通字符串而忽略。
有的 script 基本是严格模式,有的 script 脚本是正常模式,这样不利于文件合并,所以可以将整个脚本文件放在一个立即执行的匿名函数之中。这样独立创建一个作用域而不影响其他 script 脚本文件。
<script>(function (){"use strict";var num = 10;function fn() {}})();
</script>
为函数开启严格模式:
要给某个函数开启严格模式,需要把“use strict”; (或 'use strict'; ) 声明放在函数体所有语句之前。
function fn(){"use strict";return "这是严格模式。";
}
将 "use strict" 放在函数体的第一行,则整个函数以 "严格模式" 运行。
- 严格模式中的变化
变量规定:
- 在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,变量都必须先用var 命令声明,然后再使用。
- 严禁删除已经声明变量。例如,delete x; 语法是错误的。
this 指向问题:
- 以前在全局作用域函数中的 this 指向 window 对象。
- 严格模式下全局作用域中函数中的 this 是 undefined。
- 以前构造函数时不加 new也可以调用,当普通函数,this 指向全局对象。
- 严格模式下,如果 构造函数不加new调用, this 指向的是undefined 如果给他赋值则会报错。
- new 实例化的构造函数指向创建的对象实例。
- 定时器 this 还是指向 window 。
- 事件、对象还是指向调用者。
函数变化:
- 函数不能有重名的参数。
- 函数必须声明在顶层.新版本的 JavaScript 会引入“块级作用域”( ES6 中已引入)。为了与新版本接轨,不允许在非函数的代码块内声明函数。
十、高阶函数
高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出。
<script>
function fn(callback){callback&&callback();
}
fn(function(){alert('hi')}
</script>
<script>
function fn(){return function() {}
}fn();
</script>
- 此时fn 就是一个高阶函数。
- 函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用。 最典型的就是作为回调函数。
- 同理函数也可以作为返回值传递回来。
十一、闭包
- 变量作用域
变量根据作用域的不同分为两种:全局变量和局部变量。
- 函数内部可以使用全局变量。
- 函数外部不可以使用局部变量。
- 当函数执行完毕,本作用域内的局部变量会销毁。
- 什么是闭包
闭包(closure)指有权访问另一个函数作用域中变量的函数。 ----- JavaScript 高级程序设计
简单理解就是 ,一个作用域可以访问另外一个函数内部的局部变量。
function fn() {var num = 10;function fun() {console.log(num);}fun();
}
fn(); // 10
- 闭包的作用
闭包作用:延伸变量的作用范围。
function fn() {var num = 10;function fun() {console.log(num);}return fun;
}
var f = fn();
f(); // 10
// 类似于:
/* var f = function fun() {console.log(num);}
*/
十二、递归
- 什么是递归?
- 如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。
- 简单理解:函数内部自己调用自己, 这个函数就是递归函数。
- 递归函数的作用和循环效果一样。
- 由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件 return。
var num = 1;
function fn() {console.log('打印6句话');if (num == 6) {return;}num++;fn();
}
fn();
- 递归练习:
// 求 1 * 2 *3 ... * n 阶乘
function fn(n) {if (n == 1) {return 1;}return n * fn(n - 1);
}
console.log(fn(3)); // 6
// 解析:
// return 3 * fn(2);
// return 3 * (2 * fn(1));
// return 3 * (2 * 1);
// 求斐波那契数列 1、1、2、3、5、8、13、21...
function fb(n) {if (n === 1 || n === 2) {return 1;}return fb(n - 1) + fb(n - 2
}
console.log(fb(3)); // 2
- 浅拷贝和深拷贝
- 浅拷贝只是拷贝一层, 更深层次对象级别的只拷贝引用。
- 深拷贝拷贝多层, 每一级别的数据都会拷贝。
- Object.assign(target, ...sources) es6 新增方法可以浅拷贝。
浅拷贝:
var obj = {id: 1,name: 'andy',msg: {age: 18}
}
var o = {}// for (var k in obj) {
// o[k] = obj[k];
// }
// console.log(o); // {id: 1, name: "andy", msg: {age:18}}
// o.msg.age = 20;
// console.log(obj); // {id: 1, name: "andy", msg: {age:20}}Object.assign(o, obj);
console.log(o); // {id: 1, name: "andy", msg: {age:18}}
o.msg.age = 20;
console.log(obj); // {id: 1, name: "andy", msg: {age:20}}
深拷贝:
var obj = {id: 1,name: 'andy',msg: {age: 18},color: ['pink', 'red']
}
var o = {}
// 封装函数
function deepCopy(newobj, oldobj) {for (var k in oldobj) {// 判断属性值属于哪种类型// 1. 获取属性值 oldobj[k]var item = oldobj[k];// 2. 判断这个值是否为数组if (item instanceof Array) {newobj[k] = [];deepCopy(newobj[k], item)} else if (item instanceof Object) {// 3. 判断这个值是否是对象newobj[k] = {};deepCopy(newobj[k], item)} else {// 4. 属于简单数据类型newobj[k] = item;}}
}
deepCopy(o, obj);
console.log(o); // {id: 1, name: "andy", msg: {age:20}, color: Array(2)}
o.msg.age = 20;
console.log(obj); // {id: 1, name: "andy", msg: {age:18}, color: Array(2)}
十三、正则表达式
正则表达式( Regular Expression )是用于匹配字符串中字符组合的模式。在 JavaScript中,正则表达式也是对象。
正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本,例如验证表单:用户名表单只能输入英文字母、数字或者下划线, 昵称输入框中可以输入中文(匹配)。此外,正则表达式还常用于过滤掉页面内容中的一些敏感词(替换),或从字符串中获取我们想要的特定部分(提取)等 。
- 创建正则表达式
在 JavaScript 中,可以通过两种方式创建一个正则表达式。
通过调用 RegExp 对象的构造函数创建
var 变量名 = new RegExp(/表达式/);
通过字面量创建
var 变量名 = /表达式/;
// 注释中间放表达式就是正则字面量
- 测试正则表达式 test
test() 正则对象方法,用于检测字符串是否符合该规则,该对象会返回 true 或 false,其参数是测试字符串。
regexObj.test(str)
- regexObj 是写的正则表达式
- str 我们要测试的文本
- 就是检测str文本是否符合我们写的正则表达式规范
- 正则表达式的组成
一个正则表达式可以由简单的字符构成,比如 /abc/,也可以是简单和特殊字符的组合,比如 /ab*c/ 。其中特殊字符也被称为元字符,在正则表达式中是具有特殊意义的专用符号,如 ^ 、$ 、+ 等。
- 正则表达式中的特殊字符
边界符
正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符。
var reg = /^abc/;
console.log(reg.test('abc')); // true
console.log(reg.test('abcd')); // true
console.log(reg.test('aabcd')); // false
如果 ^ 和 $ 在一起,表示必须是精确匹配。
var reg = /^abc$/;
console.log(reg.test('abc')); // true
console.log(reg.test('abcd')); // false
console.log(reg.test('aabcd')); // false
console.log(reg.test('abcabc')); // false
字符类
字符类表示有一系列字符可供选择,只要匹配其中一个就可以了。所有可供选择的字符都放在方括号内。
[ ] 方括号
/[abc]/.test('andy') // true
后面的字符串只要包含 abc 中任意一个字符,都返回 true 。
// 只要包含有a 或者包含有b 或者包含有c 都返回为true
var rg = /[abc]/;
console.log(rg.test('andy')); // true
console.log(rg.test('baby')); // true
console.log(rg.test('color')); // true
console.log(rg.test('red')); // false
// 三选一 只有是a 或者是b 或者是c 这三个字母才返回true
var rg = /^[abc]$/;
console.log(rg.test('aa')); // false
console.log(rg.test('a')); // true
console.log(rg.test('b')); // true
console.log(rg.test('c')); // true
console.log(rg.test('abc')); // false
字符类
字符类表示有一系列字符可供选择,只要匹配其中一个就可以了。所有可供选择的字符都放在方括号内。
[-] 方括号内部 范围符 -
/^[a-z]$/.test(c') // true
方括号内部加上 - 表示范围,这里表示 a 到 z 26个英文字母都可以。
// 26个英文字母任何一个字母返回true - 表示的是a 到 z 的范围
var rg = /^[a-z]$/;
console.log(rg.test('a')); // true
console.log(rg.test('z')); // true
console.log(rg.test('h')); // true
console.log(rg.test('A')); // false
console.log(rg.test(1)); // false
字符组合
/[a-z1-9]/.test('andy') // true
方括号内部可以使用字符组合,这里表示包含 a 到 z 的26个英文字母和 1 到 9 的数字都可以。
// 26个英文字母(大写小写都可以)任何一个字母、下划线、短横线返回true
var rg = /^[a-zA-Z_-]$/;
console.log(rg.test('a')); // true
console.log(rg.test('z')); // true
console.log(rg.test('A')); // true
console.log(rg.test('Z')); // true
console.log(rg.test('_')); // true
console.log(rg.test('-')); // true
console.log(rg.test('!')); // false
[^] 方括号内部 取反符^
/[^abc]/.test('andy') // false
方括号内部加上 ^ 表示取反,只要包含方括号内的字符,都返回 false 。
注意和边界符 ^ 区别,边界符写到方括号外面。
// 中括号里面有^ 表示取反的意思 不能和边界符 ^ 混淆
var rg = /^[^a-zA-Z_-]$/;
console.log(rg.test('a')); // false
console.log(rg.test('z')); // false
console.log(rg.test('A')); // false
console.log(rg.test('Z')); // false
console.log(rg.test('_')); // false
console.log(rg.test('-')); // false
console.log(rg.test('!')); // true
量词符
量词符用来设定某个模式出现的次数。
// * 相当于 >=0 可以出现0次或者很多次
var reg = /^a*$/;
console.log(reg.test('')); // true
console.log(reg.test('a')); // true
console.log(reg.test('aaaa')); // true
// + 相当于 >=1 可以出现1次或者很多次
var reg = /^a+$/;
console.log(reg.test('')); // false
console.log(reg.test('a')); // true
console.log(reg.test('aaaa')); // true
// ? 相当于 1 || 0
var reg = /^a?$/;
console.log(reg.test('')); // true
console.log(reg.test('a')); // true
console.log(reg.test('aaaa')); // false
// {3} 就是重复3次
var reg = /^a{3}$/;
console.log(reg.test('')); // false
console.log(reg.test('a')); // false
console.log(reg.test('aaaa')); // false
console.log(reg.test('aaa')); // true
// {3,} 大于等于3
var reg = /^a{3,}$/;
console.log(reg.test('')); // false
console.log(reg.test('a')); // false
console.log(reg.test('aaaa')); // true
console.log(reg.test('aaa')); // true
// {3,6} 大于等于3 并且 小于等于6
var reg = /^a{3,6}$/;
console.log(reg.test('')); // false
console.log(reg.test('a')); // false
console.log(reg.test('aaaa')); // true
console.log(reg.test('aaa')); // true
console.log(reg.test('aaaaaaa')); // false
// 量词是设定某个模式出现的次数
// 这个模式用户只能输入英文字母 数字 下划线 短横线,但是边界符和[] 限定了只能多选一
var reg = /^[a-zA-Z0-9_-]{6,16}$/;
console.log(reg.test('andy-RU2')); // true
console.log(reg.test('baby_076')); // true
console.log(reg.test('78-h_Sd')); // true
console.log(reg.test('sic_43!kg')); // false
括号总结
- 大括号 量词符,里面表示重复次数
- 中括号 字符集合,匹配方括号中的任意字符
- 小括号 表示优先级
预定义类
预定义类指的是某些常见模式的简写方式。
- 正则表达式中的替换
replace() 方法可以实现替换字符串操作,用来替换的参数可以是一个字符串或是一个正则表达式。
stringObject.replace(regexp/substr,replacement)
- 第一个参数: 被替换的字符串 或者 正则表达式
- 第二个参数: 替换为的字符串
- 返回值是一个替换完毕的新字符串
var str = 'andy和jack';
var newStr = str.replace('andy', 'baby');
console.log(newStr); // baby和jack
- 正则表达式参数
/表达式/[switch]
switch(也称为修饰符) 按照什么样的模式来匹配,有三种值:
- g:全局匹配
- i:忽略大小写
- gi:全局匹配 + 忽略大小写
十四、ES6的新增语法
- let
let声明的变量只在所处于的块级有效
if (true) {let b = 20;console.log(b); // 20
}
console.log(b); // b is not defined
注意:使用let关键字声明的变量才具有块级作用域,使用var声明的变量不具备块级作用域特性。
let不存在变量提升
console.log(a); // Cannot access 'a' before initialization
let a = 100;
let暂时性死区
var num = 10;
if (true) {console.log(num);let num = 20;
}
- const
作用:声明常量,常量就是值(内存地址)不能变化的量。
具有块级作用域
if (true) {const a = 10;console.log(a); // 10
}
console.log(a); // a is not defined
声明常量时必须赋值
const PI; // Missing initializer in const declaration
常量赋值后,值不能修改
const PI = 3.14;
PI = 100; // Assignment to constant variable
const ary = [100, 200];
ary[0] = 'a';
ary[1] = 'b';
console.log(ary); // ["a", "b"]
ary = ["a", "b"]; // Assignment to constant variable
- let、const、var 的区别
- 使用 var 声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象。
- 使用 let 声明的变量,其作用域为该语句所在的代码块内,不存在变量提升。
- 使用 const 声明的是常量,在后面出现的代码中不能再修改该常量的值。
var |
let |
const |
函数级作用域 |
块级作用域 |
块级作用域 |
变量提升 |
不存在变量提升 |
不存在变量提升 |
值可更改 |
值可更改 |
值不可更改 |
- 解构赋值
ES6中允许从数组中提取值,按照对应位置,对变量赋值。对象也可以实现解构。
数组解构
let ary = [1, 2, 3];
let [a, b, c] = ary;
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
如果解构不成功,变量的值为undefined。
let [foo] = [];
console.log(foo); // undefinedlet [bar, info] = [1];
console.log(bar); // 1
console.log(info); // undefined
对象解构
let person = { name: '李四', age: 30, sex: '男' }
let { name, age, sex } = person;
console.log(name); // 李四
console.log(age); // 30
console.log(sex); // 男
let person = { name: '李四', age: 30, sex: '男' }
let { name: myName } = person;
console.log(myName); // 李四
- 箭头函数
() => {}
const fn = () => {}
函数体中只有一句代码,且代码的执行结果就是返回值,可以省略大括号。
const sum = (n1, n2) => n1 + n2;
const result = sum(10, 20);
console.log(result); // 30
如果形参只有一个,可以省略小括号
function fn (v) {return v;} const fn = v => v;
const fn = v => {console.log(v);
}
fn(20); // 20
箭头函数不绑定this关键字,箭头函数中的this,指向的是函数定义位置的上下文this。
function fn() {console.log(this); // {name: "zhangsan"}return () => {console.log(this); // {name: "zhangsan"}}
}
const obj = { name: 'zhangsan' }
const resFn = fn.call(obj);
resFn();
- 剩余参数
剩余参数语法允许我们将一个不定数量的参数表示为一个数组。
function sum(first, ...args) {console.log(first); // 10console.log(args); // [20, 30]
}
sum(10, 20, 30);
const sum = (...args) => {let total = 0;args.forEach(item => total += item)return total;
}
console.log(sum(10, 20)); // 30
console.log(sum(10, 20, 30)); // 60
- 剩余参数和解构配合使用
let arr = ['张三', '王五', '李四'];
let [s1, ...s2] = arr;
console.log(s1); // 张三
console.log(s2); // ["王五", "李四"]
- 扩展运算符(展开语法)
扩展运算符可以将数组或者对象转为用逗号分隔的参数序列。
let arr = ['a', 'b', 'c'];
console.log(...arr); // a b c
扩展运算符可以应用于合并数组。
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let arr = [...arr1, ...arr2];
console.log(arr); // [1, 2, 3, 4, 5, 6]
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
arr1.push(...arr2);
console.log(arr1); // [1, 2, 3, 4, 5, 6]
将类数组或可遍历对象转换为真正的数组
let oDivs = document.getElementsByTagName('div');
oDivs = [...oDivs];
构造函数方法:Array.from(),将类数组或可遍历对象转换为真正的数组
方法还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
var arrayLike = {'0': '1','1': '2','length': 2
}
var ary = Array.from(arrayLike, item => item * 2)
console.log(ary); // [2, 4]
实例方法:find()
用于找出第一个符合条件的数组成员,如果没有找到返回undefined
var ary = [{id: 1,name: '张三'
}, {id: 2,name: '李四'
}];
let target = ary.find(item => item.id == 2);
console.log(target); // {id: 2, name: "李四"}
实例方法:findIndex()
用于找出第一个符合条件的数组成员的位置,如果没有找到返回-1
let ary = [10, 20, 50];
let index = ary.findIndex(item => item > 15)
console.log(index); // 1
实例方法:includes()
表示某个数组是否包含给定的值,返回布尔值。
let ary = ['a', 'b', 'c'];
let result = ary.includes('a');
console.log(result); // true
result = ary.includes('e');
console.log(result); // false
- 模板字符串
ES6新增的创建字符串的方式,使用反引号定义。
let name = `zhangsan`;
模板字符串中可以解析变量。
let name = '张三';
let sayHello = `Hello,我的名字是${name}`;
console.log(sayHello); // Hello,我的名字是张三
模板字符串中可以换行。
let result = {name: '张三',age: 20
}
let html = `<div><span>${result.name}</span><span>${result.age}</span></div>
`;
console.log(html);
/*<div><span>张三</span><span>20</span></div>
*/
在模板字符串中可以调用函数。
const fn = () => {return '我是fn函数'
}
let html = `我是模板字符串${fn()}`;
console.log(html); // 我是模板字符串我是fn函数
- 实例方法:startsWith() 和 endsWith()
startsWith():表示参数字符串是否在原字符串的头部,返回布尔值
endsWith():表示参数字符串是否在原字符串的尾部,返回布尔值
let str = 'Hello ECMAScript 2015
let r1 = str.startsWith('Hello')
console.log(r1); // truelet r2 = str.endsWith('2016');
console.log(r2); // false
- 实例方法:repeat()
repeat方法表示将原字符串重复n次,返回一个新字符串。
console.log("y".repeat(5)); // yyyyy
- Set 数据结构
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
Set本身是一个构造函数,用来生成 Set 数据结构。
const s = new Set();
Set函数可以接受一个数组作为参数,用来初始化。
const set = new Set([1, 2, 3, 4, 4]);
size 在Set数据结构中存储的数据个数
const s1 = new Set();
console.log(s1.size); // 0const s2 = new Set(['a', 'b']);
console.log(s2.size); // 2
利用Set数据结构进行数组去重
const s3 = new Set(['a', 'a', 'b', 'b']);
console.log(s3.size); // 2
const ary = [...s3];
console.log(ary); // ["a", "b"]
实例方法
- add(value):添加某个值,返回 Set 结构本身
- delete(value):删除某个值,返回一个布尔值,表示删除是否成功
- has(value):返回一个布尔值,表示该值是否为 Set 的成员
- clear():清除所有成员,没有返回值
遍历
Set 结构的实例与数组一样,也拥有forEach方法,用于对每个成员执行某种操作,没有返回值。
s.forEach(value => console.log(value))
// 遍历Set数据结构,从中取值
const s5 = new Set(['a', 'b', 'c']);
s5.forEach(value => {console.log(value); // a b c
})
JavaScript高级+ES6相关推荐
- JavaScript高级程序设计之基本概念篇
日子天天过,啥都能忘,学习不能忘.<JavaScript高级程序设计>这本书之前看过,但是只看了js的部分,其余部分并没有看.当时想,看看js部分应该够用了,但最近看了篇文章,受到了很深的 ...
- 读书笔记(06) - 语法基础 - JavaScript高级程序设计
写在开头 本篇是小红书笔记的第六篇,也许你会奇怪第六篇笔记才写语法基础,笔者是不是穿越了. 答案当然是没有,笔者在此分享自己的阅读心得,不少人翻书都是从头开始,结果永远就只在前几章. 对此,笔者换了随 ...
- javascript 高级程序设计_重读《JavaScript高级程序设计》
最近自己在休假,打算闭门几天将<JavaScript高级程序设计>(第3版)这本良心教材再回顾一遍.目前自己进入前端领域两年多,现在重读并记录下这本教材的"硬"知识点 ...
- JavaScript高级之函数进阶
1. 函数的定义和调用 1.1 函数的定义方式 函数声明方式 function 关键字 (命名函数) 函数表达式 (匿名函数) new Function() Function 里面参数都必须是字符串格 ...
- 《JavaScript高级程序设计》红宝书第二遍阅读(动手实践)
<JavaScript高级程序设计>红宝书第二遍阅读(动手实践) 第1章--什么是JavaScript 第2章--HTML中的JavaScript 第3章--语言基础 第4章--变量.作用 ...
- 《JavaScript高级程序设计》红宝书第一遍阅读(了解概念)
<JavaScript高级程序设计>红宝书第一遍阅读(了解概念) 第1章--什么是JavaScript 第2章--HTML中的JavaScript 第3章--语言基础 第4章--变量.作用 ...
- javaScript高级[二]
javaScript高级[二] 函数 函数的定义和调用 函数的定义方式 函数的调用方式 this 函数内this指向 改变函数内部this指向 call()方法 apply()方法 bind()方法 ...
- JavaScript|JavaScript 高级语法——详细汇总
JavaScript 高级语法 目录 JavaScript 高级语法 一.变量提升和函数提升 作用域的概念 1. 变量提升 ① 变量提升 ② 变量提升后,与外界同名变量不会相互影响 ③ 多次声明变量 ...
- JavaScript 高级编程(二)
JavaScript 高级编程(二) BOM 一套操作浏览器的API. 常见对象 window: 代表整个浏览器窗口 注意: window是BOM中的一个对象, 并且是一个顶级的对象(全局) Navi ...
最新文章
- git在已忽略文件夹中不忽略指定文件
- hibernate lazy加载
- 从零单排学Redis【青铜】
- 【C#】详解使用Enumerable.Distinct方法去重
- java中apache安装与配置_Apache应用服务器之四:Apache与Tomcat安装与配置
- SAP ABAP刷新ALV 渲染刷新 (我也不太懂,反正就这么写了)
- php多表递归查询,使用公用表表达式的递归查询
- windows服务编写原理(下)
- 解决chrome在docky上的图标模糊或不能锁定的问题
- VS调试c++动态库最简单最高效的方法
- CSS z-index 属性 控制div上下层次
- fork函数结果分析
- vtk世界坐标系与屏幕坐标系的转换
- 软件测试分类、分级与软件缺陷管理
- Oracle-SQL语句的逻辑读怎么计算
- Python找最大数及位置
- 转:浙大高分子物理郑强教授的震撼人心的演讲
- ***详解账号泄露:全球约1亿用户已泄露
- 使用Markdown追溯产品文档历史版本
- 从面试到入职到离职,我在B站工作的30天时光!!!
热门文章
- 怎么屏蔽还有照片_有一种尴尬叫:在朋友圈发照片,忘了屏蔽父母,老妈的回应亮了…...
- 佳能Canon imagePROGRAF iPF8410 打印机驱动
- Android12之aidl与hal直通(四十八)
- 农业生产适宜性评价之气候资源评价算法
- python 基金量化分析_「Python量化」怎么在基金定投上实现收益最大化
- html表格边框void,绘制HTML表格边框
- 电脑颜色 (视力保护的最佳选择) 什么颜色对眼睛最好?
- STM8内部的MVR是什么?
- plt.style.use设置背景样式
- 凌云架构V0.1——序