JavaScript 知识点总结

105 阅读5分钟

堆和栈

是动态分布内存,大小不定也不会自动释放 ,栈内存中存放地址指向 堆内存中的对象.是按引用访问的。栈内存中存放的只是该对象的访问地址, 在堆内存中为这个值分配空间

类型(引用数据类型)

function Object Array

在内存中占据空间小、大小固定,他们的值保存在 栈空间, 是按 来访问

类型(基本数据类型)

Undefined | Null | Boolean | Number | String | Symbol

ObjectJavaScript 中所有对象的父对象
数据封装类对象:objectArrayBooleanNumberString
其他对象:FunctionArgumentsMathDateRegExpEr

思考

<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的值也会发生变化

深拷贝

实现方法

  1. 采用 递归 去拷贝所有层级属性

    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);
    
  2. 通过 JSON 对象

    function deepClone(obj){
        var _obj = JSON.stringify(obj)
        objClone = JSON.parse(_obj)
        return objClone
    }
    

    缺点:无法实现对 对象中方法的深拷贝,会显示 undefined

  3. jQuery extend

    var array = [1,2,3,4];
    var newArray = $.extend(true,[],array) // true为深拷贝, false为浅拷贝
    
  4. lodash 函数库实现

    let result = _.cloneDeep(test)
    

浅拷贝

实现方法

  1. 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
    
  2. Object.assgin方法

    var obj = {
        a:1,
        b:2
    }
    vra obj2 = Object.assgin(obj)
    obj2.a = 3
    console.log('obj',obj.a) // 3 
    

Ajax

异步,就是向服务器发送请求的时候,我们不必等待结果,而是可以同时做其他的事情,等到有了结果它自己会根据设定进行后续操作,与此同时,页面是不会发生整页刷新的,提高了用户体验。

创建Ajax的过程

  1. 创建XMLHttpRequest对象(异步调用对象)

    var xhr = new XMLHttpRequest()
    
  2. 创建新的Http请求(方法、URL、是否异步)

    xhr.open('get','example.php',false)
    
  3. 设置响应HTTP请求状态变化的函数

    onreadystatechange事件中readyState属性等于4.响应的HTTP状态为200(ok)或者304
    
  4. 发送http请求

    xhr.send(data)
    
  5. 获取异步调用返回的数据

注意:

  1. 页面初次加载时,尽量在web服务器一次性输出所有相关的数据,只在页面加载完成之后,用户进行操作时采用ajax进行交互。
  2. 同步ajax在IE上会产生页面假死的问题。所以建议采用异步ajax
  3. 尽量减少ajax请求次数
  4. ajax安全问题,对于敏感数据在服务器端处理,避免在客户端处理过滤。对于关键业务逻辑代码也必须放在服务器端处理

状态码

www.runoob.com/http/http-s…

作用域

 作用域链的原理和原型链很类似,如果这个变量在自己的作用域中没有,那么它会寻找父级的,直到最顶层。
  注意:JS没有块级作用域,若要形成块级作用域,可通过(function(){})();立即执行的形式实现

作用域就是代码的执行环境,全局执行环境就是全局作用域,函数的执行环境就是私有作用域,它们都是 栈内存。概况来说 作用域就是代码执行开辟栈内存

执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行黄静都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。

全局执行环境是最外围的执行环境。根据 ECMAScript 实现所在的宿主环境不同,表示的执行环境的对象也不一样

在 web 浏览器中,全局执行环境被认为是 window 对象,因此所有全局变量和函数都是作为 window 对象的属性和方法创建的
在 node 环境中,全局执行环境是 global 对象

作用域链

当代码在一个环境执行时,会创建变量对象的一个作用域链(作用域形成的链条)

  1. 作用域链的前端,始终都是当前执行的代码所在环境的变量对象
  2. 作用域链的下一个对象来自于外部环境,而下一个变量对象来自于下一个外部环境,一直到全局执行黄静
  3. 全局执行环境的变量对象始终都是作用域链上的最后一个对象
  4. 挡在内部函数中,需要方位一个变量的时候,首先会访问函数本身的变量对象,是否有这个变量,如果没有,那么会继续沿作用域链往上查找,知道全局作用域。如果在某个变量对象中找到则使用该变量对象中的变量值。由于变量的查找是沿着作用域链来实现的,所以也称作用域链为 变量查找的机制

this的理解

  1. this总是指向函数的直接调用者(而非间接调用者)

  2. 如果有new关键字,this指向 new 出来的那个对象

  3. this 表示当前对象的一个引用。在js中 this 会随着执行环境的改变而改变

    1. 在方法中,this 表示该方法所属的对象。
    2. 如果单独使用,this 表示全局对象。
    3. 在函数中,this 表示全局对象。
    4. 在函数中,在严格模式下,this 是未定义的(undefined)。
    5. 在事件中,this 表示接收事件的元素。
    6. 类似 call() 和 apply() 方法可以将 this 引用到任何对象。

原型与原型链

每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。如此层层递进,就构成了实例与原型的链条。这就是所谓的原型链的基本概念。

apply、call、bind

相同点:

  1. 三者都是用来改变函数的this对象的指向的
  2. 三者第一个参数都是this要指向的对象,也就是想指定的上下文
  3. 三者都可以利用后续参数传参
  4. 三者的参数不限定 string类型,允许是各种类型,包括函数、object等

区别:

  1. bind 是返回对应函数,便于稍后调用;apply、call则是立即调用。bind 返回的是一个新的函数,必须调用才会执行

  2. 回调执行使用 bind 方法,立即执行使用 apply/call

  3. 传参不同

    1. call 的参数是直接放进去,第二个及第n个参数全部都用逗号分隔
    2. apply的参数都必须放在一个数组里面
    3. 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. 其他总结

  1. CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
  2. ES6 的 module 是同步还是异步的?异步的。CommonJS 不支持异步
  3. ESModule 既然是编译时加载,那可以做到运行时加载吗?(webpack 有动态import 方式)
  4. ES6 module 语法是静态的,CommonJS 语法是动态的
  5. ES6 module 作为新的规范,可以替代之前的 AMD、CMD、CommonJS 作为浏览器和服务端的通用模块方案

浏览器存储

session

特点

  1. 存在服务器端
  2. 默认session的存储在服务器的 内存 中,每当一个新客户端发来请求,服务器都会开辟一个新的空间
  3. cookie一般会配合session使用