提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
本科是计算机专业的,毕设选了一个前端相关的,于是考研结束开始接触前端,最开始是在网上看视频自学,基本就是三天打鱼两天晒网,效率不高,考研之后放弃调剂,来到了成都华清远见开始了系统地前端学习,目前到了JavaScript结束阶段,就分享一下自己的学习笔记!
提示:以下是本篇文章正文内容,下面案例可供参考
2019全新javaScript进阶面向对象ES6
面向过程 编程,即POP(Process-oriented programming)。面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了。
面向对象 编程,即 OOP(Object Oriented Programming) 面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作。
在面向对象程序开发思想中,每—个对象都是功能中心,具有明确分工。 面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目。
面向对象的特性:
优点:
性能比面向对象高,适合跟硬件
联系很紧密的东西,例如单片机就采用的面向过程编程。
缺点:
没有面向对象易维护、易复用、易扩展。
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
缺点:性能比面向过程低。
现实生活中:万物皆对象,对象是 一个具体的事物,看得见摸得着的实物。例如,一本书、一辆汽车、一个人可以是“对象”,一个数据库、一张网页、一个与远程服务器的连接也可以是“对象”。
在 JavaScript 中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,例如字符串、数值、数组、函数等。
对象是由属性和方法组成的:
在 ES6 中新增加了类的概念,可以使用 class 关键字声明—个类,之后以这个类来实例化对象。
语法:
class ClassName {
// class body}
创建实例:
let obj = new ClassName();
Warning
类必须使用 new 实例化对象
constructor() 方法是类的构造函数(默认方法),用于传递参数,返回实例对象,通过new命令生成对象实例时,自动调用该方法。如果没有显示定义,类内部会自动给我们创建一个 constructor()。
语法:
// 创建一个学生类class Student {
constructor(uname, age, major) {
this.uname = uname;
this.age = age;
this.major = major;
}}
类的实例化——创建对象
let peter = new Student("Peter", 21, "CS");
console.log(peter.uname); // Peter
注意:
class Student {
constructor(uname, age, major) {
this.uname = uname;
this.age = age;
this.major = major;
}
// 类中添加方法
sing() {
console.log(this.uname + "会唱歌");
}}
创建实例:
let peter = new Student("Peter", 18, "化学");
peter.sing(); // Peter会唱歌
Warning
方法之间不能加逗号分隔,同时方法不需要添加 function 关键字。
给成员属性或成员方法添加 static,该成员就成为静态成员,静态成员只能由该类调用。
class Person {
static eat() {
console.log('eat');
}}let p = new Person();
Person.eat(); // eat
p.eat(); // 报错
实际上,getter 和 setter 是 ES5(ES2009)提出的特性,这里不做详细说明,只是配合 class 使用举个例子。
当属性拥有 get/set 特性时,属性就是访问器属性。代表着在访问属性或者写入属性值时,对返回值做附加的操作。而这个操作就是 getter/setter 函数。
使用场景: getter 是一种语法,这种 get 将对象属性绑定到 查询该属性时将被调用的函数。适用于某个需要动态计算的成员属性值的获取。setter 则是在修改某一属性时所给出的相关提示。
class Test {
constructor(log) {
this.log = log;
}
get latest() {
console.log('latest 被调用了');
return this.log;
}
set latest(e) {
console.log('latest 被修改了');
this.log.push(e);
}}
let test = new Test(['a', 'b', 'c']);// 每次 log 被修改都会给出提示
test.latest = 'd';// 每次获取 log 的最后一个元素 latest,都能得到最新数据。
console.log(test.latest);
以上输出:
latest 被修改了
latest 被调用了
[ 'a', 'b', 'c', 'd' ]
语法:使用 extends 关键字。
class Son extends Father {
// class body}
super 关键字用于访问和调用对象父类上的函数。可以调用父类的构造函数,也可以调用父类的普通函数。
语法:
super([arguments]);// 调用 父对象/父类 的构造函数
super.functionOnParent([arguments]);// 调用 父对象/父类 上的方法
示例:
class Person {
constructor (uname, age) {
this.uname =uname;
this.age = age;
}}class Student extends Person {
constructor (uname, age, major) {
// super 将子类的参数传递给父类构造函数,减少代码量
super(uname, age);
// 子类可以有自己独有的属性
this.major = major;
}}let rick = new Student("Rick", 22, "数学");
Warning
注意: 子类在构造函数中使用 super, 必须放到 this 前面(必须先调用父类的构造方法,再使用子类构造方法)
观察以下代码,运行将产生错误。
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) {
this.x = this.x;
this.y = this.y;
}}let obj = new Son(10, 20);
obj.sum();
解释说明:若子类没有写构造函数 constructor,则实例化时默认调用父类的,这时候程序运行无误。若子类写了构造函数,那么子类在调用 sum 方法的时候,参数的值没有传给父类,父类无法调用参数的值,也就无法执行 sum 方法。
正确:加入 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);
}}let obj = new Son(10, 20);
obj.sum(); // 30
class Parent {
sayHi() {
return "Father: hello";
}
}
class Child extends Parent {
sayHi() {
// super 调用父类普通函数
console.log(super.sayHi());
}}
let man = new Child();
man.sayHi(); // Father: hello
继承中的属性或者方法查找原则:就近原则
class Parent {
sayHi() {
console.log("Father: hello");
}}
class Child extends Parent {
sayHi() {
console.log("Son: hello");
}}
let man = new Child();
man.sayHi(); // Son: hello
子类在构造函数中使用 super, 必须放到 this 前面。
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}}
class Son extends Father {
constructor(x, y, z) {
super(x, y, z);
this.z = this.z;
}}
let obj = new Son();
let that;
class Star {
constructor (uname, age) {
that = this;
this.uname = uname;
this.age = age;
// btn按钮调用sing方法
this.btn = document.querySelector("button");
this.btn.onclick = this.sing;
// constructor 里面的this 指向的是 创建的实例对象
console.log("constructor: ", this);
}
sing() {
// 这个sing方法里面的 this 指向的是 btn 这个按钮,因为这个按钮调用了这个函数
console.log("sing:", this); // button
console.log(that.uname); // that里面存储的是constructor里面的this
}
dance() {
// 这个dance里面的this 指向的是实例对象 ldh 因为ldh 调用了这个函数
console.log("dance:", this);
}}
let rick = new Star("Rick", 20);
rick.dance();
Tip
我用阿里云盘分享了「09-面向对象案例」,你可以不限速下载
https://www.aliyundrive.com/s/p6YYAbggHpU
class Tab {
constructor(id) {
// 获取相关元素节点
// 根据传入的id选择器构建主节点
this.main = document.querySelector(id);
}
// 初始化,绑定各个事件
init() {}
// 更新节点,同步整个状态
updateNode() {}
// 1. 切换功能
toggleTab() {}
// 2. 添加功能
addTab() {}
// 3. 删除功能
removeTab() {}
// 4. 修改功能
editTab() {}}
bash window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
user-select: none;
在典型的 OOP 的语言中(如 Java),都存在类的概念,类就是对象的模板,对象就是类的实例,但在 ES6之前, JS 中并没用引入类的概念。
ES6, 全称 ECMAScript 6.0 ,2015.06 发版。但是目前浏览器的 JavaScript 是 ES5 版本,大多数高版本的浏览器也支持 ES6,不过只实现了 ES6 的部分特性和功能。 在 ES6 之前 ,对象不是基于类创建的,而是用一种称为 构建函数 的特殊函数来定义对象和它们的特征。
创建对象可以通过以下三种方式:
1.对象字面量
2.new Object()
3.自定义构造函数
构造函数 是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与 new 一起使用。我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面。
在 JS 中,使用构造函数时要注意以下两点:
1.构造函数用于创建某一类对象,其首字母要大写
2.构造函数要和 new 一起使用才有意义
new 在执行时会做四件事情:
1.在内存中创建一个新的空对象。
2.让 this 指向这个新的对象。
3.执行构造函数里面的代码,给这个新对象添加属性和方法。
4.返回这个新对象(所以构造函数里面不需要 return)。
JavaScript 的构造函数中可以添加一些成员,可以在构造函数本身上添加,也可以在构造函数内部的 this 上添加。通过这两种方式添加的成员,就分别称为 静态成员 和 实例成员。
1. 静态成员:在构造函数本上添加的成员称为 静态成员,只能由构造函数本身来访问
2. 实例成员:在构造函数内部创建的对象成员称为 实例成员,只能由实例化的对象来访问
function Human(uname, age) {
this.uname = uname;
this.age = age;}
Human.x = 10; // 静态成员let rick = new Human("rick", 35);// console.log(rick.x); // 实例化的对象不能调用静态成员
console.log(Human.x); // 静态成员只能由构造函数本身来访问
构造函数方法很好用,但是 存在浪费内存的问题。
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function() {
console.log('我会唱歌');
}}
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
函数内容一样的函数(sing()),会在内存中产生两份,占两份空间。 我们希望 所有的对象使用同一个函数,这样就比较节省内存,于是就有了构造函数原型 prototype。
构造函数通过原型分配的函数是所有对象所 共享的。
JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象。注意这个 prototype 就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。
我们可以 把那些不变的方法,直接定义在 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);
console.log(ldh.sing === zxy.sing); // true
上述代码将输出 true,因为 sing 方法已被共享。
对象都会有一个属性 proto 指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 proto 原型的存在。
proto 对象原型和构造函数的原型对象 prototype 是等价的。
proto 对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是 它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype。
Warning
Object.prototype.proto 已废弃: 该特性已经从 Web 标准中删除,虽然一些浏览器目前仍然支持它,但也许会在未来的某个时间停止支持,请尽量不要使用该特性。为了更好的支持,建议只使用 Object.getPrototypeOf()。 ——MDN
以后要得到某实例对象的原型,尽量使用 Object.getPrototypeOf()。
案例:
function Human(uname, age) {
this.uname = uname;
this.age = age;}
Human.prototype.sayHi = function () {
console.log("Hey");}
let rick = new Human('rick', 35);
let jack = new Human('jack', 33);
console.log(Object.getPrototypeOf(rick) === Human.prototype); // true
console.log(Object.getPrototypeOf(rick) === rick.__proto__); // true
对象原型(proto)和构造函数(prototype)原型对象里面都有一个属性 constructor 属性 ,constructor 我们称为构造函数,因为它指回构造函数本身。 constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
一般情况下,对象的方法都在构造函数的原型对象中设置。如果有多个对象的方法,我们可以 给原型对象采取对象形式赋值,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了。此时,我们 可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。
function Human(uname, age) {
this.uname = uname;
this.age = age;
}
Human.prototype = {
// 添加一个 constructor 指向原来的构造函数
constructor: Human,
sayHi: function () {
console.log("Hey!");
},
eat: function () {
console.log("Eat something.");
}}
let rick = new Human('rick', 35);
console.log(Object.getPrototypeOf(rick).constructor); // Human(uname, age) {...}
关于 实例对象能指向构造函数的解释:实例对象通过 ldh.proto (Obj.getPrototypeOf(ldh))指向原型 prototype,而 prototype 能通过 prototype.constructor 指向原来的构造函数。
构造函数中的 this 指向我们实例对象. 原型对象里面放的是方法, 这个方法里面的 this 指向的是这个方法的调用者, 也就是这个实例对象。
可以通过原型对象,对原来的内置对象进行扩展自定义的方法。比如给数组增加自定义求偶数和的功能。
Tip
//注意:数组和字符串内置对象不能给原型对象覆盖操作 Array.prototype = {} ,只能是 Array.prototype.xxx = function(){} 的方式。
//举例:给数组增加自定义求偶数和的功能。
// !不能写成对象字面量形式Array.prototype.sumOfEven = function () {
let sum = 0;
for (let i = 0; i < this.length; i++) {
// 位运算判断奇偶
if (!(this[i]&1)) {
sum += this[i];
}
}
return sum;}
let arr = [1,2,3,4,5,6];
console.log(arr.sumOfEven());
ES6之前并没有给我们提供 extends 继承。我们可以通过 构造函数+原型对象 模拟实现继承,被称为 组合继承。
功能:调用这个函数, 并且修改函数运行时的 this 指向。
fun.call(thisArg, arg1, arg2, …)
function Foo(uname, age) {
this.uname = uname;
this.age = age;
console.log(this);}
let obj = {x: 1};
Foo.call(obj, 'rick', 30);
以上代码输出:{x: 1, uname: ‘rick’, age: 30},说明函数内部指向改变为了指向 obj。
核心原理: 通过 call() 把父类型的 this 指向子构造函数的 this ,这样就可以实现子继承父的属性。
// 父类function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;}// 子类
function Student(name, age, sex, score) {
// 此时父类的 this 指向子类的 this,同时调用这个函数
Person.call(this, name, age, sex);
this.score = score;}
var s1 = new Student('zs', 18, '男', 100);
console.log(s1);
输出如下,说明子类成功通过 call 方法继承了父类的属性。
有了上述方法还不够,一般不能继承父类的方法,因为 一般情况下,对象的方法都在构造函数的原型对象中设置,通过构造函数无法继承父类方法。
这个时候 原型对象 就起了作用。我们可以令子类构造函数的原型对象等于父类构造函数的原型对象,这样子类也可以使用父类构造函数的原型对象上的成员方法了。即:
childFoo.prototype = parentFoo.prototype;
但是这样又产生了一个问题:这样指定之后,子类构造函数和父类构造函数的对象原型 prototype 就 指向同一个内存地址了。也就是说,你在子类原型对象上绑定其特有的成员方法,父类上也会有,显然这是不合理的。
解决方法:利用父类的实例对象。核心原理:
1.将子类所共享的方法提取出来,然后让:
子类的 prototype 原型对象 = new 父类();
2.本质:子类原型对象等于是实例化父类,因为父类实例化之后 另外开辟空间,就不会影响原来父类原型对象
3.将子类的 constructor 重新指向子类的构造函数
举例:
// 父构造函数
function Human(uname, age) {
this.uname = uname;
this.age = age;
}
// 父构造成员方法
Human.prototype.eat = function () {
console.log("eat something");}
// 子构造函数
function Student(uname, age, major) {
Human.call(this, uname, age);
this.major = major;}
// 创建实例对象,将子类原型对象指向实例对象
Student.prototype = new Human();// 将子类的 constructor 重新指向子类的构造函数
Student.prototype.constructor = Student;// 子构造函数特有成员方法Student.prototype.exam = function () {
console.log("I have exams");}
let jack = new Student('Jack', 20, 'Math');
jack.eat();
jack.exam();
console.log(Human.prototype);
运行上述代码,观察(最后一行输出)到父类的原型对象上没有 exam 成员方法。
回忆下之前学的,在 ES5 之前通过 构造函数 + 原型 实现面向对象编程。其中,这种面向对象有这些特点:
class Foo { };
let foo = new Foo();
foo.__proto__ = {
constructor: Foo,
test1: function () {
console.log('test1');
},
test2: function () {
console.log('test2');
}}
console.log(Foo.prototype);
console.log(foo.__proto__);
console.log(Foo.prototype == foo.__proto__);
foo.test1();
foo.test2();
console.log(foo.__proto__.constructor.prototype.__proto__.__proto__);
ES5 中给我们新增了一些方法,可以很方便的操作数组或者字符串,这些方法主要包括:
迭代(遍历)方法:forEach()、map()、filter()、some()、every()
forEach 方法用于遍历数组,不对原数组进行修改。
array.forEach(function(currentValue, index, arr), thisArg);
该方法接收一个函数 function 参数,其中,该函数内又有三个参数:
array.forEach(function(currentValue));
map() 方法遍历一个数组,首先创建一个新数组,新数组中的每个元素是是调用一次所提供的函数参数后的返回值,然后 返回这个新数组。
let newArray = array.map(function (currentValue, index, arr));
map 方法接收一个 function 参数,该函数有三个参数:
array.filter(function(currentValue, index, arr));
filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素,主要 用于筛选数组,注意它 返回一个新数组。
array.some(function(currentValue, index, arr));
some() 方法用于检测数组中的元素是否满足指定条件。通俗点:查找数组中是否有满足条件的元素。 注意 它返回值是布尔值,如果查找到这个元素,就返回 true,如果查找不到就返回 false。
every() 返回值是布尔值 数组中的每一个元素必须满足条件
一个不满足条件直接返回fasle 全真为真,一假全假
arr.every(function(ele,index,arr){})
ele表示数组中的每一项元素
index表示数组中元素的下标
arr表示原数组
var arr = ['red', 'green', 'blue', 'pink'];
// 1. forEach迭代 遍历
// arr.forEach(function(value) {
// if (value == 'green') {
// console.log('找到了该元素');
// return true; // 在forEach 里面 return 不会终止迭代
// }
// console.log(11);
// })
// 如果查询数组中唯一的元素, 用some方法更合适,
arr.some(function(value) {
if (value == 'green') {
console.log('找到了该元素');
return true; // 在some 里面 遇到 return true 就是终止遍历 迭代效率更高
}
console.log(11);
});
// arr.filter(function(value) {
// if (value == 'green') {
// console.log('找到了该元素');
// return true; // // filter 里面 return 不会终止迭代
// }
// console.log(11);
// });
trim() 方法会从一个字符串的两端删除空白字符。
str.trim()
trim() 方法并不影响原字符串本身,它返回的是一个新的字符串。
Object.keys() 用于获取对象自身所有的属性,返回一个数组。
Object.keys(obj)
Object.defineProperty() 定义对象中新属性或修改原有的属性。
Object.defineProperty(obj, prop, descriptor)
参数:
Object.defineProperty() 第三个参数 descriptor 说明: 以对象形式 { } 书写。
// descriptor 对象的参数值均为默认值
Object.defineProperty(obj, prop, {
value: undefined,
writable: false,
enumerable: false,
configurable: false})
函数声明方式 function 关键字 (命名函数)
函数表达式 (匿名函数):
let func = function() {};
let fn = new Function('a', 'b', 'console.log(a + b);');
调用方式 | this 指向 |
---|---|
普通函数 | window |
构造函数 | 实例对象,原型对象里面的方法也指向实例对象 |
对象的方法 | 该方法所属对象 |
绑定事件函数 | 绑定事件对象 |
定时器函数 | window |
立即执行函数 | window |
JavaScript 为我们专门提供了一些函数方法来帮我们更优雅的处理函数内部 this 的指向问题,常用的有 bind()、call()、apply() 三种方法。
call() 方法调用一个对象。简单理解为调用函数的方式,但是它可以改变函数的 this 指向。
fun.call(thisArg, arg1, arg2, ...);
参数说明:
apply() 方法调用一个函数。简单理解为调用函数的方式,但是它可以改变函数的 this 指向。
fun.apply(thisArg, [argsArray])
案例:apply + Math.max()求数组的最大值:
let arr = [-10, 2, 12, 3, 1];
let max = Math.max.apply(Math, arr);
console.log(max); // 12
bind() 方法不会调用函数。但是能改变函数内部 this 指向。
let fn = fun.bind(thisArg, arg1, arg2, ...)
var o = {
name: 'andy'
};
function fn(a, b) {
console.log(this);//object
console.log(a + b);//3
};
var f = fn.bind(o, 1, 2);
f();
// 1. 不会调用原来的函数 可以改变原来函数内部的this 指向
// 2. 返回的是原函数改变this之后产生的新函数
// 3. 如果有的函数我们不需要立即调用,但是又想改变这个函数内部的this指向此时用bind
// 4. 我们有一个按钮,当我们点击了之后,就禁用这个按钮,3秒钟之后开启这个按钮
// var btn1 = document.querySelector('button');
// btn1.onclick = function() {
// this.disabled = true; // 这个this 指向的是 btn 这个按钮
// // var that = this;
// setTimeout(function() {
// // that.disabled = false; // 定时器函数里面的this 指向的是window
// this.disabled = false; // 此时定时器函数里面的this 指向的是btn
// }.bind(this), 3000); // 这个this 指向的是btn 这个对象
// }
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;
//如果btn[i].disabled=false;会出错,for一直执行,定时器不执行,for执行完之//后i=4,这里没有btn[4]所以会报错
}.bind(this), 2000);
}
}
常用***
相同点:都可以改变函数内部的 this 指向
区别点:
JavaScript 除了提供正常模式外,还提供了 严格模式(strict mode)。ES5 的严格模式是采用具有限制性 JavaScript 变体的一种方式,即在严格的条件下运行 JS 代码。
严格模式在 IE10 以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略。
严格模式对正常的 JavaScript 语义做了一些更改:
严格模式可以应用到整个脚本或个别函数中。因此在使用时,我们可以将严格模式分为为脚本开启严格模式和为函数开启严格模式两种情况。
为整个脚本文件开启严格模式,需要在所有语句之前放一个特定语句:在所有语句之前放一个特定语句 “use strict”;(或’use strict’;)
<script>
'use strict';
console.log('严格模式已开启');<script/>
因为 “use strict” 加了引号,所以老版本的浏览器会把它当作一行普通字符串而忽略。
有的 script 基本是严格模式,有的 script 脚本是正常模式,这样不利于文件合并,所以可以将整个脚本文件放在一个立即执行的匿名函数之中。这样独立创建一个作用域而不影响其他 script 脚本文件。
<script>
(function (){
"use strict";
var num = 10;
})();
</script>
要给某个函数开启严格模式,需要把 “use strict”;(或 ‘use strict’;)声明放在函数体所有语句之前。
将 “use strict” 放在函数体的第一行,则整个函数以 “严格模式” 运行。
function fn(){
"use strict";
return "这是严格模式。";}function foo() {
console.log("这不是严格模式");}
严格模式对 Javascript 的语法和行为,都做了一些改变。
函数不能有重名的参数。
函数必须声明在顶层。新版本的 JavaScript 会引入 “块级作用域”( ES6 中已引入)。为了与新版本接轨,不允许在非函数的代码块内声明函数。
更多严格模式要求参考点击
高阶函数是对其他函数进行操作的函数,它 接收函数作为参数 或 将函数作为返回值输出。
举例1:
function fn(callback){
callback&&callback();}fn(function(){alert('hi')}
举例2:
function fn(){
return function() {}}
fn();
此时 fn 就是一个高阶函数。
函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用。最典型的就是作为回调函数。
同理函数也可以作为返回值传递回来
变量根据作用域的不同分为两种:全局变量和局部变量。
1.函数内部可以使用全局变量。
2.函数外部不可以使用局部变量。
3.当函数执行完毕,本作用域内的局部变量会销毁。
闭包(closure)指有权访问另一个函数作用域中变量的函数。(–JavaScript 高级程序设计)
简单理解就是,一个作用域可以访问另外一个函数内部的局部变量。
function fn1() {
var s = "hello"; // x 是一个被 fn1 创建的局部变量
function fn2() { // fn2() 是内部函数,一个闭包
console.log(s); // 使用了父函数中声明的变量
}
fn2();}
fn1();
1.打开浏览器,按 F12 键启动 chrome 调试工具。
2.设置断点。
3.找到 Scope 选项(Scope 作用域的意思)。
4.当我们重新刷新页面,会进入断点调试,Scope 里面会有两个参数(global 全局作用域、local 局部作用域)。
5.当执行到 fn2() 时,Scope 里面会多一个 Closure 参数 ,这就表明产生了闭包。
5.4 闭包的作用
如何在 fn() 函数外面访问 fn() 中的局部变量 num ?
// 闭包(closure)指有权访问另一个函数作用域中变量的函数。
// 一个作用域可以访问另外一个函数的局部变量
// 我们fn 外面的作用域可以访问fn 内部的局部变量
// 闭包的主要作用: 延伸了变量的作用范围
function fn() {
var num = 10;
// function fun() {
// console.log(num);
// }
// return fun;
return function() {
console.log(num);
}
}
var f = fn();
f();
// 类似于
// var f = function() {
// console.log(num);
// }
// var f = function fun() {
// console.log(num);
// }
闭包作用:延伸变量的作用范围。
Warning
以下案例都是在ES5前提下,所以没有提及 let、const
有以下节点:
<ol>
<li>Banana</li>
<li>Apple</li>
<li>Peach</li>
</ol>
循环注册 “点击li输出当前li索引号”。
错误示例:
for (var i = 0; i < lis.length; i++) {
lis[i].onclick = function () {
console.log(i);
// 一直输出最后一个的索引号
}}
方案1:设置 index 属性
for (var i = 0; i < lis.length; i++) {
lis[i].index = i;
lis[i].onclick = function () {
console.log(this.index);
}}
方案2:闭包(面试经典提)
// 2. 利用闭包的方式得到当前小li 的索引号
for (var i = 0; i < lis.length; i++) {
// 利用for循环创建了4个立即执行函数
// 立即执行函数也成为小闭包因为立即执行函数里面的任何一个函数都可以使用它的i这变量
(function(i) {
// console.log(i);
lis[i].onclick = function() {
console.log(i);
}
})(i);
}
for (var i = 0; i < lis.length; i++) {
(function (i) {
setTimeout(function () {
console.log(lis[i].innerHTML);
}, 3000);
})(i);}
问题: 闭包应用-计算打车价格
var car = (function() {
var start = 13; // 起步价 局部变量
var total = 0; // 总价 局部变量
return {
// 正常的总价(闭包)
price: function(n) {
if (n <= 3) {
total = start;
} else {
total = start + (n - 3) * 5
}
return total;
},
// 拥堵之后的费用(闭包)
yd: function(flag) {
return flag ? total + 10 : total;
}
}})();
如果 一个函数在内部可以调用其本身,那么这个函数就是 递归函数。
简单理解:函数内部自己调用自己, 这个函数就是递归函数。
递归函数的作用和循环效果一样。
由于递归很容易发生 “栈溢出” 错误(stack overflow),所以必须要加退出条件 return。
let factorial = function (n) {
if (n == 0 || n == 1) return 1;
return n * factorial(n - 1);}
let fibonacci = function (n) {
if (n < 2) return n;
return fibonacci(n - 1) + fibonacci(n - 2);};
正则表达式( Regular Expression )是用于匹配字符串中字符组合的模式。在 JavaScript 中,正则表达式也是对象。
正则表通常被用来检索、替换那些符合某个模式(规则)的文本,例如验证表单:用户名表单只能输入英文字母、数字或者下划线, 昵称输入框中可以输入中文(匹配)。此外,正则表达式还常用于过滤掉页面内容中的一些敏感词(替换),或从字符串中获取我们想要的特定部分(提取)等 。
其他语言也会使用正则表达式,本阶段我们主要是利用 JavaScript 正则表达式完成表单验证。
1.灵活性、逻辑性和功能性非常的强。
2.可以迅速地用极简单的方式达到字符串的复杂控制。
3.对于刚接触的人来说,比较晦涩难懂。比如: ^\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)*$
4.实际开发,一般都是直接复制写好的正则表达式。但是要求会使用正则表达式并且根据实际情况修改正则表达式。比如用户名:/1{3,16}$/
在 JavaScript 中,可以通过两种方式创建一个正则表达式。
通过调用 RegExp 对象的构造函数创建
var regexp = new RegExp(/表达式/);
通过字面量创建
var regexp = /表达式/;
test() 正则对象方法,用于检测字符串是否符合该规则,该对象会返回 true 或 false,其参数是测试字符串。
regexObj.test(str);
一个正则表达式可以 由简单的字符构成,比如 /abc/,也可以是 简单和特殊字符的组合,比如 /ab*c/ 。其中特殊字符也被称为 元字符,在正则表达式中是具有特殊意义的专用符号,如 ^ 、$ 、+ 等。
特殊字符非常多,可以参考:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions
http://tool.oschina.net/regex
这里我们把元字符划分几类学习。
正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符。
边界符 说明
^ 表示匹配行首的文本(以谁开始)
$ 表示匹配行尾的文本(以谁结束)
如果 ^ 和 $ 在一起,表示必须是 精确匹配(不能多不能少,只能是这些)。
let regexp = /^he$/;
console.log(regexp.test('hello')); // flase
console.log(regexp.test('he')); // true
字符类表示有一系列字符可供选择,只要匹配其中一个就可以了。所有可供选择的字符都放在方括号内。
/[abc]/.test('andy')// true
正则含义:后面的字符串只要包含 abc 中任意一个字符,都返回 true。
/^[a-z]$/.test('c')// true
含义:方括号内部加上 - 表示范围,这里表示 a 到 z 26个英文字母都可以。
/[^abc]/.test('andy')// false
方括号内部加上 ^ 表示 取反,只要包含方括号内的字符,都返回 false 。
Warning
注意和边界符 ^ 区别,边界符写到方括号外面。
/[a-z1-9]/.test('andy')// true
方括号内部可以使用字符组合,这里表示包含 a 到 z 的 26 个英文字母和 1 到 9 的数字都可以。
量词符用来设定 某个模式出现的次数。
量词 | 说明 |
---|---|
* | 重复次数 ≥ 0 |
+ | 重复次数 ≥ 1 |
? | 重复 0 次或 1 次 |
{n} | 重复 n 次 |
{n,} | 重复次数 ≥ n |
{n,n} | 重复 n 次到 m 次 |
https://c.runoob.com/
预定类 | 说明 |
---|---|
\d | 匹配 0-9 之间的任一数字,相当于 [0-9] |
\D | 匹配所有 0-9 以外的字符,相当于 [^0-9] |
\w | 匹配任意的字母、数字和下划线,相当于 [A-Za-z0-9_] |
\W | 除所有字母、数字和下划线以外的字符,相当于 [^A-Za-z0-9_] |
\s | 匹配空格(包括换行符、制表符、空格符等),相等于[\t\r\n\v\f] |
\S | 匹配非空格的字符,相当于 [^\t\r\n\v\f] |
replace() 方法可以实现替换字符串操作,用来替换的参数可以是一个字符串或是一个正则表达式。
stringObject.replace(regexp/substr,replacement)
当 replace 中第一个参数为正则表达式的时候,还有一个 switch 参数可选。
/表达式/[switch]
switch(也称为修饰符)按照什么样的模式来匹配. 有三种值:
let msg = 'what\'s the fuck? Damn it!';
msg = msg.replace(/fuck|damn/gi, '****');
console.log(msg); // what's the ****? **** it!
a-z0-9_- ↩︎
\u4e00-\u9fa5 ↩︎