Skip to content

JavaScript基础相关

zhaoluting edited this page Dec 27, 2021 · 9 revisions

数据类型相关

分类

  • 基本类型(简单/原始/值类型):String、Number、Boolean、Undefined(已声明但没初始化)、Null(不是对象)、Symbol(创建后独一无二且不可变的数据类型)
  • 引用类型(复杂/对象类型):Object、Array、Date、Function等。

判断方法

值类型推荐用typeof判断,内置引用类型推荐用toString,自定义引用类型推荐用constructor。数组推荐直接用Array.isArray。

typeof 判断值类型

typeof 返回一个表示数据类型的字符串,但只能区分基本类型,对于引用类型都会统一返回'object'。

typeof能返回7种数据类型:number、string、boolean、undefined、symbol、object、function

typeof '';             // string
typeof 1;              // number
typeof Symbol();       // symbol
typeof true;           // boolean
typeof undefined;      // undefined
typeof null;           // object(bug,null的底层二进制表示全是0,前3位为0会被判为object)
typeof [] ;            // object,未具体
typeof new Function(); // function
typeof String;         // function
typeof new Date();     // object,未具体
typeof new RegExp();   // object,未具体

instanceof 判断引用类型

instanceof运算符可以用来判断某个构造函数的prototype属性是否存在于要检测对象的原型链上。

  • instanceof 不能获取对象的具体类型。
  • 如果网页中包含多个框架,就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的构造函数。如果从一个框架向另一框架传入一数组,那么传入的数组与在第二个框架中原生创建的数组分别具有各自不同的构造函数。为了解决这个问题,可以使用Array.isArray() 方法。
function Person(){};
new Person() instanceof Person; //true
new Person() instanceof Object; //true

new Number(1) instanceof Number //true
[] instanceof Array;            //true
[] instanceof Object;           //true
new Date() instanceof Date;     //true
new Date() instanceof Object;   //true

instanceof的左值一般是一个对象,右值一般是一个构造函数,用来判断左值是否是右值的实例。它的内部实现原理是这样的:

// 设 L instanceof R 
// 沿着L的__proto__一直寻找到原型链末端,直到等于R.prototype为止。
 L.__proto__.__proto__ ..... === R.prototype
// 最终返回true or false

toString (推荐)

toString是Object原型对象上的一个方法,该方法默认返回toString运行时this指向的对象类型,基本上所有对象的类型都可以通过这个方法获取到。

Object.prototype.toString.call('') ;         // [object String]
Object.prototype.toString.call(null) ;       // [object Null]
Object.prototype.toString.call(new Date()) ; // [object Date]
Object.prototype.toString.call(document) ;   // [object HTMLDocument]
Object.prototype.toString.call(window) ;     //[object global] window是全局对象global的引用

constructor(推荐)

constructor是原型对象的属性指向构造函数。当一个函数F被定义时,JS引擎会为F添加prototype原型,然后再在 prototype上添加一个 constructor 属性,并让其指向 F 的引用。

''.constructor === String               //true
new Number(1).constructor === Number    //true
new Date().constructor === Date         //true
new Function().constructor === Function //true
document.constructor === HTMLDocument   //true
  • null和undefined是无效对象,因此无法使用 constructor。
  • Function的constructor不稳定,当开发者重写prototype后,原有的constructor引用会丢失,constructor会默认为Object

其他

  • Array.isArray() 判断是否为数组
  • jquery.type() 基于ES5的Object.prototype.toString.call进一步封装

转换

var a = 42;
var b = a + "";      // 隐式
var c = String(a); // 显式
  • toString()、toNumber()、toBoolean()、、、
  • JSON.stringify()

为什么JS中0.1+0.2 != 0.3?(小数精度丢失)

原因:计算机内部的信息都是由二进制方式表示的,即0和1组成的各种编码,但由于某些浮点数没办法用二进制准确的表示出来,也就带来了一系列精度问题。

如何解决

  • 通过升幂,将小数转为计算机能精准识别的整数,计算完再降幂。例如: (0.1 * 10 + 0.2 * 10) / 10
  • 使用tofixed函数,对于金额数要求高的场景不建议。
  • 在 ES6 中为我们提供了一个最小精度属性:Number.EPSILON
function numbersEqual(a,b){
    return Math.abs(a-b)<Number.EPSILON;
}
var a=0.1+0.2 b=0.3;
console.log(numbersEqual(a,b));    //true

拷贝

  • 浅拷贝:Object.assign()、展开运算符、concat()数组浅拷贝、slice()数组浅拷贝
  • 深拷贝:JSON.parse(JSON.stringify(obj))(不支持拷贝有循环引用的对象,不支持拷贝Function、RegExp、Date、Set、Symbol等特殊对象)、手动实现、

JavaScript原型、原型链及继承相关

javaScript没有提供传统面向对象语言中的类式继承,而是通过原型委托的方式来实现对象与对象之间的继承。

参考文章:继承与原型链JS实现继承的几种方式最详尽的 JS 原型与原型链终极详解,没有「可能是」。

  • 原型:所有函数都拥有prototype属性,在函数的创建过程中由js编译器自动添加的一个默认属性。JavaScript对象是通过引用来传递的,当修改原型时,与之相关的对象也会继承这一改变。
  • prototype(显式原型)
    • prototype属性是函数对象特有属性,是一个指向原型对象的指针,原型对象包含所有实例共享的属性和方法。
    • 本质上来讲,原型对象是一个普通对象,是函数对象的构造函数创建的一个实例。
    • 显式原型用来实现基于原型的继承与属性的共享。
    • 存在一个特例Function,Function.prototype是原型对象,本质却是函数对象。作为一个函数对象,又没有prototype属性。
  • __proto__(隐式原型)
    • 一个对象的隐式原型指向构造该对象的构造函数的原型,这也保证了实例能够访问在构造函数原型中定义的属性和方法。
    • Foo.__proto__ = Foo.constructor.prototype
    • 隐式原型的作用:原型链指针,构成原型链,同样用于实现基于原型的继承。
  • constructor(构造函数)
    • 在默认情况下,所有的原型对象都会自动获得一个constructor属性,这个属性指向prototype属性所在的函数。
    • Foo.prototype.constructor == Foo
  • 原型链
    • __proto__是任何对象都有的属性(null和undefined除外),而JS里万物皆对象,所以会形成一条__proto__连起来的链。
    • 当JS引擎查找对象属性时,先查找对象本身是否存在该属性,不存在时会在原型链上查找,但不会查找自身的prototype。
    • 原型链是实现继承的主要方法。其基本思想是:利用原型让一个引用类型继承另一个引用类型的属性和方法。
  • 函数对象:由函数new Function()创建得来的对象都是函数对象。函数对象拥有__proto__属性和prototype属性。
  • 普通对象:除函数对象外的对象都是普通对象,且只有指向原型链的__proto__属性,没有prototype属性。
  • 概览
    • Function原型链的尽头是Object,而Object原型链尽头是null。
    • Object.prototype.__proto__ === null
    • 所有函数对象的__proto__都指向Function.prototype,它是一个空函数(Empty function)。
    • 原型对象的__proto__都指向Object.prototype

继承的几种方法

  • ES6继承是在父类创建this对象,在子类constructor中来修饰父类的this,ES5是在子类创建this,将父类的属性方法绑定到子类。

1、构造继承

// 使用call或apply方法,将父对象的构造函数绑定在子对象上
function Animal(){
    this.species = "动物";
}
function Cat(name,color){
    Animal.apply(this, arguments);
    this.name = name;
    this.color = color;
}
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物

2、原型继承

function Animal(){
    this.species = "动物";
}
function Cat(name,color){
    this.name = name;
    this.color = color;
}
// 将Cat的prototype对象指向一个Animal的实例
Cat.prototype = new Animal();
// 任何一个prototype对象都有一个constructor属性,指向它的构造函数。
// 必须手动纠正,将Cat.prototype对象的constructor值改为Cat。
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物

3、拷贝继承

// 把父对象的所有属性和方法,拷贝进子对象
function Animal(){ }
Animal.prototype.species = "动物";
// 这个函数的作用,就是将父对象的prototype对象中的属性,一一拷贝给Child对象的prototype对象。
function extend2(Child, Parent) {
    var p = Parent.prototype;
    var c = Child.prototype;
    for (var i in p) {
        c[i] = p[i];
    }
    c.uber = p;
}
// 使用的时候,这样写:
extend2(Cat, Animal);
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物

4、组合继承(推荐)

function Animal(){
    this.species = "动物";
}
// 通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat = new Cat();

5、ES5 Object.create

// Object.create 是原型模式的天然实现,但效率不是最高。
function Animal(){
    this.species = "动物";
}
function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;
var cat = new Cat();

6、ES6 class关键字

class Animal{
	constructor(name) {
		this.species = "动物";
		this.name = name;
	}
    getName() { return this.name }
}
class Cat extends Animal {
	constructor(name) {
		super(name);
	}
	speak() { return "喵~" } 
}
var cat = new Cat("Tom");

JS中对象被创建的方式

  1. 由构造函数构造出来,即new 的方式
// new的实质
var obj  = {}; // 创建一个新对象
obj.__proto__ = Base.prototype; // __proto__指向了Base.prototype对象
Base.call(obj); // 将构造函数的作用域赋给新对象并执行构造函数中的代码
  1. ES5中的Object.create()
// Object.create的实质
// new关键字创建的对象会保留原构造函数的属性,而Object.create不会。
Object.create = function(Base) {
    function f(){}; // 创建一个空函数
    f.prototype = Base;  // 函数的prototype指向Base函数
    return new f(); // 让实例的_proto_指向函数f的prototype,也就是Base函数
}
  1. 对象字面量的方式
// 字面量的方式是一种为了开发人员更方便创建对象的一个语法糖,本质就是:
var o = new Object(); 
o.xx = xx;
o.yy=yy;

执行上下文

简单解释

  • 执行上下文的三种类型:全局环境、函数环境、eval环境。
  • 每一个执行上下文,都有三个重要属性:变量对象、作用域链、this
  • 浏览器首次载入脚本,它将创建全局执行上下文,并压入执行栈栈顶(不可被弹出);然后每进入其它作用域就创建对应的执行上下文并把它压入执行栈的顶部;一旦对应的上下文执行完毕,就从栈顶弹出,并将上下文控制权交给当前的栈;这样依次执行,最终都会回到全局执行上下文。
  • 生命周期:创建阶段(创建变量对象和作用域链、确定this指向)、执行阶段、回收阶段

作用域

  • 作用域:作用域规定了如何查找变量,即确定当前执行代码对变量的访问权限。
  • 词法作用域:JavaScript采用的是词法作用域,定义过程发生在代码的书写阶段,词法作用域由写代码时将变量和块作用域写在哪里决定的。
  • 动态作用域:动态作用域是在运行时确定的,关注函数从何处调用,像this机制。
  • 作用域链:全局函数无法查看局部函数的内部细节,但局部函数可以查看其上层的函数细节,直至全局细节。当需要从局部函数查找某一属性或方法时,如果当前作用域没有找到,就会上溯到上层作用域查找,直至全局函数,这种组织形式就是作用域链。

this

this是执行上下文环境的一个属性,而不是某个变量对象的属性,总是指向一个对象。

  • 在全局环境下,this始终指向全局对象window;
  • 当函数作为对象的方法被调用时,this指向该对象;
  • 当函数不作为对象属性被调用时(普通函数方式),this总是指向它的直接调用者;
    • 非严格模式下,this默认指向全局对象window;
    • 而严格模式下,this为undefined;
  • 构造函数下,this与被创建的新对象绑定;
  • DOM事件,this指向触发事件的元素;
  • 箭头函数不绑定this,它会在定义函数时捕获其所在(即定义的位置)上下文的this值,作为自己的this值,无法更改,不能用call()或者apply()进行绑定。

改变this指向的几种方法以及之间的区别

1、 call,apply 相同点

  • 方法的含义、功能都是一样的,即调用一个对象的一个方法,用另一个对象替换当前对象,改变this的指向。
  • 在特定的作用域中调用函数,等于设置函数体内this对象的值,以扩充函数赖以运行的作用域。都是为了实现多重继承
  • 第一个参数都是要绑定的上下文,后面的参数是要传递给调用该方法的函数的。
var People = {
  sayHello: function(arg1, arg2) {
    console.log('你好,' + this.name + ',' + arg1 + arg2);
  }
}
var me = {
  name: '顾客'
}
People.sayHello.call(me, '欢迎光临', '!')  // 你好,顾客,欢迎光临!
People.sayHello.apply(me, ['欢迎光临', '~'])  // 你好,顾客,欢迎光临~

不同点(仅在于传入参数不同):

  • call可以传入多个参数;apply只能传入两个参数,其第二个参数往往是作为数组形式传入。
  • call是包装在apply上的语法糖。

2、bind

  • bind第一个参数是this的指向,从第二个参数开始是接收的参数列表。bind 方法传递给调用函数的参数可以逐个列出,也可以写在数组中。
  • 创建一个函数的实例,其this值会被绑定给传入bind()函数的值,然后将函数返回。
  • bind返回对应的函数,便于稍后调用;apply,call则是立即调用。
  • 通过bind方法绑定后,函数内部的this值将被永远绑定在其第一个参数对象上,而无论其在什么情况下被调用。
var People = {
  sayHello: function(arg1, arg2) {
    console.log('你好,' + this.name + ',' + arg1 + arg2);
  }
}
var me = {
  name: '顾客'
}
People.sayHello.bind(me, '欢迎光临', '!')()  // 你好,顾客,欢迎光临!
People.sayHello.bind(me, '欢迎光临')('!')  // 你好,顾客,欢迎光临!
People.sayHello.bind(me)('欢迎光临', '!')  // 你好,顾客,欢迎光临!
var personSay = People.sayHello.bind(me, '欢迎光临');
personSay('~'); // 你好,顾客,欢迎光临~

手动实现bind MDN官方实现方法

Function.prototype.bind = function (context) {
    // 在整个bind函数作用域中this指代调用它的对象,且该对象必须为函数。
    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }
    
    // 保留原函数
    var self = this;
    
    // arguments为传入bind函数的参数对象,获取从第二个参数到最后一个参数。
    // 由于arguments是伪数组对象,故使用数组的slice方法。
    var args = Array.prototype.slice.call(arguments, 1);
    
    var fNOP = function () {};

    // fBound为最终bind函数返回的新函数,通过apply方法将函数上下文改为context。
    var fBound = function () {
        // this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用
        self.apply(this instanceof fBound
        ? this 
        : context,
        // 将bind函数中的参数和执行bind函数返回的函数传入的参数进行合并。
        // 注意:这里的arguments是fBound的传入参数
        args.concat(Array.prototype.slice.call(arguments)));
    }
    
    // 维护原型关系。修改返回函数的prototype为绑定函数的prototype,实例就可以继承函数原型中的值。
    if (this.prototype) {
        fNOP.prototype = this.prototype;
    }
    
    // 使fBound.prototype是fNOP的实例,因此返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
    fBound.prototype = new fNOP();

    return fBound;
}

变量提升

参考:js之变量提升

  • js引擎在读取js代码时会进行两个步骤,第一个步骤是解释,第二个步骤是执行。所谓解释就是会先通篇扫描所有的Js代码,然后把所有声明提升到顶端,第二步是执行,执行就是操作一类的。
  • JavaScript变量有创建(create)、初始化(initialize) 和赋值(assign)三个阶段。变量只要在初始化之后才能访问,否则就会报错。
  • var 是函数作用域,即function内的变量不可以在function外访问到;var 会把变量创建和初始化过程提升到函数顶部。
  • let、const是块级作用域,即{}内的变量不可以在{}外访问到。let、const只提升了创建过程。
  • const 和 let 只有一个区别,就是const只有创建、初始化,没有赋值过程。
  • function 的创建、初始化、赋值都被提升了。

var 声明的「创建、初始化和赋值」过程

function fn(){
  var x = 1
  var y = 2
}
fn()

在执行 fn 时,会有以下过程:

  • 进入 fn,为 fn 创建一个环境。
  • 找到 fn 中所有用var声明的变量,在这个环境中「创建」这些变量。
  • 将这些变量「初始化」为 undefined。
  • 开始执行代码
  • x = 1 将 x 变量「赋值」为 1
  • y = 2 将 y 变量「赋值」为 2

也就是说 var 声明会在代码执行之前就将「创建变量,并将其初始化为 undefined」。

let 声明的「创建、初始化和赋值」过程

{
  let x = 1
  x = 2
}

{} 里面的过程:

  • 找到所有用 let 声明的变量,在环境中「创建」这些变量
  • 开始执行代码(注意现在还没有初始化)
  • 执行 x = 1,将x「初始化」为1(这并不是一次赋值,如果代码是 let x,就将 x 初始化为 undefined)
  • 执行 x = 2,对 x 进行「赋值」

函数提升

  • 函数提升在变量提升之上。
  • js中创建函数有两种方式:函数声明式和函数字面量式。只有函数声明才存在函数提升。如:
console.log(f1); // function f1() {}   
console.log(f2); // undefined  
function f1() {}
var f2 = function() {}

function 声明的「创建、初始化和赋值」过程

fn2()
function fn2(){
  console.log(2)
}

JS 引擎会有一下过程:

  • 找到所有用 function 声明的变量,在环境中「创建」这些变量。
  • 将这些变量「初始化」并「赋值」为 function(){ console.log(2) }。
  • 开始执行代码 fn2()

也就是说function声明会在代码执行之前就「创建、初始化并赋值」。

闭包

定义:指有权访问另一个函数作用域中变量的函数,可以在一个内层函数中访问到其外层函数的作用域。 常见方式:函数内创建内部函数,外部通过内部函数访问此函数的局部变量。

作用

  1. 突破作用链域,将函数内部的变量和方法传递到外部。
  2. 延续局部变量的生存周期,防止被垃圾回收机制清除。

应用

  1. 将不需要暴露在全局的变量封装成“私有变量”;
  2. 缓存功能,减少重复计算或防止数据丢失;
<!--li节点的onclick事件都能正确的弹出当前被点击的li索引-->
  <ul id="testUL">
     <li> index = 0</li>
     <li> index = 1</li>
     <li> index = 2</li>
 </ul>
 <script type="text/javascript">
 var nodes = document.getElementsByTagName("li");
 for(i = 0;i<nodes.length;i+= 1){
    nodes[i].onclick = (function(i){
        return function() {
            console.log(i);
        } //不用闭包的话,值每次都是3
    })(i);
 }
 </script>
var Counter = (function() {
  var privateCounter = 0;
  return {
    increment: function() {
      privateCounter += 1;
    },
    value: function() {
      return privateCounter;
    }
  }
})();
console.log(Counter.value()); /* logs 0 */
Counter.increment();
console.log(Counter.value()); /* logs 1 */

异步编程

在JavaScript中异步编程的方法:回调、事件监听、发布/订阅、promise、generator、async/await、第三方async库等。

回调(callback)

定义:回调函数就是一个参数,将这个函数作为参数传到另一个函数里面,当那个函数执行完之后,再执行传进去的这个函数。这个过程就叫做回调。

Promise

30分钟,让你彻底明白Promise原理
Promise对象是为了解决"回调函数地狱"(callback hell)而提出的。允许将回调函数的嵌套改成链式调用。

  • 一个promise必须有3个状态,pending,fulfilled(resolved),rejected。当处于pending状态的时候,可以转移到fulfilled(resolved)或者rejected状态。当处于fulfilled(resolved)状态或者rejected状态的时候,就不可变。
  • 一个promise必须有一个then方法,将要在Promise异步操作成功时执行的回调放入callbacks队列,即注册回调函数。then方法接受两个参数:promise.then(onFulfilled,onRejected)。其中onFulfilled方法表示状态从pending——>fulfilled(resolved)时所执行的方法,而onRejected表示状态从pending——>rejected所执行的方法。
  • 创建Promise实例时传入的函数会被赋予一个函数类型的参数,即resolve,它接收一个参数value,代表异步操作返回的结果,当一步操作执行成功后,用户会调用resolve方法,这时候其实真正执行的操作是将callbacks队列中的回调一一执行
  • 为了实现链式调用,then方法必须返回一个promise。promise2 = promise1.then(onFulfilled,onRejected)
  • 通过setTimeout机制,将resolve中执行回调的逻辑放置到JS任务队列末尾,以保证在resolve执行时,then方法的回调函数已经注册完成.

Generator(ES6)

ES6入门-Generator 函数的语法

  • Generator(生成器)函数是 ES6 提供的一种异步编程解决方案,是一个状态机,封装了多个内部状态,执行 Generator 函数会返回一个遍历器对象。

  • Generator 函数有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。

  • Generator除了return语句,还可以用yield多次返回值。Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next()方法可以恢复执行。

  • Generator为 JavaScript 提供了手动的“惰性求值”的语法功能。也可以不用yield表达式,这时就变成了一个单纯的暂缓执行函数。

设计模式

《JavaScript设计模式与开发实践》笔记

相关概念

  • JavaScript是一门基于原型的面向对象语言,它的对象系统是使用原型模式搭建的,没有类的概念,将函数作为一等对象。
  • 很多设计模式都是通过闭包和高阶函数实现的。
  • 所有设计模式的实现都遵循一条原则:找出程序中变化的部分,并将变化封装起来,注重提高可复用和可维护性,保证代码的高内聚低耦合。
  • 鸭子类型:只关注对象行为,不关注对象本身。
  • 抽象:提取现实世界中某事物的关键特性,为该事物构建模型的过程。得到的抽象模型(包含属性、操作)称之为类。
  • 封装:使类具有独立性和隔离性,保证类的高内聚。只暴露给类外部或者子类必须的属性和操作。
  • 继承:对现有类的一种复用机制。一个类如果继承现有的类,则这个类将拥有被继承类的所有非私有特性(属性和操作)。
  • 多态:同一操作作用于不同对象上,可产生不同的解释和不同的执行结果。通常使用继承来得到多态效果。
  • 单一职责原则(SRP):一个对象(方法)只做一件事。(低耦合)
  • 最少知识原则(LKP)/迪米特法则:一个软件实体(类、模块、函数等)应当尽可能少地与其他实体发生相互作用。(对象之间减少交互)
  • 开放-封闭原则:软件实体应该是可以扩展的,但是不可修改。
  • 高阶函数:指至少满足下列条件之一的函数
    • 函数可以作为参数被传递,如回调、节流、分时函数。
    • 函数可以作为返回值输出,如柯里化函数

函数柯里化

定义:柯里化(Currying)通常也称部分求值,其含义是给函数分步传递参数,每次传递参数后部分应用参数,并返回一个更具体的函数接受剩下的参数,这中间可嵌套多层这样的接受部分参数函数,直至返回最后结果。

//  通用的函数柯里化构造方法
function curry(func){
    //新建args保存参数,注意,第一个参数应该是要柯里化的函数,所以args里面去掉第一个
    var args = [].slice.call(arguments,1);
    //新建_func函数作为返回值
    var _func =  function(){
        //参数长度为0,执行func函数,完成该函数的功能
        if(arguments.length === 0){
            return func.apply(this,args);
        }else {
            //否则,存储参数到闭包中,返回本函数
            [].push.apply(args,arguments);
            return _func;
        }
    }
    return _func;
}

function add(){
    return [].reduce.call(arguments,function(a,b){return a+b});
}
console.log(curry(add,1,2,3)(1)(2)(3,4,5,5)());//26

柯里化其实是有特点的,需要一个闭包保存参数,一个函数来进行递归。在实际使用中使用最多的一个柯里化的例子就是Function.prototype.bind()函数。

常见设计模式

  • 原型模式
    • 定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。是一种对象创建型模式,实现关键是语言本身是否提供了clone方法。
    • 基本规则
      • 所有数据都是对象。
      • 要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它。
      • 对象会记住它的原型。
      • 如果对象无法响应某个请求,它会把这个请求委托给它自己的原型。
    • 意义:不单是一种设计模式,也是一种编程泛型,它构成了JavaScript这门语言的根本。
  • 单例模式
    • 定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
    • 场景:登录浮窗、全局缓存、window对象等。
  • 策略模式
    • 定义:定义一系列算法,各自封装成策略类,对context发起请求时,context将请求委托给对应的策略类。
    • 意义:++策略模式是对象多态的完美体现,高阶函数就是一种隐式的策略模式。策略模式已经融入到了JS语言本身中。++
    • 场景:游戏动效、验证表单等。
  • 发布-订阅模式(观察者模式)
    • 定义:当一个对象的状态发生变化时,所有依赖于它的对象都将得到通知。
    • 要点:在JS中是用注册回调函数的形式来代替传统的发布订阅者模式。从架构上看,MVC和MVVM都少不了发布订阅者模式,而且JS本身也是一门基于事件驱动的语言。
    • 场景:DOM节点上绑定事件函数;订阅ajax请求的error、succ等事件;模块之间的通信;。
  • 职责链模式
    • 定义:一系列可能会处理请求的对象被连接成一条链,请求沿着链传递,直到有对象处理为止。
    • 场景:作用域链、原型链、DOM节点的事件冒泡。

代码重构技巧

  1. 提炼函数,包括重复代码、条件分支语句等情况
  2. 合理使用循环
  3. 使用return提前让函数退出
  4. 传递对象参数代替过程的参数列表
  5. 尽量减少参数数量
  6. 少用三目运算符(可读性差)
  7. 合理使用链式调用
  8. 分解大类型

es6新特性

参考:ES6入门-阮一峰你不知道的JavaScript 重要特性

  • 块级作用域的变量:let和const
  • Symbol:通过Symbol函数生成,凡是属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
  • Set数据结构:类似于数组,但是成员的值都是唯一的,没有重复的值。WeakSet的成员只能是对象,而不能是其他类型的值。
  • Map数据结构:类似于对象(键/值对),但是键值并非只能为字符串,而是可以使用任何值,甚至是另一个对象或map。WeakMap(只)接受对象作为键,这些对象是被弱持有的。
  • 箭头函数:不需要 function 关键字来创建函数,省略 return 关键字,继承当前上下文的 this 关键字。
  • 模板字面量:使用倒引号``拼接字符串,用${expression}表示占位符。
  • 解构赋值:针对可迭代对象的iterator接口,通过遍历器按顺序获取对应的值进行赋值。例如const [x, y, z] = pointconst {x, y, z} = point
  • 对象字面量简写法:如const point = {x: x}可简写为const point = {x}
  • 展开运算符:用...将字面量对象展开为多个元素
  • 对象新增方法Object.is()Object.assign()__proto__Object.keys()Object.values()
  • 模块化:文件通过export暴露接口,通过import引用其他文件的内容。
  • 函数默认参数function foo(num = 200) { return num }
  • Class(类):只是个语法糖,实际上还是原型继承,通过extends关键字实现继承。实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。
  • Promise:用同步的方式去写异步代码
  • Iterator 和 for...of 循环:Iterator是一种接口,为各种不同的数据结构提供统一的访问机制。Iterator接口主要供for...of消费,使得数据结构的成员能够按某种次序排列。
  • Generators(生成器):是能返回一个迭代器的函数。比普通的function多了个星号*,在其函数体内可以使用yield关键字,会在每个yield后暂停,调用next()方法继续。
  • Proxy:在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
  • Reflect:Reflect对象与Proxy对象一样,也是ES6为了操作对象而提供的新API。它修改某些Object方法的返回结果,让其变得更合理。
  • 尾调用优化:尾调用是指某个函数的最后一步是调用另一个函数。只保留内层函数的调用帧。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧只有一项,这将大大节省内存。

ES6 WeakMap应用场景

WeakMap(只)接受对象作为键,这些对象是被弱持有的,也就是说如果对象本身被垃圾回收的话,在WeakMap中的这个项目也会被移除。

  • 对DOM进行操作并持有DOM节点,使用WeakMap可以使得DOM节点被其他代码逻辑删除了之后,可以方便内存被回收,防止内存泄漏
  • 部署私有属性,比如某个类的两个内部属性,是实例的弱引用,如果删除实例,它们也就随之消失,不会造成内存泄漏。

箭头函数和普通函数的区别

  • 箭头函数没有自己的this对象,内部的this指向是固定的,是定义时上层作用域中的this。
  • 箭头函数是匿名函数,不能作为构造函数,不能使用new来调用。
  • 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  • 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
  • 没有原型对象,没有函数提升,形参名称不能重复。

其他

html5新特性

  1. 语意特性:有利于代码可读性、SEO及读屏。
  2. 多媒体:新增了用于媒介回放的 video 和 audio 元素。
  3. 绘图 & 效果:提供了一个更加分化范围的呈现选择(canvas、webGL)。
  4. 离线 & 存储:能够让网页在客户端本地存储数据以及更高效地离线运行(localStorage、sessionStorage、应用程序缓存等)。
  5. 设备兼容特性:提供了前所未有的数据与应用接入开放接口。使外部应用可以直接与浏览器内部的数据直接相连。
  6. 连接特性:Server-Sent Event和WebSockets这两个特性能够帮助我们实现服务器将数据“推送”到客户端的功能
  7. 性能 & 集成:提供了非常显著的性能优化和更有效的计算机硬件使用(WebWorkers、XMLHttpRequest2、HistoryAPI、拖放、requestAnimationFrame、全屏API、指针锁定API、在线和离线事件)。

严格模式

  • 严格模式可以应用于单个函数或整个脚本,只需在顶部添加"use strict"
  • ES2015 引入的 JS模块自动启用了严格模式,因此无需声明即可启用
  • 严格模式下的变化:
    • 任何以静默方式失败的代码都将抛出异常,代码不会在错误位置继续运行
    • 不允许使用无效语法,如不允许对eval和argument执行任何操作
    • 性能优化,如不再支持 arguments.callee
    • 填补安全漏洞,如不允许公开函数的 caller 和 arguments

iframe有那些缺点?

  • iframe会阻塞主页面的Onload事件;
  • 搜索引擎的检索程序无法解读,不利于SEO;
  • iframe和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载。
  • 使用iframe之前需要考虑这两个缺点。如果需要使用iframe,最好是通过javascript动态给iframe添加src属性值,这样可以绕开以上两个问题。

doctype是什么

  • 处于HTML文档的头部的<!DOCTYPE>声明不是一个HTML标签,是一个用于告诉浏览器当前HTMl版本的指令,指示web浏览器使用对应的HTML版本编写的只能进行解析。
  • 由于HTML4.01基于SGML,所以在HTML4.01中<!DOCTYPE>声明指向一个DTD(文档类型定义),DTD指定了标记规则以保证浏览器正确渲染内容。HTML5不基于SGML,所以不用指定DTD。
  • <!DOCTYPE html>兼容所有HTML的历史版本和最新的HTML5版本。如果DOCTYPE不存在或格式错误,文档会以兼容模式呈现,浏览器会使用较低的标准模式来解析HTML。

JQuery常用方法

原⽣JS DOM操作方法汇总

  • 创建节点
    • 创建一个由tagName决定的HTML元素: document.createElement(tagName)
    • 创建文本节点: document.createTextNode(data)
    • 创建一个空白的文档片段: document.createDocumentFragment()
  • 节点选择
    • 返回获取到的元素: document.querySelector(selectors)
    • 返回获取到的元素列表: document.querySelectorAll(selectors)
    • 根据元素ID获取元素: document.getElementById()
    • 根据元素标签名获取元素: document.getElementByTagName()
    • 根据元素name属性获取元素: document.getElementByName()
    • 根据元素类名获取元素: document.getElementByClassName()
  • 获取DOM元素offset/client/scroll
    • offset:只读属性,指偏移,包括这个元素在文档中占用的所有显示宽度,包括滚动条、padding、border,不包括overflow隐藏的部分,单位为像素。
      • offsetParent:属性返回距离调用offsetParent的父级元素中最近的且是已进行过CSS定位的容器元素。如果这个容器元素未进行CSS定位(position),则属性取值为根元素的引用。
      • offsetLeft、offsetTop:指当前元素相对于offsetParent属性指定的父元素的左边界/顶部的距离。
      • offsetWidth、offsetHeight:指当前元素自身的绝对宽度/高度,不包括因overflow而未显示的部分。(border + padding + width/height)
    • scroll:指滚动,包括overflow溢出的部分、padding,不包括滚动条、border和margin。
      • scrollWidth、scrollHeight:获取对象的滚动宽度/高度,对象的实际宽度/高度;
      • scrollLeft、scrollTop:设置或获取位于对象左边界/最顶端和窗口中目前可见内容的最左端/最顶端之间的距离。
    • client:只读属性,指元素本身的可视内容,包括padding,不包括overflow隐藏的部分、滚动条、border。
      • clientWidth、clientHeight:对象可见的宽度/高度,不包括滚动条等边线,会随窗口的显示大小改变。
      • clientLeft、clientTop:元素周围边框的厚度,一般值是0,因为滚动条不会出现在顶部或者左侧。