古之立大事者,不惟有超世之才,亦必有坚忍不拔之志。这篇文章主要讲述深入JavaScript高级语法相关的知识,希望能为你提供帮助。
深入学习javascript的核心语法,特别是javaScript比较复杂的、难以理解的一些概念和特性,进行深入细致的讲解。
帮助你在前端技术学习、面试、工作、项目应用上,承上启下,融会贯通,不管你是前端在职工作者还是初学者,此课程都非常值得学习。
NoBug1024 微我JavaScript 是什么 解析执行:轻量级解释型的 语言特点:动态,头等函数 (First-class Function) 又称函数是 JavaScript 中的一等公民 执行环境:在宿主环境(host environment)下运行,浏览器是最常见的 JavaScript 宿主环境 但是在很多非浏览器环境中也使用 JavaScript ,例如 node.js JavaScript 的组成 ECMAScript - 语法规范 变量、数据类型、类型转换、操作符 流程控制语句:判断、循环语句 数组、函数、作用域、预解析 对象、属性、方法、简单类型和复杂类型的区别 内置对象:Math、Date、Array,基本包装类型String、Number、Boolean Web APIs BOM onload页面加载事件,window顶级对象 定时器 location、history DOM 获取页面元素,注册事件 属性操作,样式操作 节点属性,节点层级 动态创建元素 事件:注册事件的方式、事件的三个阶段、事件对象 浏览器是如何工作的 User Interface用户界面,我们所看到的浏览器 Browser engine浏览器引擎,用来查询和操作渲染引擎 *Rendering engine 用来显示请求的内容,负责解析html、CSS,并把解析的内容显示出来 Networking网络,负责发送网络请求 *JavaScript Interpreter(解析者)JavaScript解析器,负责执行JavaScript的代码 UI BackendUI后端,用来绘制类似组合框和弹出窗口 Data Persistence(持久化)数据持久化,数据存储cookie、HTML5中的sessionStorage 1 2 3 4 5 6 7 JavaScript 执行过程 JavaScript 运行分为两个阶段:预解析 全局预解析(所有变量和函数声明都会提前;同名的函数和变量函数的优先级高) 函数内部预解析(所有的变量、函数和形参都会参与预解析) 函数 形参 普通变量 执行 先预解析全局作用域,然后执行全局作用域中的代码, 在执行全局代码的过程中遇到函数调用就会先进行函数预解析,然后再执行函数内代码。 JavaScript 面向对象编程 面向对象介绍 什么是对象 Everything is object (万物皆对象)对象到底是什么,我们可以从两次层次来理解。 (1) 对象是单个事物的抽象。 一本书、一辆汽车、一个人都可以是对象,一个数据库、一张网页、一个与远程服务器的连接也可以是对象。当实物被抽象成对象,实物之间的关系就变成了对象之间的关系,从而就可以模拟现实情况,针对对象进行编程。 (2) 对象是一个容器,封装了属性(property)和方法(method)。 属性是对象的状态,方法是对象的行为(完成某种任务)。比如,我们可以把动物抽象为animal对象,使用“属性”记录具体是那一种动物,使用“方法”表示动物的某种行为(奔跑、捕猎、休息等等)。 在实际开发中,对象是一个抽象的概念,可以将其简单理解为:数据集或功能集。 ECMAScript-262 把对象定义为:无序属性的集合,其属性可以包含基本值、对象或者函数。 严格来讲,这就相当于说对象是一组没有特定顺序的值。对象的每个属性或方法都有一个名字,而每个名字都映射到一个值。 提示:每个对象都是基于一个引用类型创建的,这些类型可以是系统内置的原生类型,也可以是开发人员自定义的类型。什么是面向对象 面向对象不是新的东西,它只是过程式代码的一种高度封装,目的在于提高代码的开发效率和可维 护性。 面向对象编程 —— Object Oriented Programming,简称 OOP ,是一种编程开发思想。 它将真实世界各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟。 在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工,可以完成接受信息、处理数据、发出信息等任务。 因此,面向对象编程具有灵活、代码可复用、高度模块化等特点,容易维护和开发,比起由一系列函数或指令组成的传统的过程式编程(procedural programming),更适合多人合作的大型软件项目。面向对象与面向过程:面向过程就是亲力亲为,事无巨细,面面俱到,步步紧跟,有条不紊 面向对象就是找一个对象,指挥得结果 面向对象将执行者转变成指挥者 面向对象不是面向过程的替代,而是面向过程的封装 面向对象的特性:封装性 继承性 [多态性]抽象 程序中面向对象的基本体现 在 JavaScript 中,所有数据类型都可以视为对象,当然也可以自定义对象。 自定义的对象数据类型就是面向对象中的类( Class )的概念。创建对象 简单方式 我们可以直接通过 new Object() 创建:var person = new Object() person.name = Jack person.age = 18person.sayName = function () {console.log(this.name) } 1 2 3 4 5 6 7 每次创建通过 new Object() 比较麻烦,所以可以通过它的简写形式对象字面量来创建:var person = {name: Jack,age: 18,sayName: function () {console.log(this.name)} } 1 2 3 4 5 6 7 对于上面的写法固然没有问题,但是假如我们要生成两个 person 实例对象呢?var person1 = {name: Jack,age: 18,sayName: function () {console.log(this.name)} }var person2 = {name: Mike,age: 16,sayName: function () {console.log(this.name)} } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 通过上面的代码我们不难看出,这样写的代码太过冗余,重复性太高。简单方式的改进:工厂函数 我们可以写一个函数,解决代码重复问题:function createPerson (name, age) {return {name: name,age: age,sayName: function () {console.log(this.name)}} } 1 2 3 4 5 6 7 8 9 然后生成实例对象:var p1 = createPerson(Jack, 18) var p2 = createPerson(Mike, 18) 1 2 这样封装确实爽多了,通过工厂模式我们解决了创建多个相似对象代码冗余的问题, 但却没有解决对象识别的问题(即怎样知道一个对象的类型)。构造函数 内容引导:构造函数语法 分析构造函数 构造函数和实例对象的关系 实例的 constructor 属性 instanceof 操作符 普通函数调用和构造函数调用的区别 构造函数的返回值 构造函数的问题 更优雅的工厂函数:构造函数 一种更优雅的工厂函数就是下面这样,构造函数:function Person (name, age) {this.name = namethis.age = agethis.sayName = function () {console.log(this.name)} }var p1 = new Person(Jack, 18) p1.sayName() // => Jackvar p2 = new Person(Mike, 23) p2.sayName() // => Mike 1 2 3 4 5 6 7 8 9 10 11 12 13 解析构造函数代码的执行 在上面的示例中,Person() 函数取代了 createPerson() 函数,但是实现效果是一样的。 这是为什么呢?我们注意到,Person() 中的代码与 createPerson() 有以下几点不同之处:没有显示的创建对象 直接将属性和方法赋给了 this 对象 没有 return 语句 函数名使用的是大写的 Person 而要创建 Person 实例,则必须使用 new 操作符。 以这种方式调用构造函数会经历以下 4 个步骤:创建一个新对象 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象) 执行构造函数中的代码 返回新对象 下面是具体的伪代码:function Person (name, age) {// 当使用 new 操作符调用 Person() 的时候,实际上这里会先创建一个对象// var instance = {}// 然后让内部的 this 指向 instance 对象// this = instance// 接下来所有针对 this 的操作实际上操作的就是 instancethis.name = namethis.age = agethis.sayName = function () {console.log(this.name)}// 在函数的结尾处会将 this 返回,也就是 instance// return this } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 构造函数和实例对象的关系 使用构造函数的好处不仅仅在于代码的简洁性,更重要的是我们可以识别对象的具体类型了。 在每一个实例对象中同时有一个 constructor 属性,该属性指向创建该实例的构造函数:console.log(p1.constructor === Person) // => true console.log(p2.constructor === Person) // => true console.log(p1.constructor === p2.constructor) // => true 1 2 3 对象的 constructor 属性最初是用来标识对象类型的, 但是,如果要检测对象的类型,还是使用 instanceof 操作符更可靠一些:console.log(p1 instanceof Person) // => true console.log(p2 instanceof Person) // => true 1 2 总结:构造函数是根据具体的事物抽象出来的抽象模板 实例对象是根据抽象的构造函数模板得到的具体实例对象 每一个实例对象都具有一个 constructor 属性,指向创建该实例的构造函数 注意: constructor 是实例的属性的说法不严谨,具体后面的原型会讲到 可以通过实例的 constructor 属性判断实例和构造函数之间的关系 注意:这种方式不严谨,推荐使用 instanceof 操作符,后面学原型会解释为什么 构造函数的问题 使用构造函数带来的最大的好处就是创建对象更方便了,但是其本身也存在一个浪费内存的问题:function Person (name, age) {this.name = namethis.age = agethis.type = humanthis.sayHello = function () {console.log(hello+ this.name)} }var p1 = new Person(Tom, 18) var p2 = new Person(Jack, 16) 1 2 3 4 5 6 7 8 9 10 11 在该示例中,从表面上好像没什么问题,但是实际上这样做,有一个很大的弊端。 那就是对于每一个实例对象,type 和 sayHello 都是一模一样的内容, 每一次生成一个实例,都必须为重复的内容,多占用一些内存,如果实例对象很多,会造成极大的内存浪费。console.log(p1.sayHello === p2.sayHello) // => false 1 对于这种问题我们可以把需要共享的函数定义到构造函数外部:function sayHello = function () {console.log(hello+ this.name) }function Person (name, age) {this.name = namethis.age = agethis.type = humanthis.sayHello = sayHello }var p1 = new Person(Top, 18) var p2 = new Person(Jack, 16)console.log(p1.sayHello === p2.sayHello) // => true 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 这样确实可以了,但是如果有多个需要共享的函数的话就会造成全局命名空间冲突的问题。你肯定想到了可以把多个函数放到一个对象中用来避免全局命名空间冲突的问题:var fns = {sayHello: function () {console.log(hello+ this.name)},sayAge: function () {console.log(this.age)} }function Person (name, age) {this.name = namethis.age = agethis.type = humanthis.sayHello = fns.sayHellothis.sayAge = fns.sayAge } var p1 = new Person(lpz, 18) var p2 = new Person(Jack, 16)console.log(p1.sayHello === p2.sayHello) // => true console.log(p1.sayAge === p2.sayAge) // => true 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 至此,我们利用自己的方式基本上解决了构造函数的内存浪费问题。 但是代码看起来还是那么的格格不入,那有没有更好的方式呢?小结 构造函数语法 分析构造函数 构造函数和实例对象的关系 实例的 constructor 属性 instanceof 操作符 构造函数的问题 原型 内容引导:使用 prototype 原型对象解决构造函数的问题 分析 构造函数、prototype 原型对象、实例对象 三者之间的关系 属性成员搜索原则:原型链 实例对象读写原型对象中的成员 原型对象的简写形式 原生对象的原型 Object Array String … 原型对象的问题 构造的函数和原型对象使用建议 更好的解决方案: prototype JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的所拥有。 这也就意味着,我们可以把所有对象实例需要共享的属性和方法直接定义在 prototype 对象上。function Person (name, age) {this.name = namethis.age = age }console.log(Person.prototype)Person.prototype.type = humanPerson.prototype.sayName = function () {console.log(this.name) }var p1 = new Person(...) var p2 = new Person(...)console.log(p1.sayName === p2.sayName) // => true 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 这时所有实例的 type 属性和 sayName() 方法, 其实都是同一个内存地址,指向 prototype 对象,因此就提高了运行效率。构造函数、实例、原型三者之间的关系 任何函数都具有一个 prototype 属性,该属性是一个对象。function F () {} console.log(F.prototype) // => objectF.prototype.sayHi = function () {console.log(hi!) } 1 2 3 4 5 6 构造函数的 prototype 对象默认都有一个 constructor 属性,指向 prototype 对象所在函数。console.log(F.prototype.constructor === F) // => true 1 通过构造函数得到的实例对象内部会包含一个指向构造函数的 prototype 对象的指针 __proto__。var instance = new F() console.log(instance.__proto__ === F.prototype) // => true 1 2 ?
?__proto__?
? 是非标准属性。实例对象可以直接访问原型对象成员。instance.sayHi() // =>
hi! 1 总结:任何函数都具有一个 prototype 属性,该属性是一个对象 构造函数的 prototype 对象默认都有一个 constructor 属性,指向 prototype 对象所在函数 通过构造函数得到的实例对象内部会包含一个指向构造函数的 prototype 对象的指针 __proto__ 所有实例都直接或间接继承了原型对象的成员 属性成员的搜索原则:原型链 了解了 构造函数-实例-原型对象 三者之间的关系后,接下来我们来解释一下为什么实例对象可以访问原型对象中的成员。每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性搜索首先从对象实例本身开始 如果在实例中找到了具有给定名字的属性,则返回该属性的值 如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性 如果在原型对象中找到了这个属性,则返回该属性的值 也就是说,在我们调用 person1.sayName() 的时候,会先后执行两次搜索:首先,解析器会问:“实例 person1 有 sayName 属性吗?”答:“没有。 ”然后,它继续搜索,再问:“ person1 的原型有 sayName 属性吗?”答:“有。 ”于是,它就读取那个保存在原型对象中的函数。 当我们调用 person2.sayName() 时,将会重现相同的搜索过程,得到相同的结果。 而这正是多个对象实例共享原型所保存的属性和方法的基本原理。总结:先在自己身上找,找到即返回 自己身上找不到,则沿着原型链向上查找,找到即返回 如果一直到原型链的末端还没有找到,则返回 undefined 实例对象读写原型对象成员 读取:先在自己身上找,找到即返回 自己身上找不到,则沿着原型链向上查找,找到即返回 如果一直到原型链的末端还没有找到,则返回 undefined 值类型成员写入(实例对象.值类型成员 = xx):当实例期望重写原型对象中的某个普通数据成员时实际上会把该成员添加到自己身上 也就是说该行为实际上会屏蔽掉对原型对象成员的访问 引用类型成员写入(实例对象.引用类型成员 = xx):同上 复杂类型修改(实例对象.成员.xx = xx):同样会先在自己身上找该成员,如果自己身上找到则直接修改 如果自己身上找不到,则沿着原型链继续查找,如果找到则修改 如果一直到原型链的末端还没有找到该成员,则报错(实例对象.undefined.xx = xx) 更简单的原型语法 我们注意到,前面例子中每添加一个属性和方法就要敲一遍 Person.prototype 。 为减少不必要的输入,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象:function Person (name, age) {this.name = namethis.age = age }Person.prototype = {type: human,sayHello: function () {console.log(我叫 + this.name + ,我今年 + this.age + 岁了)} } 1 2 3 4 5 6 7 8 9 10 11 在该示例中,我们将 Person.prototype 重置到了一个新的对象。 这样做的好处就是为 Person.prototype 添加成员简单了,但是也会带来一个问题,那就是原型对象丢失了 constructor 成员。所以,我们为了保持 constructor 的指向正确,建议的写法是:function Person (name, age) {this.name = namethis.age = age }Person.prototype = {constructor: Person, // =>
手动将 constructor 指向正确的构造函数type: human,sayHello: function () {#### 原生对象的原型所有函数都有 prototype 属性对象。
- Object.prototype
- Function.prototype
- Array.prototype
- String.prototype
- Number.prototype
- Date.prototype
- ...
?apply()?
? 方法类似,只有一个区别,就是 ??call()?
? 方法接受的是若干个参数的列表,而 ??apply()?
? 方法接受的是一个包含多个参数的数组。语法:fun.call(thisArg[, arg1[, arg2[, ...]]]) 1 参数:thisArg 在 fun 函数运行时指定的 this 值 如果指定了 null 或者 undefined 则内部 this 指向 window arg1, arg2, ... 指定的参数列表 apply apply() 方法调用一个函数, 其具有一个指定的 this 值,以及作为一个数组(或类似数组的对象)提供的参数。注意:该方法的作用和 ??call()?
? 方法类似,只有一个区别,就是 ??call()?
? 方法接受的是若干个参数的列表,而 ??apply()?
? 方法接受的是一个包含多个参数的数组。语法: ``??javascript fun.apply(thisArg, [argsArray]) ?
?`?? 参数: - ?
?thisArg?? - ?
?argsArray` apply() 与 call() 非常相似,不同之处在于提供参数的方式。 apply() 使用参数数组而不是一组参数列表。例如:fun.apply(this, [eat, bananas]) 1 bind bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在 ECMAScript 5 规范中内置的call属性)。 当目标函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也接受预设的参数提供给原函数。 一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。语法:fun.bind(thisArg[, arg1[, arg2[, ...]]]) 1 参数:thisArg 当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用new 操作符调用绑定函数时,该参数无效。 arg1, arg2, … 当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。 返回值: 返回由指定的this值和初始化参数改造的原函数拷贝。 示例1:this.x = 9;
var module = {x: 81,getX: function() { return this.x;
} };
module.getX();
// 返回 81 var retrieveX = module.getX;
retrieveX();
// 返回 9, 在这种情况下,"this"指向全局作用域// 创建一个新函数,将"this"绑定到module对象 // 新手可能会被全局的x变量和module里的属性x所迷惑 var boundGetX = retrieveX.bind(module);
boundGetX();
// 返回 81 1 2 3 4 5 6 7 8 9 10 11 12 13 示例2:function LateBloomer() {this.petalCount = Math.ceil(Math.random() * 12) + 1;
}// Declare bloom after a delay of 1 second LateBloomer.prototype.bloom = function() {window.setTimeout(this.declare.bind(this), 1000);
};
LateBloomer.prototype.declare = function() {console.log(I am a beautiful flower with+this.petalCount +petals!);
};
var flower = new LateBloomer();
flower.bloom();
// 一秒钟后, 调用declare方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 小结 call 和 apply 特性一样都是用来调用函数,而且是立即调用 但是可以在调用函数的同时,通过第一个参数指定函数内部 this 的指向 call 调用的时候,参数必须以参数列表的形式进行传递,也就是以逗号分隔的方式依次传递即可 apply 调用的时候,参数必须是一个数组,然后在执行的时候,会将数组内部的元素一个一个拿出来,与形参一一对应进行传递 如果第一个参数指定了 null 或者 undefined 则内部 this 指向 window bind可以用来指定内部 this 的指向,然后生成一个改变了 this 指向的新的函数 它和 call、apply 最大的区别是:bind 不会调用 bind 支持传递参数,它的传参方式比较特殊,一共有两个位置可以传递 在 bind 的同时,以参数列表的形式进行传递 在调用的时候,以参数列表的形式进行传递 那到底以谁 bind 的时候传递的参数为准呢还是以调用的时候传递的参数为准 两者合并:bind 的时候传递的参数和调用的时候传递的参数会合并到一起,传递到函数内部 函数的其它成员 arguments 实参集合 caller 函数的调用者 length 形参的个数 name 函数的名称 function fn(x, y, z) {console.log(fn.length) // =>
形参的个数console.log(arguments) // 伪数组实参参数集合console.log(arguments.callee === fn) // 函数本身console.log(fn.caller) // 函数的调用者console.log(fn.name) // =>
函数的名字 } function f() {fn(10, 20, 30) }f() 1 2 3 4 5 6 7 8 9 10 11 12 高阶函数 函数可以作为参数 函数可以作为返回值 作为参数 function eat (callback) {setTimeout(function () {console.log(吃完了)callback()}, 1000) }eat(function () {console.log(去唱歌) }) 1 2 3 4 5 6 7 8 9 10 作为返回值 function genFun (type) {return function (obj) {return Object.prototype.toString.call(obj) === type} }var isArray = genFun([object Array]) var isObject = genFun([object Object])console.log(isArray([])) // =>
true console.log(isArray({})) // =>
true 1 2 3 4 5 6 7 8 9 10 11 函数闭包 function fn () {var count = 0return {getCount: function () {console.log(count)},setCount: function () {count++}} }var fns = fn()fns.getCount() // =>
0 fns.setCount() fns.getCount() // =>
1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 什么是闭包 闭包就是能够读取其他函数内部变量的函数, 由于在 Javascript 语言中,只有函数内部的子函数才能读取局部变量, 因此可以把闭包简单理解成 “定义在一个函数内部的函数”。 所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。闭包的用途:可以在函数外部读取函数内部成员 让函数内成员始终存活在内存中 一些关于闭包的例子 示例1:var arr = [10, 20, 30] for(var i = 0;
i <
arr.length;
i++) {arr[i] = function () {console.log(i)} } 1 2 3 4 5 6 示例2:console.log(111)for(var i = 0;
i <
3;
i++) {setTimeout(function () {console.log(i)}, 0) } console.log(222) 1 2 3 4 5 6 7 8 正则表达式 了解正则表达式基本语法 能够使用JavaScript的正则对象 正则表达式简介 什么是正则表达式 正则表达式:用于匹配规律规则的表达式,正则表达式最初是科学家对人类神经系统的工作原理的早期研究,现在在编程语言中有广泛的应用。正则表通常被用来检索、替换那些符合某个模式(规则)的文本。 正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。正则表达式的作用 给定的字符串是否符合正则表达式的过滤逻辑(匹配) 可以通过正则表达式,从字符串中获取我们想要的特定部分(提取) 强大的字符串替换能力(替换) 正则表达式的特点 灵活性、逻辑性和功能性非常的强 可以迅速地用极简单的方式达到字符串的复杂控制 对于刚接触的人来说,比较晦涩难懂 正则表达式的测试 工具中使用正则表达式 sublime/vscode/word 演示替换所有的数字 正则表达式的组成 普通字符 特殊字符(元字符):正则表达式中有特殊意义的字符 示例演示:\\d 匹配数字 ab\\d 匹配 ab1、ab2推荐阅读
- kylin v10配置配置本地yum
- Linux网络设置
- #yyds干货盘点#单台zabbix5.0服务器如何拆分数据库角色
- fdisk指令给2T空间分区不成功演示
- 主页的宽度与主题中其他页面的宽度不同
- 隐藏评论中的帖子作者logo
- 向下滚动隐藏导航栏,向上滚动显示
- WP页眉不透明度在页面上不起作用
- 当在WordPress上更改尺寸时,页眉logo消失。可见到979width()