作用域
什么是js的作用域
js的执行环境中变量和函数能访问的范围,叫做作用域。作用域定义了函数或变量有权访问的其他数据。作用域都有一个变量对象。
作用域的类型
-
全局作用域
- 页面打开创建,页面关闭销毁
- 声明在javascript标签中的变量,可以在任意的地方访问到
- 全局作用域中生命的变量或者函数可以在window上被访问到,所有的全局变量都是window的属性
-
函数作用域
- 函数调用的时候被创建,函数执行结束之后,被销毁
- 函数的作用域可以向上层作用域访问变量,但是同层的函数作用域不能相互访问
- 函数作用域,在每次函数被调用的时候,都会创建一个新的作用域,相互之间没有影响
-
块级作用域
块级是es6之后才有的,在声名let/const变量的时候会被创建
作用域链
- 作用域链能保证执行环境对其可访问的变量和函数的有序访问。
- 在创建函数的时候,会创建一个预先包含全局变量对象的作用域链,这个作用域链被放在函数的Scope里,在调用函数的时候,就会创建一个函数的执行环境,并且会复制函数的Scope中的对象构建一个执行环境作用域链,创建活动对象AO,并把其推入执行环境的作用域链,在函数执行完毕之后,作用域链就会被销毁。
全局预编译和函数预编译
变量对象属性的值最开始都是undefiend
全局预编译
- 全局上下文,创建变量对象VO
- 先把变量声明作为VO的属性 值为undefined
- 再把函数声明作为VO的属性 值为函数本身
- 当函数声明的名称和变量声明的名称冲突时,用函数替换变量
console.log(a) // 函数a
var a = 100;
function a () { }
a = 100
console.log(a) // 100
/**
* vo = {
a: undefined funcA
}
* **/
VO的创建之后,先找a: undefiend,之后遇到funcA替换。VO声明结束,开始执行代码。
函数预编译
- 函数上下文创建之后,会创建变量对象AO
- 先找变量作为AO的属性 undefined
- 设置形参为AO的属性 undefined
- 实参给形参赋值,替换AO中形参的属性
- 设置函数声明为AO属性,值为函数本身
- 当函数声明的名称和变量声明的名称冲突时,用函数替换变量
内存与数据
内存
- js程序运行在内存里
- js变量的声明和函数的声明,调用都会占用内存
- 内存分为栈内存和堆内存
数据类型
- 基本数据类型
-
- number,string,null, undefined,boolen,biginit,symbol
- 值不能改变。在下面的代码中,a的值不是直接在栈内存里找到a,然后直接改变。而且开辟一个新的内存空间,把值变成a+1的结果,然后复制给a。之前就的a = 1的栈内存被变成了可以回收的状态,在之后会被回收掉。
- 数据存放在栈里
let a = 1;
a = a+1
- 引用数据类型
-
- object,arrary,Date,RegExp,function
- 值可以被改变。
- 数据存放在堆里,堆的地址存放在栈里。访问引用数据,是先找到地址,然后从地址里面得到真正的数据。
let obj1 = {
a: 1
}
let obj2 = obj1
console.log(obj1 === obj2)// true
obj2.a = 2
console.log(obj1.a) // 2
深拷贝/浅拷贝
浅拷贝
拷贝的属性如果是引用类型,会被共享
- Object.create()
- Object.assign()
- 扩展运算符...
深拷贝
拷贝的属性如果是引用类型,不会被共享
- JSON.parse(JSON.stringify(xx))
这个方法在处理对象属性是function,Symbol,undefined属性会丢失,属性是正则的话对应的数据会丢失
- 自定义deepClone函数处理
function deepClone(obj, map = new WeakMap()) {
if (obj === null || typeof obj === 'function' || obj instanceof Date || obj instanceof RegExp || typeof obj !== 'object') {
return obj
}
if (map.get(obj)) {
return map.get(obj)
}
if (Array.isArray(obj)) {
const ressult = []
map.set(obj, result)
obj.forEach((item) => {
ressult.push(deepClone(item, map))
})
return ressult;
}
if (obj instanceof Map) {
const result = new Map()
map.set(obj, result)
for(let val of obj) {
result.set(val[0], deepClone(val[1], map))
}
return result;
}
if (obj instanceof Set) {
const result = new Set()
map.set(obj, result)
for(let val of obj) {
result.add(deepClone(val, map))
}
return result;
}
// 放在最后防止,arrary,map,set类型进入
if (obj instanceof Object) {
const result = {}
map.set(obj, result)
for(let key in obj) {
result[key] = deepClone(obj[key], map)
}
return result;
}
}
闭包
能访问其他函数作用域里的对象的函数,称为闭包。
- 函数嵌套
function foo() {
var a = 1
return function bar() {
console.log(a)
}
}
var fn = foo()
fn()
- 回调函数
function foo(cb) {
var a = 1;
cb(a)
}
function bar(a) {
console.log(a)
}
foo(bar);
this
this的基本概念
- this不能在代码执行期间被赋值。
- this会随着执行环境而改变。
var bar = 2;
var obj = {
bar: 1,
foo: function() {
console.log(this.bar)
}
}
obj.foo() // 1
var foo = obj.foo
foo() // 2
this的指向
- 基本遵循指向调用者
- new改变this指向,指向为new生成的实例
- apply,call 改变this指向,指向为第一个参数对象,如果没有传递,就是指向window
事件循环(event loop)
js是一个单线程执行的,在代码执行的时候,由执行栈来执行任务,执行过程中,异步任务会被放入事件循环里,异步任务由分宏任务和微任务。在事件循环里面会执行当前的宏任务(从宏任务队列中取出一个任务推入执行栈),宏任务产生的微任务放入微任务队列,宏任务执行完毕之后,会处理微任务队列里所有的微任务。
- 宏任务:setTimeout, setInterval,ajax,dom事件,顶层代码
- 微任务:promise
- 执行栈: 执行栈是一个存储函数调用的栈结构,用于跟踪代码的执行顺序。当函数被调用时,它会被推入执行栈,并在执行完毕后被弹出。JavaScript 是单线程的,意味着同一时间只能执行一个任务,执行栈就是用来管理这些任务的执行顺序。
function foo() {
console.log("foo");
}
function bar() {
console.log("bar");
foo();
}
bar();
在这个例子中,执行栈会按照以下顺序进行操作:
- bar 函数被调用,推入执行栈。
- console.log("bar") 执行完毕,bar 函数从执行栈弹出。
- foo 函数被调用,推入执行栈。
- console.log("foo") 执行完毕,foo 函数从执行栈弹出。
ajax的实现原理
// 1. 创建ajax请求对象
const xhr = new XMLHttpRequest()
// 传递请求方式和url
const url = 'xxx';
xhr.open('get', url)
// 发送请求
xhr.send()
// 接收数据
xhr.onload = () => {
// todo
}
同源策略
同源必须是同协议,同域名,同端口
跨源访问的方法
- script标签嵌入跨域脚本
- link引入css
- image图片
- video/audio播放多媒体资源
- 通过object,embed,applet嵌入的插件
- @font-face引入的字体插件
- iframe载入资源
解决跨域的方法
- cors
- jsonp
function jsonp(options) {
// 创建script
const script = document.createElement('script')
script.src = options.url
// body中插入script
document.body.appendChild(script)
// script加载资源之后删除script
script.onload = () => {
document.body.removeChild(script)
}
}
jsonp({
url: 'http://127.0.0.1:5501/jsonp/'
})
// 这里声明fn是因为服务端会返回一个'fn()',fn就可以自动被调用了
function fn() {
console.log('调用')
}
简单请求/非简单请求
- 简单请求
-
- 请求方式为get head post
- 请求中没有自定义的头部
- content-type: (application/x-www-form-urlencoded、multipart/form-data、text/plain)只能是这3个中任意一个
- 非简单请求
-
- 请求方式由put, delete, post
- 请求中有自定义的头部
- content-type不是text/plain, multipart/form-data, application/x-www-form-urlencoded中的任意一个。
预检请求是确保服务器是否允许发送实际的请求。
面向对象
面向对象是一种编程方式,把代码封装起来,便于维护和扩展。
具有封装,继承,多态的三大特性。
封装
工厂模式
function car(color, name) {
let obj = new Object()
obj.name = name;
obj.color = color
return obj
}
let car1 = car('红', '宝马')
let car2 = car('蓝', '奔驰')
构造函数
function Car(color, name) {
this.name = name
this.color = color
}
let car1 = new Car('红', '宝马')
let car2 = new Car('蓝', '奔驰')
原型和原型链
在js里每个对象都有一个被称为原型的隐藏属性,原型里面包含了共享的属性和方法,可以被其他的对象继承。
对象都有一个隐式的链接指向它的原型,这个链接称为原型链。
- 实例对象通过__proto__指向原型对象
- 构造函数通过prototype指向原型对象
- 实例对象的constructor指向构造函数
console.log(obj.__proto__ === Fn.prototype) // true
console.log(obj.constructor === Fn) // true
console.log(Fn.prototype.constructor === Fn) // true
console.log(Fn.prototype === obj) // false
console.log(Fn.__proto__ === Function.prototype) // true
console.log(Object.__proto__ === Function.prototype) // true
console.log(Fn.__proto__ === Object.__proto__) // true
console.log(Fn.prototype.__proto__ === Object.prototype) // true
console.log(Fn.prototype.__proto__.__proto__) // null