执行上下文 和 执行上下文栈
- 代码类型:分为全局代码和函数代码
- 执行上下文根据代码类型可以区分:全局执行上下文 和 函数执行上下文
- 全局执行上下文:在执行全局代码前将window确定为全局执行上下文。过程:
- 如果使用 var 定义的全局变量提升到全局前端,值为 undefined,添加为window的属性
- function声明的的全局函数添加为window的方法
- 给全局的 this 赋值 window
- 执行全局代码
- 函数执行上下文:只有在调用函数时才会生成函数执行上下文
- 调用函数,在执行函数体之前,创建对应函数的函数执行上下文对象
- 形参变量赋值实参传递的数据,添加为函数执行上下文的属性
- argument被赋值所有实参的列表(一个类数组),添加为函数执行上下文的属性
- 定义的变量添加为函数执行上下文的属性,使用 var 定义的变量还会提升到函数执行上下文的前端值为 undefined
- function声明的的函数添加为函数执行上下文的方法
- this赋值(调用函数的对象)
- 执行函数执行上下文
- 全局执行上下文:在执行全局代码前将window确定为全局执行上下文。过程:
- 执行上下文栈:在全局代码执行前,js引擎会创建一个用来储存管理所有执行上下文的栈
- 在全局执行上下文(window)确定后,添加到栈中,也是第一个添加在栈中的执行上下文
- 调用函数创建函数执行上下文,将函数执行上下文添加到栈中
- 函数调用结束后,将在栈区中最顶层的函数执行上下文移除,依次从上到下将已经调用结束的函数的函数执行上下文,从栈区移除
- 总结规律就是最后进入的函数执行上下文最先移除:后进先出
- 当所有的代码执行结束后,栈中只剩下全局执行上下文(window)
变量提升 和 函数提升
- 在代码中使用 var 定义的变量,会产生变量提升,也就是变量会提升到当前执行上下文的最前端。
- 定义一个函数,函数也会进行提升,且在js中 是先提升变量,再提升函数。也就是函数一定在变量之前。
- 如果函数定义使用的是 函数表达式:let fun = function(){},则函数不会提升
this的指向
this的指向是根据函数的调用方式(跟创建方式无关)来决定的。
- 以函数形式调用时,this 指向 window
- 以方法形式调用时,this 指向调用方法的对象
- 构造函数调用模式, 此时函数内部的 this 指向创建的 实例对象
- 函数的方法借调形式, 通过 apply、call、bind 等函数方法去传入对象, this 指向被传入的对象
function fun() {
console.log(this)
}
fun() // 函数形式调用 打印的this指向 window,前面的调用也可以是=> window.fun(), 也是一种方法调用
let obj = {
say: fun
}
obj.say() // 方法形式调用 打印的 this 指向 obj
let obj2 = new fun() // 构造函数创建实例对象,this 指向当前创建的对象 obj2
let obj3 = {}
fun.apply(obj3, ...) // 这时候 this 指向 obj3,call、bind 方法也是一样
构造函数
构造函数和普通函数的区别就是调用方式不同, 普通函数是直接调用, 而构造函数是通过 new 关键字来调用, 构造函数的首字母一般为大写
构造函数的执行过程
- 创建一个新对象
- 将新建的对象设置为函数中的 this, 在构造函数中可以使用 this 来引用新建的对象
- 逐行执行构造函数中的代码
- 将新建的对象作为返回值返回
使用构造函数来创建对象可以很好的区分不同对象的类型
通过 instanceof 可以判断当前的对象是否是一个类(构造函数)的实例
但是所有的对象的顶级原型都是 Object, 也就是所有的对象都是 Object 的后代
注意点:
- 在构造函数中, 不可以直接定义函数方法, 因为这样在创建多个对象时就会创建多个方法, 这样会消耗内存,可以在原型对象中去添加公共的方法
// 形容狗的构造函数
function Dog(name) {
this.name = name
}
let dog1 = new dog()
console.log(dog1) // 打印: Dog {}
// 形容人的构造函数
function Person(name) {
this.name = name
// 举例 向对象中添加方法
// 这种方式去向对象中添加方法, ,每个对象中的方法都是唯一的且相同的,
this.say = function() {
}
// 1. 可以将函数定义在全局作用域中, 再将函数赋值给构造函数中的方法, 但不推荐这么做
// 2. 原型:
}
let person = new Person()
console.log(person) // 打印: Person {}
console.log(person instanceof Person) // 打印 true
console.log(person instanceof Dog) // 打印 false
console.log(person instanceof Object) // 打印 true
原型(prototype)与 原型链
创建一个函数, 函数中就会有一个属性 prototype, 函数的 prototype 对应着一个object空对象, 即 原型对象,简称原型
- 原型对象中有一个属性 constructor,该属性指向函数对象,也就是其函数本身。
- 如果函数作为普通函数调用则 prototype 没有作用
- 给函数的原型对象添加属性(方法),通过该函数 new 实例对象时,该对象也拥有原型中添加的属性(方法)。
- 如果函数以构造函数形式调用, 它所创建的 实例对象 中会有一个隐含的属性, 该属性指向构造函数的原型对象, 可以通过
__proto__来访问该属性. - 所以用同一个构造函数创建的 实例对象 的
__proto__指向的原型对象都是同一个. - 相当于 原型对象 是一个公共区域, 由同一个构造函数(类)创建的 实例对象 都可以访问, 也就是可以将 对象共有的部分设置到 原型对象 中.
- 原型链:
- 当访问实例对象的一个属性时, 会先在对象的自身中去寻找这个属性, 如果存在则直接使用.
- 如果不存在则会通过
__proto__找到原型对象中是否存在, 存在则使用 (在编译器中会省略__proto__, 直接 对象.属性名, 这样访问也会执行上面的规则):对象.__proto__.属性名 简写=> 对象.name. - 如果还不存在, 则会在原型对象的原型中去查找这个属性, 也就是:
对象.__proto__.__proto__.属性 - 所有函数也是由 顶级函数 function Function(){} 创建的实例对象。也就是说
函数.__proto__ === Function.prototype - 而 顶级函数 function Function(){} 的 prototype 和
__proto__是指向自身的原型对象:Function.prototype === Function.__proto__ - 且 function Function(){} 的原型对象的隐式原型也指向 Object.prototype:
Function.prototype === Object.prototype - 函数对象 function Object(){} 也是由顶级函数 function Function(){} 创建的实例对象,所以:
Object.__proto__ === Function.prototype - 在 js 中顶级 函数对象 function Object(){},Object.prototype 也就是原型对象,是所有对象查找到的最高层原型对象,所以顶级函数 Function 的原型对象的隐式原型也指向Object.prototype:
Function.prototype.__proto__ === Object.prototype, 而Object.prototype再往上的原型为 null:Object.prototype.__proto__ === null - 一直找到顶级 Object 的原型对象 Object.prototype,如果在Object的原型还没找到想要的属性, 则会返回 undefined
继承模式
- 原型链继承
- 一个构造函数想要继承另外一个构造函数的原型,则要 子类构造函数的原型指向父类的实例对象:
子函数名.prototype = new 父函数名() - 上面的赋值,让子类可以获取到父类的属性或方法,但这时候访问
子函数名.prototype.constructor属性时,指向的却是父类函数本身,这是因为 父类的实例对象赋值给了子类的原型,这里就需要及时改正:子函数名.prototype.constructor = 子函数名。
- 一个构造函数想要继承另外一个构造函数的原型,则要 子类构造函数的原型指向父类的实例对象:
function Dog(name) {
this.name = name
}
let dog = new Dog()
//示例: 原型对象
Dog.prototype.name = '哈哈哈'
// 原型对象中 constructor 属性
console.log(Dog.prototype.constructor === Dog) // 打印 true
console.log(dog.__proto__ === Dog.prototype) // 打印 true
console.log(dog.name) // 打印 '哈哈哈', 这里就是先查找自身是否存在 'name'属性,不存在则到原型对象中查找 => 相当于 dog.__proto__.name
dog.name = '嘿嘿嘿' // 给dog对象添加了一个name属性,并赋值‘嘿嘿嘿’
console.log(dog.name) // 打印 '嘿嘿嘿', 这里就直接打印自身存在的属性
闭包(Closure)
闭包:包含了被引用的变量或者函数的对象,存在于嵌套的内部函数中
- 闭包产生的条件
- 函数内部嵌套函数
- 内部函数引用外部函数的变量或者函数
- 执行外部函数(内部函数不执行也会产生闭包)
- 常见闭包:
- 将函数作为另外一个函数的返回值
- 将函数作为实参传递给一个函数调用
- 闭包的作用():
- 使函数内部的变量在函数执行完之后,仍然存活在内存中,只有嵌套的内部函数引用的变量才可以,也就是存在闭包中的变量(延迟生命周期)
- 让函数外部可以通过闭包操作到函数内部的数据(变量或函数)
- 防止全局变量的污染
- 闭包的缺点
- 函数执行后,内部变量没有被释放,容易造成内存泄漏
- 解决方法:用完及时主动释放
对象方法:
- 对象.hasOwnProperty('属性名') // 查找当前对象自身是否存在这个属性
回收机制
JavaScript拥有自动的垃圾回收的机制, 会将垃圾对象从内存中销毁, 避免内存浪费导致运行卡顿.
所以我们在编写代码时, 存在不需要的对象(变量)时, 可以将对象(变量)的值设置为 null, 使得引用的对象得到释放.
函数的方法
call() apply() 这两个方法都是函数对象的方法, 需要通过函数对象来调用. 当函数调用这两个方法都会调用函数执行.
call() apply() 可以将一个对象指定为第一个参数传给函数内部, 这个对象会成为函数执行的 this , 也就是修改 this 的指向
当然在定义函数的时候会指定形参, 而 call 和 apply 在传入指定的 对象 时不会和形参出现冲突.
- call(obj, 实参1, 实参2, ...) call 方法第一个实参用于传入指定对象, 对应实参依次传入
- apply(obj, [实参1, 实参2, ...]) apply 方法第一个实参传入指定对象, 对应的实参组成数组作为第二个参数传入
函数中的方法:
- arguments: 获取调用函数传入的所有实参, 是一个类数组, 但不是数组, 可以使用数组的方法.
- arguments.length: 获取实参的长度, 也就是传入了多少个实参
- arguments.callee: 获取到当前函数对象
正则表达式
通过构造函数 RegExp 创建一个正则对象: new RegExp("正则表达式规则", "匹配模式")
通过字面量创建正则表达式: let 变量 = /正则表达式/匹配模式
匹配模式: 该参数有两个值 i: 忽略大小写; g: 全局匹配模式(默认该值);
正则对象可以通过 test() 方法检查一个对象是否符合正则表达式的规则, 检查的方式是: 检查对象中是否存在指定的元素, 且校验默认严格区分大小写, 返回值为布尔值. 还有其他方法可以查询w3cschool
正则表达式 符号意义:
- '|': 表示或, 用于隔开表达式字符. 例: /a|b/ 检查是否存在a 或者 b
- '[字符]': 方括号, 查找方括号之间的任何字符。例: /[abc...]/ 检查是否存在a或者b或者c或者...
- '[0-9]': 查找任何从 0 至 9 的数字。
- '[a-z]': 查找任何小写的从 a 至 z 的字母
- '[A-Z]': 查找任何大写的从 A 至 Z 的字母
- '[A-z]': 查找任何大小写字母
- '[^字符]': 除了该字符. 例:let reg = /[^a]/ reg.test('a')=>打印false; reg.test('ab')=>打印true
- '^': 开头字符
- '$': 结尾字符
// 使用构造函数创建
let reg = new RegExp("a")
let str = 'a'
console.log(reg.test(str)) // 打印 true, 检查的字符串包含'a'符合规则
str = 'abc'
console.log(reg.test(str)) // 打印 true, 检查的字符串包含'a'符合规则
str = 'skdf'
console.log(reg.test(str)) // 打印 false, 检查的字符串不包含'a'不符合规则
//使用字面量创建, 与构造函数创建用法一致
let reg2 = /a/g
DOM
浏览器加载一个页面(HTML)时,是按照自上到下的顺序加载的,所以最好的情况 script 标签会写在 body 标签中的最后面,也就是在加载页面结构后再执行 js 代码。当然也可以在 script 标签中添加事件(onload)监听,当页面加载完成之后再去执行 js 代码,这样 script 标签写在哪里都可以。
<script>
window.onload = function() {
// 编写 js 代码, 这里就可以在页面加载完成之后再执行
}
</script>
DOM查询
也就是获取元素节点,通过 document 对象调用相应的方法,获取对应的元素节点
let dom = document.getElementById('btn') // 这里就是获取到页面中id属性的属性值为 'btn' 的元素
| 方法 | 描述 |
|---|---|
| getElementById() | 返回带有指定 ID 的元素。 |
| getElementsByTagName() | 返回包含带有指定标签名称的所有元素的节点列表(集合/节点数组(类数组))。 |
| getElementsByClassName() | 返回包含带有指定类名的所有元素的节点列表。 |
| getElementsByName() | 返回包含带有指定属性name的所有元素节点列表 |
| appendChild() | 把新的子节点添加到指定节点。 |
| removeChild() | 删除子节点。 |
| replaceChild() | 替换子节点。 |
| insertBefore() | 在指定的子节点前面插入新的子节点。 |
| createAttribute() | 创建属性节点。 |
| createElement() | 创建元素节点。 |
| createTextNode() | 创建文本节点。 |
| getAttribute() | 返回指定的属性值。 |
| setAttribute() | 把指定属性设置或修改为指定的值。 |
document属性
- body 属性 表示页面的 body 元素节点: document.body
- documentElement 属性 表示页面的根标签 HTMl:document.documentElement
- all 属性 表示页面中所有的元素节点列表
document方法
- querySelector() 方法 可以根据css选择器的字符串作为参数来查询一个元素节点对象,当页面有多个相同的选择器元素,则只会找到第一个:
document.querySelector('.box div') - querySelectorAll() 方法 可以根据css选择器的字符串作为参数来查询元素节点列表。
获取元素节点的子节点 通过具体的元素节点对象调用相应的方法获取子节点,也就是先通过上面 document对象的方法获取到元素节点对象,再调用方法获取子节点
- getElementsByTagName()方法 返回当前节点下指定标签名的子节点列表
- childNodes 属性 表示当前节点的所有子节点,不仅是元素节点,是包括文本节点(标签之间的空白也会识别为文本节点)等所有节点
- children 属性 表示当前节点的所有 子元素节点,不包含文本节点
- firstChild 属性 表示当前节点的第一个子节点(包括文本节点)
- firstElementChild 属性 表示当前节点的第一个 子元素节点(不兼容IE8)
- lastChild 属性 表示当前节点的最后一个子节点
- parentNode 属性 表示当前节点的父节点
- previousSibling 属性 表示当前节点的前一个兄弟节点(会获取空白的文本节点)
- previousElementSibling 属性 表示当前节点的前一个兄弟元素节点
- nextSibling 属性 表示当前节点的后一个兄弟节点,同理
DOM事件句柄 (Event Handlers)
| 属性 | 此事件发生在何时... | IE | F | O | W3C |
|---|---|---|---|---|---|
| onabort | 图像的加载被中断。 | 4 | 1 | 9 | Yes |
| onblur | 元素失去焦点。 | 3 | 1 | 9 | Yes |
| onchange | 域的内容被改变。 | 3 | 1 | 9 | Yes |
| onclick | 当用户点击某个对象时调用的事件句柄。 | 3 | 1 | 9 | Yes |
| ondblclick | 当用户双击某个对象时调用的事件句柄。 | 4 | 1 | 9 | Yes |
| onerror | 在加载文档或图像时发生错误。 | 4 | 1 | 9 | Yes |
| onfocus | 元素获得焦点。 | 3 | 1 | 9 | Yes |
| onkeydown | 某个键盘按键被按下。 | 3 | 1 | No | Yes |
| onkeypress | 某个键盘按键被按下并松开。 | 3 | 1 | 9 | Yes |
| onkeyup | 某个键盘按键被松开。 | 3 | 1 | 9 | Yes |
| onload | 一张页面或一幅图像完成加载。 | 3 | 1 | 9 | Yes |
| onmousedown | 鼠标按钮被按下。 | 4 | 1 | 9 | Yes |
| onmousemove | 鼠标被移动。 | 3 | 1 | 9 | Yes |
| onmouseout | 鼠标从某元素移开。 | 4 | 1 | 9 | Yes |
| onmouseover | 鼠标移到某元素之上。 | 3 | 1 | 9 | Yes |
| onmouseup | 鼠标按键被松开。 | 4 | 1 | 9 | Yes |
| onreset | 重置按钮被点击。 | 4 | 1 | 9 | Yes |
| onresize | 窗口或框架被重新调整大小。 | 4 | 1 | 9 | Yes |
| onselect | 文本被选中。 | 3 | 1 | 9 | Yes |
| onsubmit | 确认按钮被点击。 | 3 | 1 | 9 | Yes |
| onunload | 用户退出页面。 | 3 | 1 | 9 | Yes |
改变 HTML 样式 获取 HTML 样式
通过 HTML DOM,您能够访问 HTML 元素的样式对象。获取元素节点对象,通过style属性更改元素节点的样式,更改的样式属性与css一致,但css中以‘-’连接的样式名要修改成驼峰命名。而且 js 修改的样式,是通过行内样式修改的。所以css样式中如果存在‘!important’ 的样式 js无法修改。
<html>
<body>
<p id="p2">Hello world!</p>
<script>
document.getElementById("p2").style.color="blue";
</script>
</body>
</html>
获取 HTML 样式
获取指定元素的 CSS 样式。 window.getComputedStyle()
内存溢出 与 内存泄漏
- 内存溢出:1.一种程序运行出现错误。2.程序运行需要的内存超过剩余内存时,就会抛出内存溢出的错误
- 内存泄漏:占用的内存没有及时释放,内存泄漏积攒多了就容易内存溢出
- 常见的内存泄漏:1.意外的全局变量。2. 没有及时清理的计时器和回调函数。3. 闭包