js忍者秘籍读书笔记(前四章)
作者:互联网
js忍者秘籍读书记录(第2版)
以下文章都是从书中所得,自己记录了一些本人认为比较重要的东西,方便日后查找
记录原因
之前大概花了一个月时间看完了这本书,但是中间因为一些原因,大概中断了10天左右,后来再拿起来的时候发现前面的都忘记了,但是还是坚持从中断的地方开始看,为了对书中内容加强记忆,打算再看一遍,这一遍会一直记录一些笔记。再之前看红宝书第三版的时候也有记笔记,当时是手写笔记,虽然手写映像比较深刻,但是保存不好,之后再拿起想添加修改内容的时候已经无法下笔,写的有点儿乱,所以这次打算在博客记录,这篇文章仅仅是个人笔记记录。
第一章 无处不在的javascript
第一章是介绍js的运行环境,发展历史,和浏览器的介绍,以及浏览器的概念和api,不予重要记录
第二章 运行时的页面构建过程
第二章阅读开始之前先提几个问题
1、浏览器地址栏输入url或者点击链接到页面渲染都发生了什么
2、浏览器是如何解析HTML以及DOM构建
3、web应用生命周期
这个流程图可以描述出客户端web应用的周期从用户指定某个网站地址或者单击链接开始,由两个步骤组成:页面构建和事件处理
当我们关闭网页离开的时候应用的生命周期结束
接下来说一下页面构建和事件处理
页面构建阶段
构建阶段是从服务器响应返回HTML、CSS、JS之后开始构建,其中主要包括两个部分:
1、解析HTML代码并构建文档对象DOM
2、执行js代码
步骤一会在浏览器处理HTML节点的时候执行,步骤二两会在HTML解析到一种特殊节点-脚本节点(script)时执行,页面构建阶段中,这两个步骤会交替执行多次
HTML是用来构建DOM蓝图的,但两者并不相同,浏览器在构建过程中会修复HTML节点中的错误
例如:当head节点中包含p标签时,浏览器解析完毕之后可以看到,p标签并没有在head中而是在body标签中
在页面构建过程中,每当解析到脚本元素时,浏览器就会停止从HTML构建DOM,并开始执行js代码
执行javaScript代码
js代码是由浏览器中的JavaScript引擎执行,浏览器不同JavaScript引擎也不一样。(Chrome是V8引擎)
但代码主要目的是提供动态页面,所以路蓝旗通过全局对象提供了一个API使JavaScript引擎可以与之交互并改变页面内容:就是全局对象 window
JavaScript代码的不同类型
JavaScript代码分为两种类型:全局代码和函数代码
<script>
// 这是函数代码,在函数内的
function Son() {
this.rules = true
console.log(this);
return 1
}
// 这是全局代码,函数外的
let name = new Son()
</script>
在页面构建阶段执行JavaScript代码
因为浏览起在页面构建阶段遇到脚本节点就会执行js,并停止构建DOM,而且js可以操作DOM(移除或者修改节点),所以如果我们在某一个DOM完成构建之前执行某一段js代码去修改则不会完成,所以这就是为什么在开发中要把script元素放到页面底部的原因。这样我们就不用担心某个HTML节点是否已经加载为DOM了
在构建过程中一旦遇到脚本元素,浏览器就睡进入JavaScript引擎开始执行,当执行到最后一行,浏览器就睡退出JavaScript引擎,并继续构建DOM,如果再次遇到脚本元素,那么从HTML到DOM构建会再次停止,并进入JavaScript引擎执行余下的js代码。重点是:JavaScript应用在此时已然保持着全局状态。所有在某个JavaScript代码执行期间的用户创建的全局变量都能正常的被其他脚本元素中的js代码所访问到。其原因在于全局对象会存在与整个页面的生存期间。只要有没处理完的HTML元素和没执行完的JavaScript代码,下面两个步骤就会一直执行(这也是为什么会说全局变量会浪费内存,少用的原因)
1、解析HTML代码并构建文档对象DOM
2、执行js代码
事件处理
事件处理器
浏览器核心思想是:同一时间只能执行一个代码片段,即所谓的单线程执行模型
事件处理的过程
- 浏览器检查事件队列头
- 如果浏览器没有在队列中检查到事件,则继续检查
- 如果检测到了事件,则取出该事件并执行相应的事件处理器(如果存在)。在这个过程中,余下的事件队列要在队列中耐心等待,直到检测它为止
注册事件处理器
注册事件有两种方法
- 通过函数赋值给某个特殊属性
- 通过使用内置的addEventListener方法
使用第二种方法来注册事件有个缺点是:对于某个事件只能注册一个事件处理器,一不小心就会将上一个节点处理器改写掉。但是使用addEventListener就不会
下面是两个注册方法
document.addEventListener('click',function(){
console.log('addEventListener可以注册多个');
})
document.onclick = function(){
console.log('只能注册一个');
}
处理事件
事件处理背后的主要思想是:当事件发生时候,浏览器调用响应的事件进行处理,由于浏览器是单线程执行模型,所以同一时间只能处理一个事件,后面的事件只能等待当前事件执行完毕之后才能被处理
假如现在有两个事件,一个是鼠标移动事件,一个是鼠标点击事件。先执行移动事件,再执行点击事件,此时队列中有两个事件:1、鼠标移动事件 2、鼠标点击事件
在事件处理阶段中,事件循环会检查队列,发现队首是移动事件,则开始执行,执行完毕后,会继续检查,发现等待的点击事件,则开始执行,执行完毕之后再次检查,队列里面没有事件可以执行了。则事件循环就会一直循环检测看是否有新的事件推入到事件队列中,这个循环会一直执行到用户关闭了web应用
第三章 新手的第一堂函数课:定义与参数
函数是JavaScript中的一等公民
因为函数可以做函数的参数被调用,也可以被作为函数的返回值,还可以添加属性,反正就是可以做很多的事情
函数式的不同点到底是什么
浏览器中除了执行全局的代码之外,其他执行的代码都是在函数内执行
为什么说函数是一等公民呢?下面先介绍一下对象中我们可以使用的功能。
函数拥有对象的所有能力
- 对象可以使用字面量来创建{}
- 对象可以复制给变量、数组项、或其他对象的属性
var obj = {}// 通过字面量创建
arr.push({}) // 向数组中push一个对象
- 对象可以作为函数的参数使用
function fn(obj){
obj.name = 'tom'
}
fn(obj)
- 从函数中返回一个新对象
function fn2(){
return {}
}
- 对象具有动态创建和分配的属性
var person = {}
person.name='job'
函数是第一类对象
function fn() { } // 通过字面量创建
var fn = function () { }// 赋值给变量、数组向或者其他对象的属性
function call(fn) { }// 作为函数的参数传递
function call() { return function () { } } // 作为函数的返回值
var fn = function () { } // 具有动态创建和分配的属性
fn.name = 'Tom'
对象可以做的函数都可以做,唯一特殊的就是函数式可调用的
回调函数
回调函数的形成是因为函数可以作为函数的参数被调用,所以有了回调函数
一个例子说明回调函数的用法,排序
JavaScript数组都有sort方法,默认排序是升序
var arr = [2, 5, 3, 4, 1]
var b = arr.sort()
console.log(b); // 1,2,3,4,5
那如果我们想降序排列怎么做呢?看下面,用回调函数
var c = arr.sort(function (a, b) {
return b - a
})
console.log(c); // 5,4,3,2,1
以上可以利用回调函数实现降序排列是因为在函数比较的时候,每次都调用回调函数
sort方法在回调函数的期望值返回值为:如果传入值的顺序需要被调换,返回正数;不需要被调换,返回负数;两个值相等,返回0
函数作为对象的乐趣
函数这个功能真是我第一次见,之前从来不知道函数还可以添加属性
var fn = function () { } // 具有动态创建和分配的属性
fn.name = 'Tom'
也正是这个特性解决了一个我一直疑惑的问题(不知道这么理解对不对),就是使用axios库的时候,调用get方法有多种调用方法。我猜测是因为这个特性,后面有时间看看axios源码看看
1、使用axios.get()可以调用
2、直接调用axios('get’)方法也可以执行
函数定义
- 函数声明和函数表达式(最常用的)
- 箭头函数(ES6的新语法)
- 函数构造函数(不常用)
- 函数生成器(ES6的新功能)
函数声明和函数表达式
函数声明是独立的,独立的代码块。函数是需要是调用的,在调用的时候需要有标识符,所以函数声明的时候必须要有函数名称,不然你用什么去调用
function fn(){
// fn就是函数名称
}
函数表达式
var fn = function(){}
(function(){})() // 立即调用函数也是函数表达式
// 一元操作符也可以调用函数
// 自己的理解:类似于立即执行函数
+function () { console.log(1) }()
- function () { console.log(2) }()
! function () { console.log(3) }()
~ function () { console.log(4) }()
当函数被当做参数传入的时候也是函数表达式
函数表达式和函数声明更重要的不同点是:函数声明的函数名是必须的,而对于函数表达式来说是可选的
箭头函数
var fn = num => 1+num
当参数是一个的时候可以不使用括号,如果是多个需要使用括号包住并且需要使用逗号分隔
参数列表之后加一个胖箭头符号=>,以此向JavaScript引擎指示当前处理的是箭头函数
函数的实参和形参
这里我是彻底理解了实参和形参是什么了,之前是在太菜了
- 形参是我们定义函数时所列举的变量
- 实参是我们调用函数时所传递给函数的值
参数是从左到右赋值的,如果有多余参数将不做处理
第四章 函数进阶:理解函数调用
这一节主要讲的是函数的两个参数对象:隐式函数参数this和arguments。
this表示被调用函数的上下文对象,而arguments对象表示函数调用过程中传递的所有参数
这章的学习中我们要解决这三个问题
- 为什么this参数表示函数上下文?
- 函数(function)和方法(method)之间有什么区别?
- 如果一个构造函数显示的返回一个对象会发生什么?
使用隐式函数参数:在函数调用时会传递两个隐式参数:this和arguments
arguments是一个类数组,有length属性,可以获取到传递所有参数的长度,可以通知数组下标的方式获取参数,例如:使用arguments[1]可以获取到第二个参数。
注意:arguments是一个类数组,但不是数组,仅仅是一个类数组的结构
下面一个示例,sum函数并没有显示的声明参数,但是传递之后可以使用arguments打印
function sum(){
console.log(arguments[2]) // 3
}
sum(1,2,3,4)
注意
上一节讲过剩余参数(rest parameter)来代替arguments参数,剩余参数是真正的Array实例
arguments特性
它可以作为参数的别名。例如:如果给arguments[0]赋值一个新值,则同时会修改第一个参数的值
反过来也是一样的,修改一个参数的值,也会修改对应的arguments对象
但这样使用不推荐
避免使用别名
因为使用arguments对象作为参数别名会影响代码可读性,所以在JavaScript提供的严格模式(strict mode)中无法使用
使用严格模式修改arguments对象中的值不会再影响参数值:
“use strict” 是告诉JavaScript引擎,下面的代码在严格模式下执行
"use strict"
function sum(a, b, c) {
arguments[2] = 5
console.log(arguments[2]) // 5
console.log(c); // 3
}
sum(1, 2, 3)
this参数:函数上下文
我自己感觉this是一个神奇的存在,在JavaScript中是很重要的一个东西,
之前很久对他都没有弄明白,这一节也只是讲一个概念,后面会展开仔细描述
this代表函数调用相关联的对象,因此,通常称this为函数上下文
函数上下文是来自面向对象语言(如Java)的一个概念,在这些语言中,this通常指向定义当前方法的实例
小心注意
在JavaScript中,将一个函数作为方法(method)调用仅仅是一个函数调用的一种方式。事实上,this参数指向不仅是由定义函数的方式和位置决定的,同事还严重受到函数调用方式的影响。下面了解函数调用的不同方式,会发现主要区别是this的值不同
函数调用
函数调用的时候发生了什么呢?
主要体现在this参数以及函数上下问是如何建立的
函数调用的4种方式
- 作为一个函数(function)------shulk(),直接被调用----函数调用
- 作为一个方法(method)—nijia.skulk(),关联在一个对象上,实现面向对象编程----方法调用
- 作为一个构造函数(constructor)----new Ninja(),实例化一个新的对象
- 通过函数的apply和call方法-----skulk.apply(ninja)或者skulk.call(ninjia)
作为函数直接被调用
function fn(){console.log('111')}
fn() // 作为函数直接调用
这种方式是作为函数直接调用,函数上下文(this关键字的值)有两种情况
1、在非严格模式下,this指向的是window(全局上下文)
2、在严格模式下,this指向undefined
作为方法被调用
当一个函数被复制给一个对象的属性,并且通过对象属性引用的方式调用函数时,函数会作为对象的放大被调用
var ninjia = {}
ninja.fn = funciton(){}
ninja.fn()
使用对象属性调用放大时,这个时候函数上下文就是ninja对象了,this指向的就是ninja对象
作为构造函数调用
构造函数是通过关键字new来调用的
function Person() { console.log(11) }
new Person() // 11
new Person // 11 当没有参数时可以不加()
在使用new关键字的时候做了什么
- 新创建了一个对象
- 该对象作为this参数传递给构造函数,从而成为构造函数的函数上下文
- 新构造的函数作为new运算符的返回值
例如:
function Ninja(){
this.skulk = funciton(){
return this
}
}
var ninja1 = new Ninja()
//ninjia1就是使用new关键字创建的一个新的空对象
// 然后新的空对象被设置为该函数的上下文this
// 第三步是给ninjia1对象增加了一个新的方法(下图显示)
// 最后将该对象作为函数的返回值
函数的目的是创建一个新的空对象,并对其初始化设置,然后将其作为构造函数的返回值
通过关键字new调用的函数将返回新创建的对象
构造函数的返回值都是新创建的对象吗?
用下面的例子来证明一下
function Ninja() {
this.fn = function () {
return true
}
return 1
}
let ninja = new Ninja()
console.log(ninja); // 返回的是新创建的对象,不是1,返回值1被忽略了
上面的例子可以说明使用new关键字的时候返回的是新创建的对象,返回值1被忽略了,但是当通过函数调用时的确返回值是1
好的,咱们再来举一个例子:
const son = {
name: 'tom'
}
function Person() {
this.name = 'Bob'
return son // 返回全局对象son
}
const son2 = new Person()
console.log(son2); // 返回的是全局对象 son,不是和上一个例子一样把返回值给忽略了
console.log(son2.name); // 返回的是tom,不是Bob
这两个测试例子可以总结出一个结论:
- 如果构造函数返回一个对象,则该对象将作为整个表达式的值返回,而传入的构造函数this将被丢弃
- 但是如果构造函数返回的是非对象类型,则忽略返回值,返回新创建的对象
使用apply和call方法调用
不同类型函数调用之间的主要区别在于:最终作为函数上下文(可以通过this参数隐式引用到)传递给执行函数的对象不同。对于方法而言,即为方法所在的对象;对与顶级函数而是是window或者是undefined(取决于是否处于严格模式);对与构造函数而言是一个新创建的对象实例
而这一小节讲的就是如何修改函数的上下文
先看下面一个神奇的事情,this的指向问题
function Button() {
this.clicked = false
this.click = function () {
console.log(this); // 这个的this是button这个元素
this.clicked = true
console.log((button.clicked)); // 预期结果是true,实际结果确是 false
}
}
var button = new Button()
let btn = document.getElementById('btn')
btn.addEventListener('click', button.click)
为什么会发生这样的事情呢?是因为元素点击事件绑定之后,事件的this指向的是绑定元素,所以这里执行点击方法的时候this已经是元素,再获取button.clicked自然只能获取上面定时时候的值,修改时候的值的this已经不属于button这个实例对象了
使用apply和call解决
函数是由内置的Function构造函数所创建
这两种的使用区别
apply和call使用方式类似,只是传参方式不一样
apply的参数是以数组的方式传递,例如:fn.apply(this,[1,2,3,4])
call的参数是直接传递,例如:fn.call(this,1,2,3)
解决函数上下文的问题
使用bind和箭头函数可以实现和apply和call同样的效果
一个重要的知识点:之前一直知道的是箭头函数可以改变this的指向,其实箭头函数并没有这个功能,箭头函数没有自己的this,他的this与声明所在的上下文的相同
再看一下之前按钮元素点击的例子,使用箭头函数重写的方法,结果就大不相同
function Button() {
this.clicked = false
this.click = () => {
console.log(this); // 这个的this是Button对象
this.clicked = true
console.log((button.clicked)); // 结果是true
}
}
var button = new Button()
let btn = document.getElementById('btn')
btn.addEventListener('click', button.click)
在本例中,箭头函数在构造函数内部,this指向创建的对象本身,因此无论何时调用click函数,this都将指向新创建的对象
这个例子和之前的不同之处只是修改了click方法的写法,就出现了不同的结果,可以证明,箭头函数自身不含上下文,从定义时所在的函数继承上下文
以上例子,使用bind可实现同样功能
function Button() {
this.clicked = false
this.click = function () {
console.log(this); // 这个的this是Button对象
this.clicked = true
console.log((button.clicked)); // 结果是true
}
}
var button = new Button()
let btn = document.getElementById('btn')
btn.addEventListener('click', button.click.bind(button))
使用bind是创建了一个新函数,不会修改原始函数,而是创建了一个新的函数(与原来的函数体相同)
使用一周的零碎时间记录了这些笔记,之后的每一章节都会有单独的记录,这几章看完第二遍之后印象又深刻了一些。
尤其是第四章讲解this,之前对this的指向是一塌糊涂,这本书里面讲解的很透彻
- 明确了new关键字的作用(面试常被问到的问题)
- 箭头函数为什么可以改变this的指向(其实不是改变,只是它没有自己的this,继承了函数定义时所在上下文的this)
- 如何使用apply、call、bind来修改函数上下文
- 严格模式和非严格模式的this问题
- 页面构建阶段:解析html代码并构建DOM、执行js代码
标签:忍者,function,console,函数,读书笔记,对象,js,fn,log 来源: https://blog.csdn.net/qq_39183005/article/details/110682630