JavaScript知识点总结(未完待续)

1、变量

1. 数据类型

  • 基本数据类型html

    • Stringgit

    • Number程序员

    • Booleangithub

    • null面试

      特殊: typeof null === 'Object' //truesegmentfault

    • Undefinedapi

    • Symbol 符号(ES6新增)数组

  • 引用数据类型浏览器

    • Object
      • Function
      • Array
      • Date
      • RegExp
  • 基本数据类型和引用数据类型的区别(存储位置不一样)bash

    • 原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,因此放入栈中存储;
    • 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定,若是存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中得到实体

数据封装类对象:ObjectArrayBooleanNumberString

其余对象:FunctionArgumentsMathDateRegExpError

2. 类型判断

2.1 null

typeof null === 'Object' //true

null是惟一一个用typeof检测会返回object基本类型值(注意‘基本’两字)

缘由:不一样的对象在底层都表示为二进制 在JavaScript中二进制前三位为0的话都会被判断为object类型 null的二进制表示全是0,天然前三位也是0 因此 typeof null === “object”

2.2 引用类型的判断

参考连接:深刻理解JS的类型、值、类型转换

2.2.1 typeof

  • typeof 除了能判断基本类型、Object外,还能判断function类型

2.2.2 instanceof

  • 判断对象用 instanceof,其内部机制是经过原型链来判断的

  • instanceof原理:判断实例对象的__proto__属性,和构造函数的prototype属性,是否为同一个引用(是否指向同一个地址)

  • 注意1:虽说,实例是由构造函数 new 出来的,可是实例的__proto__属性引用的是构造函数的prototype。也就是说,实例的__proto__属性与构造函数自己无关。

    注意2:在原型链上,原型的上面可能还会有原型,以此类推往上走,继续找__proto__属性。这条链上若是能找到, instanceof 的返回结果也是 true。

咱们也能够试着实现一下 instanceof
function instanceof(left, right) {
    // 得到类型的原型
    let prototype = right.prototype
    // 得到对象的原型
    left = left.__proto__
    // 判断对象的类型是否等于类型的原型
    while (true) {
    	if (left === null)
    		return false
    	if (prototype === left)
    		return true
    	left = left.__proto__
    }
}
复制代码
  • 分析一个问题

问题:已知A继承了B,B继承了C。怎么判断 a 是由A直接生成的实例,仍是B直接生成的实例呢?仍是C直接生成的实例呢?

分析:这就要用到原型的constructor属性了。

  • foo.__proto__.constructor === Foo的结果为true,可是 foo.__proto__.constructor === Object的结果为false。

因此,用 consturctor判断就比用 instanceof判断,更为严谨。

  • 若是咱们想得到一个变量的正确类型,能够经过 Object.prototype.toString.call(xx)

3. 类型转换

2.3.1 显示类型转换

  • 转换成字符串 String()

    toString() 能够被显式调用,或者在须要字符串化时自动调用

    null 转换为 "null",undefined 转换为 "undefined",true 转换为 "true"。 数字的字符串化则遵循通用规则 极小和极大的 数字使用指数形式:

    // 1.07 连续乘以七个 1000
    var a = 1.07 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000;
    // 七个1000一共21位数字 
    a.toString(); // "1.07e21"
    复制代码

    数组的默认 toString() 方法通过了从新定义,将全部单元字符串化之后再用 "," 链接起来

    var a = [1,2,3];
     a.toString(); // "1,2,3"
    复制代码
  • 转换成数字 Number()

    其中 true 转换为 1,false 转换为 0。undefined 转换为 NaN,null 转换为 0。 处理失败 时返回 NaN(处理数字常量失败时会产生语法错误)

  • 转换成布尔值 Boolean()

    • undefined
    • null
    • false
    • +0、-0 和 NaN
    • ""
    复制代码

    除了上面之外的,都为true

2.3.2 隐式类型转换

  • 字符串和数字之间的隐式转换

    一个坑

    [] + {}; // "[object Object]" {} + []; // 0

    console.log([] + {}); //[object Object]

    console.log({} + []); //[object Object]

    第一行代码中,{} 出如今 + 运算符表达式中,所以它被看成一个值(空对象)来处理。 第二行代码中,{} 被看成一个独立的空代码块(不执行任何操做)。代码块结尾不须要分号,因此这里不存在语法上的问题。最后 + [] 将 [] 显式强制类型转换(参见第 4 章) 为 0。

    第四行代码中,{} 其实应该当成一个代码块,而不是一个 Object,当你在console.log使用的时候,{} 被当成了一个 Object

  • 隐式强制类型转换为布尔值

    下面的状况会发生 布尔值隐式强制类型转换。

    • (1)if (..)语句中的条件判断表达式。
    • (2)for ( .. ; .. ; .. )语句中的条件判断表达式(第二个)。
    • (3) while (..) 和 do..while(..) 循环中的条件判断表达式。
    • (4)? :中的条件判断表达式。
    • (5) 逻辑运算符 ||(逻辑或)和 &&(逻辑与)左边的操做数(做为条件判断表达式)。
  • || 与 &&

  • == 与 ===

    == 容许在相等比较中进行强制类型转换,而 === 不容许

    [] == ![] //true
    复制代码

    参考连接:为何[] ==![]

2、函数

1. 函数调用

4种方式,每种方式的不一样在于this的初始化

通常而言,在Javascript中,this指向函数执行时的当前对象。

1.1. 做为一个函数调用

function myFunction(a, b) {
    return a * b;
}
myFunction(10, 2);  // myFunction(10, 2) 返回 20
window.myFunction(10, 2); //myFunction() 和 window.myFunction() 是同样的
复制代码

函数做为全局对象调用,会使 this 的值成为全局对象。 使用 window 对象做为一个变量容易形成程序崩溃。

1.2 函数做为方法调用

var myObject = {
    firstName:"John",
    lastName: "Doe",
    fullName: function () {
        return this.firstName + " " + this.lastName;
    }
}
myObject.fullName();         // 返回 "John Doe"
复制代码

fullName 方法是一个函数。函数属于对象。 myObject 是函数的全部者。this对象,拥有 JavaScript 代码。实例中 this 的值为 myObject 对象。

函数做为对象方法调用,会使得 this 的值成为对象自己。

1.3 使用构造函数调用

// 构造函数:
function myFunction(arg1, arg2) {
    this.firstName = arg1;
    this.lastName  = arg2;
}
 
// This creates a new object
var x = new myFunction("John","Doe");
x.firstName;                             // 返回 "John"
复制代码

构造函数中 this 关键字没有任何的值。 this 的值在函数调用实例化对象(new object)时建立。

1.4 使用函数的方法调用

call()apply() 是预约义的函数方法。 两个方法可用于调用函数,两个方法的第一个参数必须是对象自己。

function myFunction(a, b) {
    return a * b;
}
myObject = myFunction.call(myObject, 10, 2);     // 返回 20
复制代码
function myFunction(a, b) {
    return a * b;
}
myArray = [10, 2];
myObject = myFunction.apply(myObject, myArray);  // 返回 20
复制代码

经过 call() 或 apply() 方法你能够设置 this 的值, 且做为已存在对象的新方法调用。

在 JavaScript 严格模式(strict mode)下, 在调用函数时第一个参数会成为 this 的值, 即便该参数不是一个对象。

在 JavaScript 非严格模式(non-strict mode)下, 若是第一个参数的值是 null 或 undefined, 它将使用全局对象替代。

2. call、apply、bind的区别

  • call和apply改变了函数的this上下文后便当即执行该函数,而bind不会当即执行函数,而是将函数返回。
  • 他们第一个参数都是要改变上下文的对象,而call、bind从第二个参数开始以参数列表的形式展示,apply则是把除了改变上下文对象的参数放在一个数组里面做为它的第二个参数。

参考连接:详解call、apply、bind

3. 做用域及做用域链

  • 做用域就是变量与函数的可访问范围,即做用域控制着变量与函数的可见性和生命周期

  • 内层函数可访问外层函数局部变量

  • 外层函数不能访问内层函数局部变量

  • 通俗地讲,当声明一个函数时,局部做用域一级一级向上包起来,就是做用域链。

    1.当执行函数时,老是先从函数内部找寻局部变量

    2.若是内部找不到(函数的局部做用域没有),则会向建立函数的做用域(声明函数的做用域)寻找,依次向上

4. 闭包

4.1 什么是闭包?

闭包就是可以读取其余函数内部变量的函数。

函数 A 内部有一个函数 B,函数 B 能够访问到函数 A 中的变量,那么函数 B 就是闭包。

4.2 闭包的用途

  • 能够读取函数内部的变量
  • 让这些变量始终保持在内存中

经典面试题,循环中使用闭包解决 var 定义函数的问题

for (var i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i)
  }, i * 1000)
}
//咱们但愿输出的是1,2,3,4,5,但由于 setTimeout 是个异步函数,因此会先把循环所有执行完毕,这时候 i就是 6 了,因此会输出五个6
复制代码

解决方法:

  1. 闭包

    for (var i = 1; i <= 5; i++) {
      (function(j) {
        setTimeout(function timer() {
          console.log(j)
        }, j * 1000)
      })(i)
    }
    复制代码
  2. 使用 setTimeout 的第三个参数,这个参数会被当成 timer 函数的参数传入

    for (var i = 1; i <= 5; i++) {
      setTimeout(
        function timer(j) {
          console.log(j)
        },
        i * 1000,
        i
      )
    }
    复制代码
  3. 使用let

    for (let i = 1; i <= 5; i++) {
      setTimeout(function timer() {
        console.log(i)
      }, i * 1000)
    }
    复制代码

4.3 闭包的优缺点

  • 优势:避免全局变量的污染
  • 缺点:因为闭包会使得函数中的变量都被保存在内存中,内存消耗很大,因此不能滥用闭包,不然会形成网页的性能问题,在IE中可能致使内存泄露
  • 解决方法:在退出函数以前,将不使用的局部变量所有删除

3、对象

1. 建立对象的方式

  1. 对象字面量

    person={firstname:"Mark",lastname:"Yun",age:25,eyecolor:"black"};
    复制代码
  2. 工厂方式(内置对象)

    var wcDog =new Object();
         wcDog.name="旺财";
         wcDog.age=3;
         wcDog.work=function(){
           alert("我是"+wcDog.name+",汪汪汪......");
         }
         wcDog.work();
    复制代码

    方式1与方式2效果同样,第一种写法,person会指向Object

  3. 经过构造函数

    // 无参
    function Person(){}
    	var person=new Person();//定义一个function,若是使用new"实例化",该function能够看做是一个Class
            person.name="Mark";
            person.age="25";
            person.work=function(){
            alert(person.name+" hello...");
    }
    person.work();
    复制代码
    // 带参(用this关键字定义构造的上下文属性)
    function Pet(name,age,hobby){
           this.name=name;//this做用域:当前对象
           this.age=age;
           this.hobby=hobby;
           this.eat=function(){
               alert("我叫"+this.name+",我喜欢"+this.hobby+",是个程序员");
           }
    }
    var maidou =new Pet("麦兜",25,"coding");//实例化、建立对象
    maidou.eat();//调用eat方法
    复制代码
  4. Object.create

    var p = {name:'smyhvae'};
    var obj3 = Object.create(p);  //此方法建立的对象,是用原型链链接的,obj3是实例,p是obj3的原型(name是p原型里的属性),构造函数是Objecet
    复制代码

2. new

2.1 new的做用

  • new出来的实例能够访问到构造函数中的属性
  • new出来的实例能够访问到构造函数原型链中的属性(实例与构造函数经过原型链链接了起来)

2.2 new的原理

  • 建立一个新的空对象实例。
  • 将此空对象的隐式原型指向其构造函数的显示原型。
  • 执行构造函数(传入相应的参数,若是没有参数就不用传),同时 this 指向这个新实例。
  • 若是返回值是一个新对象,那么直接返回该对象;若是无返回值或者返回一个非对象值,那么就将步骤(1)建立的对象返回。

2.3 如何实现new

function create(Con, ...args) {
  let obj = {}
  Object.setPrototypeOf(obj, Con.prototype)
  let result = Con.apply(obj, args)
  return result instanceof Object ? result : obj
}
复制代码
  1. 首先函数接受不定量的参数,第一个参数为构造函数,接下来的参数被构造函数使用

  2. 而后内部建立一个空对象 obj

  3. 由于 obj 对象须要访问到构造函数原型链上的属性,因此咱们经过 setPrototypeOf 将二者联系起来。这段代码等同于 obj.__proto__ = Con.prototype

  4. obj 绑定到构造函数上,而且传入剩余的参数

  5. 判断构造函数返回值是否为对象,若是为对象就使用构造函数返回的值,不然使用 obj,这样就实现了忽略构造函数返回的原始值

参考连接:new操做符

2.4 new建立对象和字面量建立对象有何区别?

  • 不管是function Foo()仍是let a = { b : 1 },其实都是经过new产生的
  • 使用 new Object() 的方式建立对象须要经过做用域链一层层找到 Object,可是你使用字面量的方式就没这个问题
function Foo() {}
// function 就是个语法糖
// 内部等同于 new Function()
let a = { b: 1 }
// 这个字面量内部也是使用了 new Object()
复制代码

3. 原型及原型链

3.1 概念

  • 每一个对象都会在其内部初始化一个属性,就是prototype(原型)
  • 当咱们访问一个对象的属性时,若是这个对象内部不存在这个属性,那么他就会去prototype里找这个属性,这个prototype又会有本身的prototype,因而就这样一直找下去,也就是咱们平时所说的原型链的概念
  • 任何一个实例,经过原型链,找到它上面的原型,该原型对象中的方法和属性,能够被全部的原型实例共享。

3.2 原型、构造函数、实例

  • 构造函数经过new生成实例
  • 实例的构造函数属性(constructor)指向构造函数
  • 原型对象(Person.prototype)是 构造函数(Person)的一个实例
person1.constructor === Person
Person.prototype.constructor === Person
复制代码
  • 实例的__proto__指向其构造函数的原型
person1.__proto__ === Person.prototype
复制代码

4. 继承

4.1 构造函数继承

function Parent1() {
        this.name = 'parent1 的属性';
    }

    function Child1() {
        Parent1.call(this);         //【重要】此处用 call 或 apply 都行:改变 this 的指向,parent的实例 --> 改成指向child的实例
        this.type = 'child1 的属性';
    }

    console.log(new Child1);
复制代码

这种方式,虽然改变了 this 的指向,可是,Child1 没法继承 Parent1 的原型。也就是说,若是我给 Parent1 的原型增长一个方法,这个方法是没法被 Child1 继承的。

4.2 原型继承

/* 经过原型链实现继承 */
    function Parent() {
        this.name = 'Parent 的属性';
    }

    function Child() {
        this.type = 'Child 的属性';
    }

    Child.prototype = new Parent(); //【重要】

    console.log(new Child());
复制代码

咱们把Parent的实例赋值给了Childprototye,从而实现继承。此时,new Child.__proto__ === new Parent()的结果为true

这种继承方式,Child 能够继承 Parent 的原型,但有个缺点:

若是修改 child1实例的name属性,child2实例中的name属性也会跟着改变。形成这种缺点的缘由是:child1和child2共用原型。即:chi1d1.__proto__ === child2__proto__是严格相同。

4.3 组合继承

用原型链实现对原型属性和方法的继承,用借用构造函数技术来实现实例属性的继承。

/* 组合方式实现继承:构造函数、原型链 */
    function Parent3() {
        this.name = 'Parent 的属性';
        this.arr = [1, 2, 3];
    }

    function Child3() {
        Parent3.call(this); //【重要1】执行 parent方法
        this.type = 'Child 的属性';
    }
    Child3.prototype = new Parent3(); //【重要2】第二次执行parent方法

    var child = new Child3();
复制代码

这种方式,能解决以前两种方式的问题:既能够继承父类原型的内容,也不会形成原型里属性的修改。

这种方式的缺点是:让父亲Parent的构造方法执行了两次。

4.4 ES6类继承extends

extends关键字主要用于类声明或者类表达式中,以建立一个类,该类是另外一个类的子类。其中constructor表示构造函数,一个类中只能有一个构造函数,有多个会报出SyntaxError错误,若是没有显式指定构造方法,则会添加默认的 constructor方法。

4、DOM事件

1.DOM事件的级别

  • DOM0

    element.onclick = function () {
    
        }
    复制代码

​ 一是在标签内写onclick事件 二是在JS写onclick=function(){}函数

  • DOM2

    //高版本浏览器
    	element.addEventListener('click', function () {
    
        }, false);
    	//IE8及如下版本
        element.attachEvent('onclick', function () {
    
        });
    	//兼容写法
                /* * 参数: * element 要绑定事件的对象 * eventStr 事件的字符串(不要on) * callback 回调函数 */
    	function myBind(element , eventStr , callback){
            if(element.addEventListener){
                //大部分浏览器兼容的方式
                element.addEventListener(eventStr , callback , false);
            }else{
                //IE8及如下
                element.attachEvent("on"+eventStr , function(){
                    //在匿名函数 function 中调用回调函数callback
                    callback.call(element);
                });
            }
    复制代码

    上面的第三参数中,true表示事件在捕获阶段触发,false表示事件在冒泡阶段触发(默认)。若是不写,则默认为false。

  • DOM3

    element.addEventListener('keyup', function () {
    
        }, false);
    复制代码

    DOM3中,增长了不少事件类型,好比鼠标事件、键盘事件等。

  • 为什么事件没有DOM1的写法呢?由于,DOM1标准制定的时候,没有涉及与事件相关的内容。

2. 事件流

事件传播的三个阶段是:事件捕获、事件目标、事件冒泡

  • 事件捕获阶段:事件从祖先元素往子元素查找(DOM树结构),直到捕获到事件目标 target。在这个过程当中,默认状况下,事件相应的监听函数是不会被触发的。
  • 事件目标:当到达目标元素以后,执行目标元素该事件相应的处理函数。若是没有绑定监听函数,那就不执行。
  • 事件冒泡阶段:事件从事件目标 target 开始,从子元素往冒泡祖先元素冒泡,直到页面的最上一级标签。

3. DOM事件模型(捕获、冒泡)

3.1 事件捕获

事件从祖先元素往子元素查找(DOM树结构),直到捕获到事件目标 target。

addEventListener能够捕获事件

element.addEventListener('click', function () {

    }, true);
复制代码

参数为true,表明事件在捕获阶段执行。

捕获阶段,事件依次传递的顺序是:window --> document --> html--> body --> 父元素、子元素、目标元素。

window.addEventListener("click", function () {
        alert("捕获 window");
    }, true);

    document.addEventListener("click", function () {
        alert("捕获 document");
    }, true);

    document.documentElement.addEventListener("click", function () {
        alert("捕获 html");
    }, true);  //获取html节点

    document.body.addEventListener("click", function () {
        alert("捕获 body");
    }, true);  //获取body节点

    fatherBox.addEventListener("click", function () {
        alert("捕获 father");
    }, true);

    childBox.addEventListener("click", function () {
        alert("捕获 child");
    }, true);
复制代码

3.2 事件冒泡

当一个元素上的事件被触发的时候(好比说鼠标点击了一个按钮),一样的事件将会在那个元素的全部祖先元素中被触发。这一过程被称为事件冒泡;这个事件从原始元素开始一直冒泡到DOM树的最上层。

通俗来说,冒泡指的是:子元素的事件被触发时,父元素的一样的事件也会被触发。取消冒泡就是取消这种机制。

冒泡顺序

通常的浏览器: (除IE6.0以外的浏览器)

  • div -> body -> html -> document -> window

IE6.0:

  • div -> body -> html -> document

不能冒泡的事件:

blur、focus、load、unload、onmouseenter、onmouseleave

检查一个元素是否会冒泡:event.bubbles

阻止冒泡:

w3c的方法:(火狐、谷歌、IE11)

event.stopPropagation();
复制代码

IE10如下:

event.cancelBubble = true
复制代码

兼容代码:

childBox.onclick = function(event){
    event = event || window.event;
    if(event && event.stopPropagation){
        event.stopPropagation();
    }else{
        event.cancelBubble = true;
    }
}
复制代码

上方代码中,咱们对childBox进行了阻止冒泡,产生的效果是:事件不会继续传递到 father、grandfather、body了

4. event对象的常见应用(经常使用api方法)

  • 阻止默认事件

    event.preventDefault();
    复制代码
    function stopDefault( event ) { 
        //阻止默认浏览器动做(W3C) 
        if ( event && event.preventDefault ) 
            event.preventDefault(); 
        //IE中阻止函数器默认动做的方式 
        else 
            window.event.returnValue = false; 
    }
    复制代码

    好比,已知<a>标签绑定了click事件,此时,若是给<a>设置了这个方法,就阻止了连接的默认跳转

  • 阻止冒泡

    代码见上

  • 设置事件优先级

    event.stopImmediatePropagation();
    复制代码

    好比说,我用addEventListener给某按钮同时注册了事件A、事件B。此时,若是我单击按钮,就会依次执行事件A和事件B。如今要求:单击按钮时,只执行事件A,不执行事件B。该怎么作呢?这是时候,就能够用到stopImmediatePropagation方法了。作法是:在事件A的响应函数中加入这句话。

  • event.currentTarget   //当前所绑定的事件对象。在事件委托中,指的是【父元素】。
    
    event.target  //当前被点击的元素。在事件委托中,指的是【子元素】。
    复制代码

5. 自定义事件

var myEvent = new Event('clickTest');
    element.addEventListener('clickTest', function () {
        console.log('smyhvae');
    });

	//元素注册事件
    element.dispatchEvent(myEvent); //注意,参数是写事件对象 myEvent,不是写 事件名 clickTest
复制代码

6. 事件委托

事件代理/事件委托是利用事件冒泡的特性,将本应该绑定在多个元素上的事件绑定在他们的祖先元素上,尤为在动态添加子元素的时候,能够很是方便的提升程序性能,减少内存空间。

相关文章
相关标签/搜索