堆和栈
堆
是动态分布内存,大小不定也不会自动释放 ,栈内存中存放地址指向 堆内存中的对象.是按引用访问的。栈内存中存放的只是该对象的访问地址, 在堆内存中为这个值分配空间
类型(引用数据类型)
function Object Array
栈
在内存中占据空间小、大小固定,他们的值保存在 栈空间, 是按 值 来访问
类型(基本数据类型)
Undefined | Null | Boolean | Number | String | Symbol
Object 是 JavaScript 中所有对象的父对象
数据封装类对象:object、Array、Boolean、Number 和 String
其他对象:Function、Arguments、Math、Date、RegExp、Er
思考
<b>为了使程序运行时占用的内存最小,通常要实现垃圾回收机制。</b>
<b>当一个方法执行时,每个方法都会建立自己的内存栈,在这个方法内定义的变量将会逐个放入这块栈存里,随着方法的执行结束,这个方法的栈存也将自然销毁了。因此,所有在方法中定义的变量都是放在栈内存中的;</b>
<b>当我们在程序中创建一个对象时,这个对象将被保存到运行时数据区中,以便反复利用(因为对象的创建成本开销较大),这个运行时数据区就是堆内存。堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(方法的参数传递时很常见),则这个对象依然不会被销毁,只有当一个对象没有任何引用变量引用它时,系统的垃圾回收机制才会在核实的时候回收它。</b>
基本数据类型存储在栈中,引用数据类型(对象)存储在堆中,指针放在栈中。
两种类型的区别是:存储位置不同;原始数据类型直接存储在栈中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;引用数据类型存储在堆中的对象,占据空间大、大小不固定,如果存储在栈中,将会影响程序运行的性能
引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
栈(stack):由编译器自动分配释放,存放函数的参数值,局部变量等;
堆(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由操作系统释放。
代码
// demo1
var a = 1;
var b = a;
b = 2
// a的值是什么
console.log('a',a,'b',b)
// demo2
var m = {a:1,b:2};
var n = m;
n.a = 3;
// m.a 的值是什么?
console.log('m',m,'n',n)
深拷贝和浅拷贝
如何区分
简单点来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,如果没变那就是深拷贝
// demo2
var m = {a:1,b:2};
var n = m;
n.a = 3;
// m.a 的值是什么?
console.log('m',m,'n',n)
// 此时m.a 的值是发生变化为 3;因为 m 和 n 指向的是同一个地址,当n.a的值发生变化时,m.a的值也会发生变化
深拷贝
实现方法
-
采用 递归 去拷贝所有层级属性
function deepClone(obj){ let objClone = Array.isArray(obj)?[]:{}; if(obj && typeof obj==="object"){ for(key in obj){ if(obj.hasOwnProperty(key)){ //判断ojb子元素是否为对象,如果是,递归复制 if(obj[key]&&typeof obj[key] ==="object"){ objClone[key] = deepClone(obj[key]); }else{ //如果不是,简单复制 objClone[key] = obj[key]; } } } } return objClone; } let a=[1,2,3,4], b=deepClone(a); a[0]=2; console.log(a,b); -
通过 JSON 对象
function deepClone(obj){ var _obj = JSON.stringify(obj) objClone = JSON.parse(_obj) return objClone }缺点:无法实现对 对象中方法的深拷贝,会显示 undefined
-
jQuery extend
var array = [1,2,3,4]; var newArray = $.extend(true,[],array) // true为深拷贝, false为浅拷贝 -
let result = _.cloneDeep(test)
浅拷贝
实现方法
-
for in 只循环第一层
function simpCopy(obj){ var obj2 = Array.isArray(obj) ? [] :{} // 判断是否为数组 for(let i in obj){ obj2[i] = obj[i] } return obj2 } var test = { a:1, b:2, c:{ d:3 } } var test2 = simpCopy(test) test2.a = 4 test2.c.d = 5 console.log('test',test,'test2',test2) // test.a === 4; test.c.d ===5 -
Object.assgin方法
var obj = { a:1, b:2 } vra obj2 = Object.assgin(obj) obj2.a = 3 console.log('obj',obj.a) // 3
Ajax
异步,就是向服务器发送请求的时候,我们不必等待结果,而是可以同时做其他的事情,等到有了结果它自己会根据设定进行后续操作,与此同时,页面是不会发生整页刷新的,提高了用户体验。
创建Ajax的过程
-
创建XMLHttpRequest对象(异步调用对象)
var xhr = new XMLHttpRequest() -
创建新的Http请求(方法、URL、是否异步)
xhr.open('get','example.php',false) -
设置响应HTTP请求状态变化的函数
onreadystatechange事件中readyState属性等于4.响应的HTTP状态为200(ok)或者304 -
发送http请求
xhr.send(data) -
获取异步调用返回的数据
注意:
- 页面初次加载时,尽量在web服务器一次性输出所有相关的数据,只在页面加载完成之后,用户进行操作时采用ajax进行交互。
- 同步ajax在IE上会产生页面假死的问题。所以建议采用异步ajax
- 尽量减少ajax请求次数
- ajax安全问题,对于敏感数据在服务器端处理,避免在客户端处理过滤。对于关键业务逻辑代码也必须放在服务器端处理
状态码
作用域
作用域链的原理和原型链很类似,如果这个变量在自己的作用域中没有,那么它会寻找父级的,直到最顶层。
注意:JS没有块级作用域,若要形成块级作用域,可通过(function(){})();立即执行的形式实现
作用域就是代码的执行环境,全局执行环境就是全局作用域,函数的执行环境就是私有作用域,它们都是 栈内存。概况来说 作用域就是代码执行开辟栈内存
执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行黄静都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。
全局执行环境是最外围的执行环境。根据 ECMAScript 实现所在的宿主环境不同,表示的执行环境的对象也不一样
在 web 浏览器中,全局执行环境被认为是 window 对象,因此所有全局变量和函数都是作为 window 对象的属性和方法创建的
在 node 环境中,全局执行环境是 global 对象
作用域链
当代码在一个环境执行时,会创建变量对象的一个作用域链(作用域形成的链条)
- 作用域链的前端,始终都是当前执行的代码所在环境的变量对象
- 作用域链的下一个对象来自于外部环境,而下一个变量对象来自于下一个外部环境,一直到全局执行黄静
- 全局执行环境的变量对象始终都是作用域链上的最后一个对象
- 挡在内部函数中,需要方位一个变量的时候,首先会访问函数本身的变量对象,是否有这个变量,如果没有,那么会继续沿作用域链往上查找,知道全局作用域。如果在某个变量对象中找到则使用该变量对象中的变量值。由于变量的查找是沿着作用域链来实现的,所以也称作用域链为 变量查找的机制
this的理解
-
this总是指向函数的直接调用者(而非间接调用者)
-
如果有new关键字,this指向 new 出来的那个对象
-
this 表示当前对象的一个引用。在js中 this 会随着执行环境的改变而改变
- 在方法中,this 表示该方法所属的对象。
- 如果单独使用,this 表示全局对象。
- 在函数中,this 表示全局对象。
- 在函数中,在严格模式下,this 是未定义的(undefined)。
- 在事件中,this 表示接收事件的元素。
- 类似 call() 和 apply() 方法可以将 this 引用到任何对象。
原型与原型链
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。如此层层递进,就构成了实例与原型的链条。这就是所谓的原型链的基本概念。
apply、call、bind
相同点:
- 三者都是用来改变函数的this对象的指向的
- 三者第一个参数都是this要指向的对象,也就是想指定的上下文
- 三者都可以利用后续参数传参
- 三者的参数不限定 string类型,允许是各种类型,包括函数、object等
区别:
-
bind 是返回对应函数,便于稍后调用;apply、call则是立即调用。bind 返回的是一个新的函数,必须调用才会执行
-
回调执行使用 bind 方法,立即执行使用 apply/call
-
传参不同
- call 的参数是直接放进去,第二个及第n个参数全部都用逗号分隔
- apply的参数都必须放在一个数组里面
- bind 参数和call 一样,但是返回的是函数
JS继承
前言
创建的子类将继承超类的所有属性和方法,包括构造函数及方法的实现。所有属性和方法都是公用的,因此子类可直接访问这些方法。子类还可添加超类中没有的新属性和方法,也可以覆盖超类的属性和方法。
继承的方式
JavaScript中的继承机制并不是明确规定的,而是通过模仿实现的。这意味着所有的继承细节并非完全由解释程序处理。作为开发者,有权决定最适用的继承方式
对象冒充
原理:
构造函数使用this关键字给所有属性和方法赋值(即采用类声明的构造函数方式)。应为构造函数只是一个函数,所以可使 Class A 构造函数称为 Class B的方法,然后调用它。class B 就会收到ClassA 的构造函数中定义的属性和方法
实现:
关键字 this 引用的是构造函数当前创建的对象。不过在这个方法中,this 指向的所属的对象。这个原理是把classA 作为常规函数来建立继承机制,而不是作为构造函数。如下使用构造函数 classB可以实现继承机制
function classA(sColor){
this.color = sColor;
this.sayColor = function(){
alert(this.color)
}
}
function classB(sColor){
this.newMethod = classA;
this.newMethod(sColor);
delete this.newMethod;
}
在这段代码中,为 classA 赋予了方法 newMethod (函数名只是指向它的指针)。然后调用该方法,传递给它的是 classB 构造函数的参数 sColor。 最后一行代码删除了对 classA 的引用,这样以后就不能再调用它
注意:
所有新属性和新方法都必须在删除了新方法的代码行后定义。否则,可能会覆盖超类的相关属性和方法
function classA(sColor){
this.color = sColor;
this.sayColor = function(){
alert(this.color)
}
}
function ClassB(sColor, sName) {
this.newMethod = ClassA;
this.newMethod(sColor);
delete this.newMethod;
this.name = sName;
this.sayName = function () {
alert(this.name);
};
}
var objA = new ClassA("blue");
var objB = new ClassB("red", "John");
objA.sayColor(); //输出 "blue"
objB.sayColor(); //输出 "red"
objB.sayName(); //输出 "John"
对象冒充多重继承
例如,如果存在两个类 ClassX 和 ClassY,ClassZ 想继承这两个类,可以使用下面的代码:
function ClassZ() {
this.newMethod = ClassX;
this.newMethod();
delete this.newMethod;
this.newMethod = ClassY;
this.newMethod();
delete this.newMethod;
}
注意:
这里存在一个弊端,如果存在两个类 ClassX 和 ClassY 具有同名的属性和方法,ClassY 具有高优先级。因为它从后面的类继承。除了这点小问题之外,用对象冒充继承机制轻而易举
call方法
call()方法与经典的对象冒充方法最相似的方法。它的第一个参数用作 this 的对象。其他参数都直接传递给函数自身。例如:
function sayColor(sPrefix,sSuffix) {
alert(sPrefix + this.color + sSuffix);
};
var obj = new Object();
obj.color = "blue";
sayColor.call(obj, "The color is ", "a very nice color indeed.");
// The color is blue, a very nice color indeed.
要与继承机制的对象冒充方法一起使用该方法,只需要将前三行的赋值、调用和删除代码替换即可:
function classB(sColor,sName){
classA.call(this,sColor);
this.name = sName;
this.sayName = function(){
alert(this.name)
}
}
function classA(sColor){
this.color = sColor;
this.sayColor = function(){
alert(this.color)
}
}
apply方法
apply()方法有两个参数,用作 this 的对象和要传递给函数的参数的数组。例如:
function sayColor(sPrefix,sSuffix) {
alert(sPrefix + this.color + sSuffix);
};
var obj = new Object();
obj.color = "blue";
sayColor.apply(obj, new Array("The color is ", "a very nice color indeed."));
该方法也用于替换前三行的赋值、调用和删除新方法的代码
function classB(sColor,sName){
classA.apply(this,new Array(sColor));
this.name = sName;
this.sayName = function(){
alert(this.name)
}
}
同样的,第一个参数仍是 this,第二个参数只是只有一个值 color 的数组,可以把 classB 的整个 argumens 对象作为第二个参数传递给 apply()方法:
function classB(sColor,sName){
classA.apply(this,arguments);
this.name = sName;
this.sayName = function(){
alert(this.name)
}
}
原型链
function ClassA(){}
ClassA.prototype.color = 'blue';
ClassA.prototype.sayColor = function(){
alert(this.color);
}
function ClassB(){}
ClassB.prototype = new ClassA();
ClassB.prototype.name = ''
ClassB.prototype.sayName = function(){
alert(this.name)
}
测试用例
var objA = new ClassA();
var objB = new ClassB();
objA.color = "blue";
objB.color = "red";
objB.name = "John";
objA.sayColor();
objB.sayColor();
objB.sayName();
混合方式
function ClassA(sColor) {
this.color = sColor;
}
ClassA.prototype.sayColor = function () {
alert(this.color);
};
function ClassB(sColor, sName) {
ClassA.call(this, sColor);
this.name = sName;
}
ClassB.prototype = new ClassA();
ClassB.prototype.sayName = function () {
alert(this.name);
};
var objA = new ClassA("blue");
var objB = new ClassB("red", "John");
objA.sayColor(); //输出 "blue"
objB.sayColor(); //输出 "red"
objB.sayName(); //输出 "John"
前端模块化
CommonJS
node js 是 commonJS 规范的主要实践者
提供支持:module、exports、require、global
实际使用时,使用 module.exports 定义当前模块对外输出的接口(不推荐直接使用 exports),用require 加载模块
注意:
commonJS 用同步的方式加载模块。在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,更合理的方案是使用异步加载。
Webpack 不支持使用 commonjs 模块来完成 tree-shaking。
AMD 和 require.js
AMD 规范采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。JavaScript 库 require.js 实现了 AMD 规范的模块化:用 require.config() 指定引用路径等,用 define() 定义模块,用 require() 加载模块。
CMD 和 sea.js
CMD是另一种js模块化方案,它与AMD很类似,不同点在于:AMD 推崇依赖前置、提前执行,在定义模块的时候就要声明其依赖的模块,CMD推崇依赖就近、延迟执行,只有在用到某个模块的时候再去require,此规范其实是在sea.js推广过程中产生的。
/** AMD写法 **/
define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) {
// 等于在最前面声明并初始化了要用到的所有模块
a.doSomething();
if (false) {
// 即便没用到某个模块 b,但 b 还是提前执行了
b.doSomething()
}
});
/** CMD写法 **/
define(function(require, exports, module) {
var a = require('./a');
//在需要时申明
a.doSomething();
if (false) {
var b = require('./b');
b.doSomething();
}
});
/** sea.js **/
// 定义模块
math.jsdefine(function(require, exports, module) {
var $ = require('jquery.js');
var add = function(a,b){
return a+b;
}
exports.add = add;
});
// 加载模块
seajs.use(['math.js'], function(math){
var sum = math.add(1+2);
});
ES5 Module
export default
import xx from xx
ES6 模块与 CommonJS 模块的差异
1. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
- ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令
import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
2. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
- 运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。
- 编译时加载: ES6 模块不是对象,而是通过
export命令显式指定输出的代码,import时采用静态命令的形式。即在import时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”。
3. 其他总结
- CommonJS 加载的是一个对象(即
module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。 - ES6 的 module 是同步还是异步的?异步的。CommonJS 不支持异步
- ESModule 既然是编译时加载,那可以做到运行时加载吗?(webpack 有动态import 方式)
- ES6 module 语法是静态的,CommonJS 语法是动态的
- ES6 module 作为新的规范,可以替代之前的 AMD、CMD、CommonJS 作为浏览器和服务端的通用模块方案
浏览器存储
session
特点
- 存在服务器端
- 默认session的存储在服务器的 内存 中,每当一个新客户端发来请求,服务器都会开辟一个新的空间
- cookie一般会配合session使用