前端面试题目汇总摘录(JS 基础篇)

温故而知新,保持空杯心态javascript

JS 基础

JavaScript 的 typeof 返回那些数据类型

object number function boolean undefined stringhtml

typeof null; // object
typeof isNaN; // function
typeof isNaN(123); //boolean
typeof []; // object
Array.isArray(); // false
toString.call([]); // [object Array]
var arr = [];
arr.constructor; // ƒ Array() { [native code] }
复制代码

强制类型转换和隐式类型转换?

显示转换(强制类型转换)

js 提供了如下几种转型函数:前端

转换的类型 函数
数值类型 Number(mix),parseInt(string,radix),parseFloat(string);
字符串类型 toString(radix),String(mix)
布尔类型 Boolean(mix)
Number(mix) 函数,能够将任意类型的参数 mix 转换为数值类型,规则为
  1. 若是是布尔值,truefalse分别被转换为 1 和 0
  2. 若是是数字值,返回自己
  3. 若是是 null,返回 0
  4. 若是是 undefined,返回 NaN
  5. 若是是字符串,遵循如下规则:
    1. 若是字符串中只包含数字,则将其转换为十进制(忽略前导0,前面正负号有效)
    2. 若是字符串中包含有效的浮点格式,则将其转换为对应的浮点数值(忽略前导0,前面正负号有效)
    3. 若是字符串中包含有效的十六进制格式,则转换为相同大小的十进制整数值
    4. 若是字符串是空的(不包含任何字符),则将其转换为 0
    5. 若是字符串中包含上述格式以后的字符,则将其转换为 NaN
  6. 若是是对象,则调用对象的 valueOf() 方法,而后按照前面的规则进行转换返回的值,若是是转换结果是 NaN,则调用对象的 toString() 方法,而后再一次按照前面的规则进行返回的字符串值的转换

下表是对象的 valueOf() 的返回值java

对象 返回值
Array 数组的元素被转换为字符串,这些字符串由逗号分隔,链接在一块儿。其操做与 Array.toString 和 Array.join 方法相同。
Boolean Boolean 值。
Date 存储的时间是从 1970 年 1 月 1 日午夜开始计的毫秒数 UTC。
Function 函数自己。
Number 数字值。
Object 对象自己。这是默认状况。
String 字符串值。

因为 Number()函数在转换字符串时原理比较复杂,且不够合理,所以在处理字符串时,更经常使用的是 parseInt() 函数node

parstInt(string,radix) 函数,将字符串转换为整数类型的数值,其规则为
  1. 忽略前面字符串前面的空格,直至找到第一个非空格字符
  2. 若是第一个字符不是数字字符或者负号,就会返回 NaN(也就是遇到空字符会返回 NaN)
  3. 若是第一个字符是数字字符,会继续解析第二个字符,知道解析完全部后续的字符或者是遇到一个非数字字符
  4. 若是字符串中第一个字符是数字字符,也可以识别各类进制
  5. 最好在 第二个参数指定转换的基数(进制),就不会有所歧义。
parseFloat(string)函数,将字符串转换为浮点数类型的数值。

与parseInt()函数相似,parseFloat()也是从第一个字符(位置0)开始解析每一个字符。并且也是一直解析到字符串末尾,或者解析到碰见一个无效的浮点数字字符为止。也就是说,字符串中的第一个小数点是有效的,而第二个小数点就是无效的了,所以它后面的字符串将被忽略。jquery

toString(radix)

undefinednull以外的全部类型的值都具备 toString() 方法,其做用是返回对象的字符串表示。web

多数状况下,调用toString()方法没必要传递参数。可是,在调用数值的toString()方法时,能够传递一个参数:输出数值的基数。默认状况下,toString()方法以十进制格式返回数值的字符串表示。面试

对象 操做
Array 将 Array 的元素转换为字符串。结果字符串由逗号分隔,且链接起来。
Boolean 若是 Boolean 值是 true,则返回 “true”。不然,返回 “false”。
Date 返回日期的文字表示法。
Error 返回一个包含相关错误信息的字符串。
Function 返回以下格式的字符串,其中 functionname 是被调用 toString 方法函数的名称:function functionname( ) { [native code] }
Number 返回数字的文字表示。
String 返回 String 对象的值。
默认 返回 “[object objectname]”,其中 objectname 是对象类型的名称。

在不知道要转换的值是否是null或undefined的状况下,还可使用转型函数String(),这个函数可以将任何类型的值转换为字符串。正则表达式

String(mix)函数,将任何类型的值转换为字符串,其规则为:
  1. 若是有toString()方法,则调用该方法(不传递radix参数)并返回结果
  2. 若是是null,返回”null”
  3. 若是是undefined,返回”undefined”
Boolean(mix)函数,将任何类型的值转换为布尔值。

如下值会被转换为false:false、”"、0、NaN、null、undefined,其他任何值都会被转换为trueshell

隐式转换(非强制转换类型)

在某些状况下,即便咱们不提供显示转换,Javascript也会进行自动类型转换,主要状况有:

用于检测是否为非数值的函数:isNaN(mix)

isNaN()函数,经测试发现,该函数会尝试将参数值用 Number() 进行转换,若是结果为“非数值”则返回 true,不然返回 false

递增递减操做符(包括前置和后置)、一元正负符号操做符(通过对比发现,其规则与Number()规则基本相同)
  1. 若是是包含有效数字字符的字符串,先将其转换为数字值(转换规则同 Number()),再执行加减1的操做,字符串变量变为数值变量。
  2. 若是是不包含有效数字字符的字符串,将变量的值设置为 NaN,字符串变量变成数值变量。
  3. 若是是布尔值 false,先将其转换为0再执行加减1的操做,布尔值变量编程数值变量。
  4. 若是是布尔值 true,先将其转换为1再执行加减1的操做,布尔值变量变成数值变量。
  5. 若是是浮点数值,执行加减1的操做。
  6. 若是是对象,先调用对象的 valueOf() 方法,而后对该返回值应用前面的规则。若是结果是 NaN,则调用 toString() 方法后再应用前面的规则。对象变量变成数值变量。
加法运算操做符

加号运算操做符在Javascript也用于字符串链接符,因此加号操做符的规则分两种状况:

若是两个操做值都是数值,其规则为:

  1. 若是一个操做数为 NaN,则结果为 NaN
  2. 若是是 Infinity+Infinity,结果是 Infinity
  3. 若是是 -Infinity+(-Infinity),结果是 -Infinity
  4. 若是是 Infinity+(-Infinity),结果是 NaN
  5. 若是是 +0+(+0),结果为 +0
  6. 若是是 (-0)+(-0),结果为 -0
  7. 若是是 (+0)+(-0),结果为 +0

若是有一个操做值为字符串,则:

  1. 若是两个操做值都是字符串,则将它们拼接起来 若是只有一个操做值为字符串,则将另外操做值转换为字符串,而后拼接起来
  2. 若是一个操做数是对象、数值或者布尔值,则调用toString()方法取得字符串值,而后再应用前面的字符串规则。
  3. 对于undefined和null,分别调用String()显式转换为字符串。

能够看出,加法运算中,若是有一个操做值为字符串类型,则将另外一个操做值转换为字符串,最后链接起来。

乘除、减号运算符、取模运算符

这些操做符针对的是运算,因此他们具备共同性:若是操做值之一不是数值,则被隐式调用Number() 函数进行转换。具体每一种运算的详细规则请参考ECMAScript中的定义。

逻辑操做符(!、&&、||)

逻辑非(!)操做符首先经过Boolean()函数将它的操做值转换为布尔值,而后求反。

逻辑与(&&)操做符,若是一个操做值不是布尔值时,遵循如下规则进行转换:

  1. 若是第一个操做数经 Boolean() 转换后为 true,则返回第二个操做值,不然返回第一个值(不是 Boolean() 转换后的值)
  2. 若是有一个操做值为 null,返回 null
  3. 若是有一个操做值为 NaN,返回 NaN
  4. 若是有一个操做值为 undefined,返回 undefined

逻辑或(||)操做符,若是一个操做值不是布尔值,遵循如下规则

  1. 若是第一个操做值经 Boolean() 转换后为 false,则返回第二个操做值,不然返回第一个操做值(不是 Boolean() 转换后的值)
  2. 对于 undefinednullNaN 的处理规则与逻辑与(&&)相同
关系操做符(<, >, <=, >=)

与上述操做符同样,关系操做符的操做值也能够是任意类型的,因此使用非数值类型参与比较时也须要系统进行隐式类型转换:

  1. 若是两个操做值都是数值,则进行数值比较
  2. 若是两个操做值都是字符串,则比较字符串对应的字符编码值
  3. 若是只有一个操做值是数值,则将另外一个操做值转换为数值,进行数值比较
  4. 若是一个操做数是对象,则调用 valueOf() 方法(若是对象没有 valueOf() 方法则调用 toString() 方法),获得的结果按照前面的规则执行比较
  5. 若是一个操做值是布尔值,则将其转换为数值,再进行比较

注:NaN 是很是特殊的值,它不和任何类型的值相等,包括它本身,同时它与任何类型的值比较大小时都返回 false

相等操做符(==)

相等操做符会对操做值进行隐式转换后进行比较:

  1. 若是一个操做值为布尔值,则在比较以前先将其转换为数值
  2. 若是一个操做值为字符串,另外一个操做值为数值,则经过Number()函数将字符串转换为数值
  3. 若是一个操做值是对象,另外一个不是,则调用对象的 valueOf() 方法,获得的结果按照前面的规则进行比较
  4. nullundefined 是相等的
  5. 若是一个操做值为 NaN,则相等比较返回 false
  6. 若是两个操做值都是对象,则比较它们是否是指向同一个对象

split()、join()的区别

前者是切割成数组的形式

后者是将数组转换为字符串

数组方法pop/push/unshift/shift

数组方法 描述
pop() 删除原数组最后一项,并返回删除元素的值;若是数组为空则返回undefined
push() 将参数添加到原数组末尾,并返回数组的长度
unshift() 将参数添加到原数组开头,并返回数组的长度
shift() 删除原数组第一项,并返回删除元素的值;若是数组为空则返回undefined

事件绑定和普通事件有什么区别

普通事件中的onclick是DOM0级事件只支持单个事件,会被其余onclick事件覆盖,而事件绑定中的addEventListener是DOM2级事件能够添加多个事件而不用担忧被覆盖

普通添加事件的方法:

var btn = document.getElementById("hello");
btn.onclick = function(){
	alert(1);
}
btn.onclick = function(){
	alert(2);
}
复制代码

执行上面的代码只会alert 2

事件绑定方式添加事件:

var btn = document.getElementById("hello");
btn.addEventListener("click",function(){
	alert(1);
},false);
btn.addEventListener("click",function(){
	alert(2);
},false);
复制代码

执行上面的代码会先alert 1 再 alert 2

IE 和 DOM 事件流有什么区别

事件

HTML元素事件是浏览器内在自动产生的,当有事件发生时html元素会向外界(这里主要指元素事件的订阅者)发出各类事件,如click,onmouseover,onmouseout等等。

DOM事件流

DOM(文档对象模型)结构是一个树型结构,当一个HTML元素产生一个事件时,该事件会在元素结点与根结点之间的路径传播,路径所通过的结点都会收到该事件,这个传播过程可称为DOM事件流。

冒泡型事件(Bubbling)

这是IE浏览器对事件模型的实现。冒泡,顾名思义,事件像个水中的气泡同样一直往上冒,直到顶端。从DOM树型结构上理解,就是事件由叶子结点沿祖先结点一直向上传递直到根结点;从浏览器界面视图HTML元素排列层次上理解就是事件由具备从属关系的最肯定的目标元素一直传递到最不肯定的目标元素.

捕获型事件(Capturing)

Netscape Navigator的实现,它与冒泡型恰好相反,由DOM树最顶层元素一直到最精确的元素,直观上的理解应该如同冒泡型,事件传递应该由最肯定的元素,即事件产生元素开始。

冒泡和捕获

DOM标准事件模型

由于两个不一样的模型都有其优势和解释,DOM标准支持捕获型与冒泡型,能够说是它们二者的结合体。它能够在一个DOM元素上绑定多个事件处理器,而且在处理函数内部,this关键字仍然指向被绑定的DOM元素,另外处理函数参数列表的第一个位置传递事件event对象。

首先是捕获式传递事件,接着是冒泡式传递,因此,若是一个处理函数既注册了捕获型事件的监听,又注册冒泡型事件监听,那么在DOM事件模型中它就会被调用两次。

实例

<body>
    <div>
        <button>点击这里</button>
    </div>
</body>
复制代码

冒泡:button -> div -> body (IE 事件流)

捕获:body -> div -> button (Netscape事件流)

DOM: body -> div -> button -> button -> div -> body(先捕获后冒泡)

**事件侦听函数的区别 **

// IE使用: 
[Object].attachEvent("name_of_event_handler", fnHandler); //绑定函数 
[Object].detachEvent("name_of_event_handler", fnHandler); //移除绑定 

// DOM使用: 
[Object].addEventListener("name_of_event", fnHandler, bCapture); //绑定函数 
[Object].removeEventListener("name_of_event", fnHandler, bCapture); //移除绑定 
复制代码

如何取消浏览器事件的传递与事件传递后浏览器的默认处理

取消事件传递是指,中止捕获型事件或冒泡型事件的进一步传递。

事件传递后的默认处理是指,一般浏览器在事件传递并处理完后会执行与该事件关联的默认动做(若是存在这样的动做)。例如,若是表单中input type 属性是 “submit”,点击后在事件传播完浏览器就就自动提交表单。又例如,input 元素的 keydown 事件发生并处理后,浏览器默认会将用户键入的字符自动追加到 input 元素的值中。

要取消浏览器的事件传递,IE与DOM标准又有所不一样。

在IE下,经过设置 event 对象的 cancelBubbletrue 便可。

function someHandle() { 
   window.event.cancelBubble = true; 
}
复制代码

DOM标准经过调用 event对象的 stopPropagation() 方法便可。

function someHandle(event) {
    event.stopPropagation(); 
}
复制代码

因些,跨浏览器的中止事件传递的方法是:

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

取消事件传递后的默认处理,IE与DOM标准又不所不一样。

在IE下,经过设置 event 对象的 returnValuefalse 便可。

function someHandle() { 
   window.event.returnValue = false; 
}
复制代码

DOM标准经过调用 event 对象的 preventDefault() 方法便可。

function someHandle(event) { 
   event.preventDefault(); 
}
复制代码

因些,跨浏览器的取消事件传递后的默认处理方法是:

function**` `someHandle(event) { 
event = event || window.event; 
if(event.preventDefault) 
     event.preventDefault(); 
  else event.returnValue = false; 
}
复制代码

IE 和标准下有哪些兼容性的写法

var ev = ev || window.event
document.documentElement.clinetWidth || document.body.clientWidth
var target = ev.srcElement || ev.target
复制代码

call 和 apply 的区别

call 和 apply 相同点: 都是为了用一个本不属于一个对象的方法,让这个对象去执行

基本使用

call()

function.call(obj[,arg1[, arg2[, [,.argN]]]]]) 复制代码
  • 调用call的对象必须是个函数function
  • call的第一个参数将会是function改变上下文后指向的对象.若是不传,将会默认是全局对象window
  • 第二个参数开始能够接收任意个参数,这些参数将会做为function的参数传入function
  • 调用call的方法会当即执行

apply()

function.apply(obj[,argArray]) 复制代码

call方法的使用基本一致,可是只接收两个参数,其中第二个参数必须是一个数组或者类数组,这也是这两个方法很重要的一个区别

数组与类数组小科普

数组咱们都知道是什么,它的特征都有哪些呢?

  1. 能够经过角标调用,如 array[0]
  2. 具备长度属性length
  3. 能够经过 for 循环和forEach方法进行遍历

类数组顾名思义,具有的特征应该与数组基本相同,那么能够知道,一个形以下面这个对象的对象就是一个类数组

var arrayLike = {
    0: 'item1',
    1: 'item2',
    2: 'item3',
    length: 3
}
复制代码

类数组arrayLike能够经过角标进行调用,具备length属性,同时也能够经过 for 循环进行遍历

咱们常用的获取dom节点的方法返回的就是一个类数组,在一个方法中使用 arguments关键字获取到的该方法的全部参数也是一个类数组

可是类数组却不能经过forEach进行遍历,由于forEach是数组原型链上的方法,类数组毕竟不是数组,因此没法使用

不一样点

call方法从第二个参数开始能够接收任意个参数,每一个参数会映射到相应位置的func的参数上,能够经过参数名调用,可是若是将全部的参数做为数组传入,它们会做为一个总体映射到func对应的第一个参数上,以后参数都为空

function func (a,b,c) {}

func.call(obj, 1,2,3)
// function接收到的参数其实是 1,2,3

func.call(obj, [1,2,3])
// function接收到的参数其实是 [1,2,3],undefined,undefined
复制代码

apply方法最多只有两个参数,第二个参数接收数组或者类数组,可是都会被转换成类数组传入func中,而且会被映射到func对应的参数上

func.apply(obj, [1,2,3])
// function接收到的参数其实是 1,2,3

func.apply(obj, {
    0: 1,
    1: 2,
    2: 3,
    length: 3
})
// function接收到的参数其实是 1,2,3
复制代码

b 继承 a 的方法

方法一:对象冒充

function Parent(username){
    this.username = username;
    this.hello = function(){
        console.log(this.username);
    }
}
function Child(username,password){
    this.method = Parent; // this.method 做为一个临时的属性,而且指向了 Parent所指向的对象函数
    this.method(username); // 执行 this.method 方法,即执行了 Parent 所指向的对象函数
    delete this.method; // 销毁 this.method 属性,即此时 Child 就已经拥有了 Parent 的全部方法和属性 
    this.password = password;
    this.world = function(){
        console.log(this.password);
    }
}
const parent = new Parent('hello parent');
const child = new Child('hello child','123456');
console.log(child);
parent.hello();
child.hello();
child.world(); 
复制代码

**方法二:call() **

call 方法是 Function 类中的方法 call 方法的第一个参数的值赋值给类(即方法)中出现的 this call 方法的第二个参数开始依次赋值给类(即方法)所接受的参数

function Parent(username){
    this.username = username;
    this.hello = function(){
        console.log(this.username);
    }
}
function Child(username,password){
    Parent.call(this,username);
    this.password = password;
    this.world = function(){
        console.log(this.password);
    }
}
const parent = new Parent('hello parent');
const child = new Child('hello child','123456');
parent.hello();
child.hello();
child.world();
复制代码

方法三:apply()

apply方法接受2个参数

第一个参数与call方法的第一个参数同样,即赋值给类(即方法)中出现的this

第二个参数为数组类型,这个数组中的每一个元素依次赋值给类(即方法)所接受的参数

function Parent(username){
    this.username = username;
    this.hello = function(){
        console.log(this.username);
    }
}
function Child(username,password){
    Parent.apply(this,new Array(username));
    this.password = password;
    this.world = function(){
        console.log(this.password);
    }
}
const parent = new Parent('hello parent');
const child = new Child('hello child','123456');
parent.hello();
child.hello();
child.world();
复制代码

方法四:原型链

即子类经过 prototype 将全部在父类中经过 prototype 追加的属性和方法都追加到 Child ,从而实现继承

function Parent(){}
Parent.prototype.hello = "hello";
Parent.prototype.sayHello = function(){
    console.log(this.hello);
}
function Child(){}
Child.prototype = new Parent();// 将 Parent 中全部经过 prototype 追加的属性和方法都追加到 Child 从而实现了继承
Child.prototype.world = "world";
Child.prototype.sayWorld = function(){
    console.log(this.world);
}
const child = new Child();
child.sayHello();
child.sayWorld();
复制代码

方法五:混合方式,call()+ 原型链

function Parent(hello){
    this.hello = hello;
}
Parent.prototype.sayHello = function(){
    console.log(this.hello);
}
function Child(hello,world){
    Parent.call(this,hello); // 将父类的属性继承过来
    this.world = world;
}
Child.prototype = new Parent(); //将父类的方法继承过来
Child.prototype.sayWorld = function(){ // 新增方法
    console.log(this.world);
}
const child = new Child("hello","world");
child.sayHello();
child.sayWorld();
复制代码

JavaScript this 指针、闭包、做用域

**js 中的this 指针 **

在函数执行时,this 老是指向调用该函数的对象。要判断 this 的指向,其实就是判断 this 所在的函数属于谁。

在《javaScript语言精粹》这本书中,把 this 出现的场景分为四类,简单的说就是:

1)有对象就指向调用对象

var myObject = { value: 123 }
myObject.getValue = function(){
    console.log(this.value); // 123
    console.log(this); // {value: 123, getValue: ƒ}
}
myObject.getValue();
复制代码

2)没调用对象就指向全局对象

var myObject = { value: 123 }
myObject.getValue = function(){
    var foo = function(){
        console.log(this.value); // undefined
        console.log(this); // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …} 

        // foo函数虽然定义在getValue 函数体内,可是不属于 getValue也不属于 myObject,因此调用的时候,它的 this 指针指向了全局对象
    }
    foo();
    return this.value;
}
console.log(myObject.getValue()); // 123
复制代码

3) 用new构造就指向新对象

// js 中经过 new 关键词来调用构造函数,此时 this 会绑定杂该新对象上
var someClass = function(){
    this.value = 123;
}
var myCreate = new someClass();
console.log(myCreate.value); // 123
复制代码

4)经过 apply 或 call 或 bind 来改变 this 的指向

var myObject = { value: 123 };
var foo = function(){
    console.log(this);
}
foo(); // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
foo.apply(myObject); // {value: 123}
foo.call(myObject); // {value: 123}
var newFoo = foo.bind(myObject);
newFoo(); // {value: 123}
复制代码

闭包

闭包英文是 Closure ,简而言之,闭包就是

  1. 函数的局部集合,只是这些局部变量在函数返回后会继续存在
  2. 函数的“堆栈”在函数返回后并不释放,能够理解为这些函数堆栈并不在栈上分配而是在堆上分配
  3. 当在一个函数内部定义另一个函数就会产生闭包

做为局部变量均可以被函数内的代码访问,这个和静态语言是没有差异的,闭包的差异在于局部变量能够在函数执行结束后仍然被函数外的代码访问,这意味着函数必须返回一个指向闭包的“引用”,或将这个“引用”赋值给某个外部变量,才能保证闭包中局部变量被外部代码访问,固然包含这个引用的实体应该是一个对象。可是ES并无提供相关的成员和方法来访问包中的局部变量,可是在ES中,函数对象中定义的内部函数是能够直接访问外部函数的局部变量,经过这种机制,能够用以下方式完成对闭包的访问。

function greeting(name){
    var text = "Hello " + name; // 局部变量
    // 每次调用时,产生闭包,并返回内部函数对象给调用者
    return function(){
        console.log(text);
    }
}
var sayHello = greeting('Closure');
// 经过闭包访问到了局部变量text
sayHello();  // 输出Hello Closure 
复制代码

在 ECMAscript 的脚本函数运行时,每一个函数关联都有一个执行上下文场景(Exection Context),这个执行上下文包括三个部分

  • 文法环境(The LexicalEnvironment)
  • 变量环境(The VariableEnvironment)
  • this绑定

其中第三点this绑定与闭包无关,不在本文中讨论。文法环境中用于解析函数执行过程使用到的变量标识符。咱们能够将文法环境想象成一个对象,该对象包含了两个重要组件,环境记录(Enviroment Recode),和外部引用(指针)。环境记录包含包含了函数内部声明的局部变量和参数变量,外部引用指向了外部函数对象的上下文执行场景。全局的上下文场景中此引用值为NULL。这样的数据结构就构成了一个单向的链表,每一个引用都指向外层的上下文场景。

例如上面咱们例子的闭包模型应该是这样,sayHello函数在最下层,上层是函数greeting,最外层是全局场景。以下图:

closure

所以当sayHello被调用的时候,sayHello会经过上下文场景找到局部变量text的值,所以在屏幕的对话框中显示出”Hello Closure”

针对一些例子来帮助你们更加深刻的理解闭包,下面共有5个样例,例子来自于JavaScript Closures For Dummies(镜像)

例子1:闭包中局部变量是引用而非拷贝

function say667(){
    var num = 666;
    var sayConsole = function(){
        console.log(num);
    }
    num++;
    return sayConsole;
}
var sayConsole = say667();
sayConsole(); // 667
复制代码

例子2:多个函数绑定同一个闭包,由于他们定义在同一个函数内。

function setupSomeGlobals(){
    var num = 666;
    gConsoleNumber = function() { console.log(num); }
    gIncreaseNumber = function() { num++; }
    gSetNumber = function(x) { num = x; }
}
setupSomeGlobals();
gConsoleNumber(); // 666
gIncreaseNumber();
gConsoleNumber(); // 667
gSetNumber(12);
gConsoleNumber(); // 12
复制代码

例子3:当在一个循环中赋值函数时,这些函数将绑定一样的闭包

function buildList(list){
    var result = [];
    for(var i = 0; i < list.length; i++){
        var item = 'item' + list[i];
        result.push(function(){
            console.log(item+' '+list[i]);
        })
    }
    return result;
}
function testList(){
    var fnList = buildList([1,2,3]);
    for(var j = 0; j < fnList.length; j++){
        fnList[j]();
    }
}
testList(); // 输出3次 item3 undefined
复制代码

testList的执行结果是弹出item3 undefined窗口三次,由于这三个函数绑定了同一个闭包,并且item的值为最后计算的结果,可是当i跳出循环时i值为4,因此list[4]的结果为undefined.

例子4:外部函数全部局部变量都在闭包内,即便这个变量声明在内部函数定义以后。

function sayAlice(){
    var sayConsole = function(){
        console.log(alice);
    }
    var alice = "Hello Alice";
    return sayConsole;
}
var helloAlice=sayAlice();
helloAlice();
复制代码

执行结果输出”Hello Alice”的窗口。即便局部变量声明在函数sayAlert以后,局部变量仍然能够被访问到。

例子5:每次函数调用的时候建立一个新的闭包

function newClosure(someNum,someRef){
    var num = someNum;
    var anArray = [1,2,3];
    var ref = someRef;
    return function(x){
        num += x;
        anArray.push(num);
        console.log('num: ' + num +'\nanArray ' + anArray.toString() +'\nref.someVar ' + ref.someVar);
    }
}

closure1=newClosure(40,{someVar:'closure 1'});
closure2=newClosure(1000,{someVar:'closure 2'});

closure1(5); // num: 45 anArray 1,2,3,45 ref.someVar closure 1
closure2(-10); // num: 990 anArray 1,2,3,990 ref.someVar closure 2
复制代码
闭包的缺点:

(1)因为闭包会使得函数中的变量都被保存在内存中,内存消耗很大,因此不能滥用闭包,不然会形成网页的性能问题,在IE中可能致使内存泄露。解决方法是,在退出函数以前,将不使用的局部变量所有删除。

(2)闭包会在父函数外部,改变父函数内部变量的值。因此,若是你把父函数看成对象(object)使用,把闭包看成它的公用方法(Public Method),把内部变量看成它的私有属性(private value),这时必定要当心,不要随便改变父函数内部变量的值。

例子:

function Cars(){
  this.name = "Benz";
  this.color = ["white","black"];
}
Cars.prototype.sayColor = function(){
  var outer = this;
  return function(){
    return outer.color
  };
};
 
var instance = new Cars();
console.log(instance.sayColor()())
复制代码

改造:

function Cars(){
  this.name = "Benz";
  this.color = ["white","black"];
}
Cars.prototype.sayColor = function(){
  var outerColor = this.color; //保存一个副本到变量中
  return function(){
    return outerColor; //应用这个副本
  };
  outColor = null; //释放内存
};
 
var instance = new Cars();
console.log(instance.sayColor()())
复制代码

做用域

在JS当中一个变量的做用域(scope)是程序中定义这个变量的区域。变量分为两类:全局(global)的和局部的。其中全局变量的做用域是全局性的,即在JavaScript代码中,它到处都有定义。而在函数以内声明的变量,就只在函数体内部有定义。它们是局部变量,做用域是局部性的。函数的参数也是局部变量,它们只在函数体内部有定义。

咱们能够借助JavaScript的做用域链(scope chain)更好地了解变量的做用域。每一个JavaScript执行环境都有一个和它关联在一块儿的做用域链。这个做用域链是一个对象列表或对象链。当JavaScript代码须要查询变量x的值时(这个过程叫作变量解析(variable name resolution)),它就开始查看该链的第一个对象。若是那个对象有一个名为x的属性,那么就采用那个属性的值。若是第一个对象没有名为x的属性,JavaScript就会继续查询链中的第二个对象。若是第二个对象仍然没有名为x的属性,那么就继续查询下一个对象,以此类推。若是查询到最后(指顶层代码中)不存在这个属性,那么这个变量的值就是未定义的。

var a,b;
(function(){
    alert(a); // undefined 
    alert(b); // undefined 
    var a = b = 3;
    alert(a); // 3
    alert(b); // 3
})();
    alert(a); // undefined 
	alert(b); // 3
复制代码

以上代码至关于

var a,b;
(function(){
    alert(a);
    alert(b);
    var a = 3;
    b = 3;
    alert(a);
    alert(b);
})();
    alert(a);

复制代码

事件委托是什么?

概述

什么叫作事件委托,别名叫事件代理,JavaScript 高级程序设计上讲。事件委托就是利用事件冒泡,只指定一个事件处理程序,就能够管理某一类型的全部事件。

实际例子:

有三个同事预计会在周一收到快递,为签收快递,有两种方法:一是三我的在公司门口等快递,二是委托给前台的小姐代为签收。现实生活中,咱们大多采用委托的方案(公司也不会容忍那么多人站在门口)。前台小姐收到快递后,会判断收件人是谁,按照收件人的要求签收,甚至是代付。这种方案还有一个好处就是,即便公司来了不少新员工(无论多少),前台小姐也会在收到寄给新员工们的快递后核实代为签收。

这里有2层意思: 第1、如今委托前台的小姐是能够代为签收的,即程序中的现有的 DOM 节点是有事件的。

第2、新员工也能够被前台小姐代为签收,即程序中新添加的 DOM 节点也是有事件的。

为何要使用事件委托

通常来讲,DOM 须要有事件处理程序,就会直接给它设处理程序,可是若是是不少 DOM 须要添加处理事件呢?例如咱们有100个 li,每一个 li 都有相同的 click 点击事件,可能咱们会用到 for 循环,来遍历全部 li ,而后给它们添加事件,那么会存在什么样的问题?

在 JavsScript 中,添加到页面上的事件处理程序数量将直接影响到总体运行性能,由于须要不断地与 DOM 进行交互,访问 DOM 的次数越多,引发浏览器重绘与重排的次数也就越多,就会延长整个页面的交互就绪时间,这就是为何性能优化的主要思想之一就是减小 DOM 操做的缘由、若是要用到事件委托,就会将全部的操做都放在 js 程序里面,与 DOM 的操做就只须要交互一次,这样就能够大大减小与 DOM 的交互次数,提升性能。

每一个函数都是一个对象,是对象就会占用内存,对象越多,内存占用率就越大,天然性能就越差了(内存不够用,是硬伤,哈哈),好比上面的100个li,就要占用100个内存空间,若是是1000个,10000个呢,那只能说呵呵了,若是用事件委托,那么咱们就能够只对它的父级(若是只有一个父级)这一个对象进行操做,这样咱们就须要一个内存空间就够了,是否是省了不少,天然性能就会更好。

事件委托的原理

事件委托是利用事件的冒泡原理来实现的,何为事件冒泡?就是事件从最深的节点开始执行,而后逐步向上传播事件,例子:

页面上有一个节点树,div>ul>li>a,好比给最里面的 a 加一个 click 点击事件,那么这个事件就会一层一层的往外执行,执行顺序 a>li>ul>div,有这么一个机制,那么咱们给最外面的 div 加点击事件,那么里面的 ul,li,a 作点击事件的时候,都会冒泡到最外层的 div 上面,都会触发,这就是事件委托,委托他们父级代为执行事件。

事件委托怎么实现

<ul id="ul">
    <li>111</li>
    <li>222</li>
    <li>333</li>
    <li>444</li>
</ul>
复制代码

实现功能是点击li,弹出123:

window.onload = function(){
    var oUl = document.getElementById('ul');
    var aLi = oUl.getElementsByTagName('li');
    for(var i = 0; i < aLi.length; i++){
        aLi[i].onclick = function(){
            alert(123);
        }
    }
}
复制代码

上面的代码的意思很简单,相信不少人都是这么实现的,咱们看看有多少次的dom操做,首先要找到ul,而后遍历li,而后点击li的时候,又要找一次目标的li的位置,才能执行最后的操做,每次点击都要找一次li;

那么咱们用事件委托的方式作又会怎么样呢?

window.onload = function(){
    var oUl = document.getElementById('ul');
    oUl.onclick = function(){
        alert(123);
    }
}
复制代码

这里用父级ul作事件处理,当li被点击时,因为冒泡原理,事件就会冒泡到ul上,由于ul上有点击事件,因此事件就会触发,固然,这里当点击ul的时候,也是会触发的,那么问题就来了,若是我想让事件代理的效果跟直接给节点的事件效果同样怎么办,好比说只有点击li才会触发,不怕,咱们有绝招:

Event对象提供了一个属性叫target,能够返回事件的目标节点,咱们成为事件源,也就是说,target就能够表示为当前的事件操做的dom,可是不是真正操做dom,固然,这个是有兼容性的,标准浏览器用ev.target,IE浏览器用event.srcElement,此时只是获取了当前节点的位置,并不知道是什么节点名称,这里咱们用nodeName来获取具体是什么标签名,这个返回的是一个大写的,咱们须要转成小写再作比较(习惯问题):

window.onload = function(){
    var oUl = document.getElementById("ul");
    oUl.onclick = function(ev){
        var ev = ev || window.event;
        var target = ev.target || ev.srcElement;
        if(target.nodeName.toLowerCase() == "li"){
            alert(target.innerHTML);
        }
    }
}
复制代码

这样改下就只有点击li会触发事件了,且每次只执行一次dom操做,若是li数量不少的话,将大大减小dom的操做,优化的性能可想而知!

上面的例子是说li操做的是一样的效果,要是每一个li被点击的效果都不同,那么用事件委托还有用吗?

var Add = document.getElementById("add");
var Remove = document.getElementById("remove");
var Move = document.getElementById("move");
var Select = document.getElementById("select");
Add.onclick = function(){
    alert('添加');
};
Remove.onclick = function(){
    alert('删除');
};
Move.onclick = function(){
    alert('移动');
};
Select.onclick = function(){
    alert('选择');
}
复制代码

上面实现的效果我就很少说了,很简单,4个按钮,点击每个作不一样的操做,那么至少须要4次dom操做,若是用事件委托,能进行优化吗?

var oBox = document.getElementById("box");
oBox.onclick = function(ev){
    var ev = ev || window.event;
    var target = ev.target || ev.srcElement;
    if(target.nodeName.toLowerCase() == 'input'){
        switch(target.id) {
            case 'add':
            alert('添加');
            break;
            case 'remove':
            alert('删除');
            break;
            case 'move':
            alert('移动');
            break;
            case 'select':
            alert('选择');
            break;
            default :
            alert('业务错误');
            break;
        }
    }
}
复制代码

用事件委托就能够只用一次dom操做就能完成全部的效果,比上面的性能确定是要好一些的

如今讲的都是document加载完成的现有dom节点下的操做,那么若是是新增的节点,新增的节点会有事件吗?也就是说,一个新员工来了,他能收到快递吗?

<input type="button" name="" id="btn" value="添加" />
<ul id="ul1">
    <li>111</li>
    <li>222</li>
    <li>333</li>
    <li>444</li>
</ul>
复制代码

如今是移入li,li变红,移出li,li变白,这么一个效果,而后点击按钮,能够向ul中添加一个li子节点

window.onload = function(){
    var oBtn = document.getElementById("btn");
    var oUl = document.getElementById("ul");
    var aLi = oUl.getElementsByTagName("li");
    var num = 4;

    // 鼠标移入变红,移出变白
    for(var i = 0; i < aLi.length; i++){
        aLi[i].onmouseover = function(){
            this.style.background = 'red';
        }
        aLi[i].onmouseout = function(){
            this.style.background = '#fff';
        }
    }

    // 新增节点
    oBtn.onclick = function(){
        num++;
        var oLi = document.createElement('li');
        oLi.innerHTML = 111 * num;
        oUl.appendChild(oLi);
    }
}
复制代码

这是通常的作法,可是你会发现,新增的li是没有事件的,说明添加子节点的时候,事件没有一块儿添加进去,这不是咱们想要的结果,那怎么作呢?通常的解决方案会是这样,将for循环用一个函数包起来,命名为mHover,以下:

// 将鼠标移出移入包装为一个函数
window.onload = function(){
    var oBtn = document.getElementById("btn");
    var oUl = document.getElementById("ul");
    var aLi = oUl.getElementsByTagName("li");
    var num = 4;


    // 鼠标移入变红,移出变白
    function mHover(){
        for(var i = 0; i < aLi.length; i++){
            aLi[i].onmouseover = function(){
                this.style.background = 'red';
            }
            aLi[i].onmouseout = function(){
                this.style.background = '#fff';
            }
        }
    }
    mHover();
    // 新增节点
    oBtn.onclick = function(){
        num++;
        var oLi = document.createElement('li');
        oLi.innerHTML = 111 * num;
        oUl.appendChild(oLi);
        mHover();
    }
}
复制代码

虽然功能实现了,看着还挺好,但实际上无疑是又增长了一个dom操做,在优化性能方面是不可取的,那么有事件委托的方式,能作到优化吗?

window.onload = function(){
    var oBtn = document.getElementById("btn");
    var oUl = document.getElementById("ul");
    var aLi = oUl.getElementsByTagName("li");
    var num = 4;


    // 事件委托 鼠标移入变红,移出变白
    // 添加的子元素也有事件
    oUl.onmouseover = function(){
        var ev = ev || window.event;
        var target = ev.target || ev.srcElement;
        if(target.nodeName.toLowerCase() == 'li'){
            target.style.background = 'red';
        }
    }		
    oUl.onmouseout = function(){
        var ev = ev || window.event;
        var target = ev.target || ev.srcElement;
        if(target.nodeName.toLowerCase() == 'li'){
            target.style.background = '#fff';
        }
    }
    // 新增节点
    oBtn.onclick = function(){
        num++;
        var oLi = document.createElement('li');
        oLi.innerHTML = 111 * num;
        oUl.appendChild(oLi);
    }
}
复制代码

另一个思考的问题

如今给一个场景 ul > li > div > p,div占满li,p占满div,仍是给ul绑定时间,须要判断点击的是否是li(假设li里面的结构是不固定的),那么e.target就多是p,也有多是div,这种状况你会怎么处理呢?

<ul id="test">
    <li>
        <p>11111111111</p>
    </li>
    <li>
        <div>
            22222222
        </div>
    </li>
    <li>
        <span>3333333333</span>
    </li>
    <li>4444444</li>
</ul>
复制代码

如上列表,有4个li,里面的内容各不相同,点击li,event对象确定是当前点击的对象,怎么指定到li上,下面我直接给解决方案:

var oUl = document.getElementById('test');
oUl.addEventListener('click',function(ev){
    var target = ev.target;
    while (target !== oUl) {
        if(target.tagName.toLowerCase() == 'li'){
            alert(target.innerHTML);
            break;
        }
        target = target.parentNode;
    }
});
复制代码

如何阻止事件冒泡和默认事件

在解答这个问题以前,先来看看事件的执行顺序

<div>
    <ul>
        <li>冒泡/捕获</li>
    </ul>
</div>
复制代码

实例:

var html = document.documentElement;
var body = document.body;
var div = body.querySelector('div');
var ul = body.querySelector('ul');
var li = body.querySelector('li');


// 捕获
ul.addEventListener('click',captureCallback,true);
li.addEventListener('click',captureCallback,true);
div.addEventListener('click',captureCallback,true);
body.addEventListener('click',captureCallback,true);
html.addEventListener('click',captureCallback,true);	

// 冒泡
ul.addEventListener('click',bubblingCallback,false);
li.addEventListener('click',bubblingCallback,false);
div.addEventListener('click',bubblingCallback,false);
body.addEventListener('click',bubblingCallback,false);
html.addEventListener('click',bubblingCallback,false);

function captureCallback(e){
    // e.stopPropagation();
    var target = e.currentTarget;
    console.log(target.tagName);
}	
function bubblingCallback(e){
    // e.stopPropagation();
    var target = e.currentTarget;
    console.log(target.tagName);
}
复制代码

点击 html 中的冒泡/捕获,能够获得如下结果

capturing&bubbling phase.html:36 HTML
capturing&bubbling phase.html:36 BODY
capturing&bubbling phase.html:36 DIV
capturing&bubbling phase.html:36 UL
capturing&bubbling phase.html:36 LI
capturing&bubbling phase.html:40 LI
capturing&bubbling phase.html:40 UL
capturing&bubbling phase.html:40 DIV
capturing&bubbling phase.html:40 BODY
capturing&bubbling phase.html:40 HTML
复制代码

总结就是先捕获,后冒泡,捕获是从上到下,冒泡是从下到上。(形象说法:捕获像石头沉入海底,冒泡像气球冒出水面)

去掉 函数 bubblingCallback 中的 e.stopPropagation()的注释,则只会进行捕获,输出

capturing&bubbling phase.html:40 HTML
capturing&bubbling phase.html:40 BODY
capturing&bubbling phase.html:40 DIV
capturing&bubbling phase.html:40 UL
capturing&bubbling phase.html:40 LI
capturing&bubbling phase.html:45 LI
复制代码

而取消 函数 captureCallback中的 e.stopPropagation() 注释,则只捕获到 最上面的标签

capturing&bubbling phase.html:40 HTML
复制代码

经过上面,咱们能够得知, event.stopPropagation(),能够阻止 捕获和冒泡阶段当前事件的进一步传播。

规则:

  1. 在冒泡事件和捕获事件同时存在的状况下,捕获事件优先级高一点
  2. 在同一个元素的绑定事件中,冒泡和捕获没有次序之分,遵循Javascript的执行顺序。
  3. 在元素上同时绑定捕获事件和冒泡事件,若是经过此元素的子级元素触发,则优先触发捕获事件,若不经过此元素的子级元素触发,则按照Javascript执行顺序触发。

事件不一样浏览器处理函数

  • element.addEventListener(type, listener[, useCapture]); // IE6~8不支持(捕获和冒泡经过useCapture,默认false)
  • element.attachEvent(’on’ + type, listener); // IE6~10,IE11不支持(只执行冒泡事件)
  • element[’on’ + type] = function(){} // 全部浏览器(默认执行冒泡事件)

事件不一样浏览器处理函数

W3C 中定义了 3个事件阶段,依次是 捕获阶段、目标阶段、冒泡阶段。事件对象按照上图的传播路径依次完成这些阶段。若是某个阶段不支持或事件对象的传播被终止,那么该阶段就会被跳过。举个例子,若是Event.bubbles属性被设置为false,那么冒泡阶段就会被跳过。若是Event.stopPropagation()在事件派发前被调用,那么全部的阶段都会被跳过。

  • 捕获 阶段:在事件对象到达事件目标以前,事件对象必须从 window 通过目标的祖先节点传播到事件目标。这个阶段被咱们称为捕获阶段,在这个阶段注册的事件监听器在事件到达其目标前必须先处理事件。
  • 目标 阶段:事件对象到达其事件目标,这个阶段被咱们称之为目标阶段。一旦事件对象达到事件目标,该阶段的事件监听器就要对它进行处理。若是一个事件对象类型被标志为不能冒泡。那么对应的事件对象在到此阶段时就会终止传播。
  • 冒泡 阶段:事件对象以一个与捕获阶段相反的方向从事件目标传播通过其祖先节点传播到 window ,这个阶段被称之为冒泡阶段,在此阶段注册的事件监听器会对应的冒泡事件进行处理

在一个事件完成了全部阶段的传播路径后,它的Event.currentTarget会被设置为null而且Event.eventPhase会被设为0。Event的全部其余属性都不会改变(包括指向事件目标的Event.target属性)

跨浏览器的事件处理函数

var EventUtil = {
  addHandler: function(element, type, handler) {
    if (element.addEventListener) {  // DOM2
      element.addEventListener(type, handler, false);
    } else if (element.attachEvent) {  // IE
      element.attachEvent('on' + type, handler);
    } else {  // DOM0
      element['on' + type] = handler;
    }
  },

  removeHandler: function(element, type, handler) {
    if (element.removeEventListener) {
      element.removeEventListener(type, handler, false);
    } else if (element.detachEvent) {
      element.detachEvent('on' + type, handler);
    } else {
      element['on' + type] = null;
    }
  }
};
复制代码

跨浏览器的事件对象:

var EventUtil = {
  getEvent: function(e) {
    return e ? e : window.e;
  },

  getTarget: function(e) {
    return e.target || e.srcElement;
  },

  preventDefault: function(e) {
    if (e.preventDefault) {
      e.preventDefault();
    } else {
      e.returnValue = false;
    }
  },

  stopPropagation: function(e) {
    if (e.stopPropagation) {
      e.stopPropagation()
    } else {
      e.cancelBubble = true;
    }
  }
}
复制代码

js冒泡和捕获是事件的两种行为,使用event.stopPropagation()起到阻止捕获和冒泡阶段中当前事件的进一步传播。使用event.preventDefault()能够取消默认事件。

防止冒泡和捕获

w3c的方法是e.stopPropagation(),IE则是使用e.cancelBubble = true

topPropagation也是事件对象(Event)的一个方法,做用是阻止目标元素的冒泡事件,可是会不阻止默认行为。什么是冒泡事件?如在一个按钮是绑定一个”click”事件,那么”click”事件会依次在它的父级元素中被触发 。stopPropagation就是阻止目标元素的事件冒泡到父级元素。如:

<div id='div' onclick='alert("div");'>
    <ul onclick='alert("ul");'>
    	<li onclick='alert("li");'>test</li>
    </ul>
</div>
复制代码

单击时,会依次触发alert(“li”),alert(“ul”),alert(“div”),这就是事件冒泡。

阻止冒泡

window.event? window.event.cancelBubble = true : e.stopPropagation();
复制代码

取消默认事件

w3c的方法是e.preventDefault(),IE则是使用e.returnValue = false

preventDefault它是事件对象(Event)的一个方法,做用是取消一个目标元素的默认行为。既然是说默认行为,固然是元素必须有默认行为才能被取消,若是元素自己就没有默认行为,调用固然就无效了。什么元素有默认行为呢?如连接 <a>,提交按钮 <input type="submit"> 等。当Event 对象的 cancelable为false时,表示没有默认行为,这时即便有默认行为,调用preventDefault也是不会起做用的。

咱们都知道,连接 <a> 的默认动做就是跳转到指定页面,下面就以它为例,阻止它的跳转:

//假定有连接<a href="http://laibh.top/" id="testA" >laibh.top</a>
var a = document.getElementById("test");
a.onclick =function(e){
if(e.preventDefault){
    	e.preventDefault();
    }else{
    	window.event.returnValue == false;
    }
}
复制代码

return false

javascript的return false只会阻止默认行为,而是用jQuery的话则既阻止默认行为又防止对象冒泡

下面这个使用原生js,只会阻止默认行为,不会中止冒泡

<div id='div' onclick='alert("div");'>
<ul onclick='alert("ul");'>
    <li id='ul-a' onclick='alert("li");'><a href="http://caibaojian.com/"id="testB">caibaojian.com</a></li>
</ul>
</div>
<script> var a = document.getElementById("testB"); a.onclick = function(){ return false; }; </script>
复制代码

总结使用方法

当须要中止冒泡行为时,可使用

function stopBubble(e) { 
//若是提供了事件对象,则这是一个非IE浏览器 
if ( e && e.stopPropagation ) 
    //所以它支持W3C的stopPropagation()方法 
    e.stopPropagation(); 
else 
    //不然,咱们须要使用IE的方式来取消事件冒泡 
    window.event.cancelBubble = true; 
}
复制代码

当须要阻止默认行为时,可使用

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

事件注意点

  • event表明事件的状态,例如触发event对象的元素、鼠标的位置及状态、按下的键等等;
  • event对象只在事件发生的过程当中才有效。

firefox里的event跟IE里的不一样,IE里的是全局变量,随时可用;firefox里的要用参数引导才能用,是运行时的临时变量。

在IE/Opera中是window.event,在Firefox中是event;而事件的对象,在IE中是window.event.srcElement,在Firefox中是event.target,Opera中二者均可用。

function a(e){
    var e = (e) ? e : ((window.event) ? window.event : null); 
    var e = e || window.event; // firefox下window.event为null, IE下event为null
}
复制代码

查找 添加 删除 替换 插入到某个节点的方法

// 查找节点
document.getElementById('id'); // 经过id查找,返回惟一的节点,若是有多个将会返回第一个,在IE六、7中有个bug,会返回name值相同的元素,全部要作一个兼容
document.getElementsByClassName('class'); // 经过class查找,返回节点数组
document.getElementsByTagName('div'); // 经过标签名

// 建立节点
document.createDocumentFragment(); // 建立内存文档碎片
document.createElement(); // 建立元素
document.createTextNode(); // 建立文本节点

// 添加节点
var oDiv = document.createElement('div');

// 插入 Dom 节点
// 方法1:appendChild() 把节点插入到父节点的末尾
document.body.appendChild(oDiv) // 把 div 插入到 body 中,而且位于末尾
// 方法2:insertBefore() 把节点插入到父节点的某个兄弟节点的前面
var oP = createElement('p'); // 建立一个 p 节点
document.body.insertBefore(oP,oDiv); // 把 p 节点插入到 div 的前面

// 删除节点
document.body.removeChild(oP); // 删除 p 节点

// 替换 Dom 节点
var oSpan = document.createElement('span');
document.body.replaceChild(oSpan,oBox); // 用 span 标签替换 div 标签

复制代码

javaScript 的本地对象,内置对象和宿主对象

内部对象

js中的内部对象包括Array、Boolean、Date、Function、Global、Math、Number、Object、RegExp、String以及各类错误类对象,包括Error、EvalError、RangeError、ReferenceError、SyntaxError和TypeError。

本地对象

Array、Boolean、Date、Function、Number、Object、RegExp、String、Error、EvalError、RangeError、ReferenceError、SyntaxError和TypeError 等 new 能够实例化

内置对象

其中Global和Math这两个对象又被称为“内置对象”,这两个对象在脚本程序初始化时被建立,没必要实例化这两个对象。

宿主对象

宿主环境:通常宿主环境由外壳程序建立与维护,只要能提供js引擎执行的环境均可称之为外壳程序。如:web浏览器,一些桌面应用系统等。即由web浏览器或是这些桌面应用系统早就的环境即宿主环境。

那么宿主就是浏览器自带的 document , window 等

===== 的不一样

前者会自动转换类型,然后者不会。

比较过程:

双等号==:

  1. 若是两个值类型相同,再进行三个等号(===)的比较
  2. 若是两个值类型不一样,也有可能相等,需根据如下规则进行类型转换再比较
    1. 若是一个是 null,一个是 undefined 那么相等
    2. 若是一个是字符串,一个是数值,把字符串转换成数值以后再进行比较

三等号 === :

  1. 若是类型不一样,就必定不相等
  2. 若是两个都是数值,而且是同一个值,那么相等;若是其中至少一个是 NaN,那么不相等(判断一个值是不是 NaN,只能使用 isNaN() 来判断)
  3. 若是两个都是字符串,每一个位置的字符都同样,那么相等,不然不相等
  4. 若是两个值都是 true,或是 false,那么相等
  5. 若是两个值都引用同一个对象或者是函数,那么相等,不然不相等
  6. 若是两个值都是 null,或者是 undefined 那么相等。

javaScript 的同源策略

同源策略的含义:脚本只能读取和所属文档来源相同的窗口和文档的属性。

同源指的是主机名、协议和端口号的组合

同源策略

同源策略带来的限制

  1. cookie、LocalStorage 和 IndexDB没法读取

  2. DOM 没法获取

  3. AJAX请求没法发送

主流跨域请求解决方案

一、JSONP 实现跨域

为了便于客户端使用数据,逐渐造成了一种非正式传输协议。人们把它称做JSONP。该协议的一个要点就是容许用户传递一个callback参数给服务端,而后服务端返回数据时会将这个callback参数做为函数名来包裹住JSON数据,这样客户端就能够随意定制本身的函数来自动处理返回数据了。

jsonp的核心是动态添加 script标签 来调用服务器提供的js脚本。

说说JSON和JSONP

JSONP实现原理?

  1. JS 跨域请求资源会被限制。可是在页面中,script 标签跨域时,倒是没有限制的(frame,img同理)。
  2. 咱们经过,script的src属性,请求服务器,并经过参数(如:?callback=foo,foo为本地一个执行的方法)告诉服务器返回指定格式的JS脚本,并将数据封装在此脚本中。
  3. 服务器再配合客户端返回一段脚本 (如:* foo({“id”: 123, “name” : 张三, “age”: 17});* ),其实返回的就是 一个客户端本地的一个 可执行的方法的方法名, 并将要返回的 数据封装在了参数 里。
  4. 请求到资源后,本地就会执行此方法,经过对参数的处理,也就获取到了咱们所要的数据。

JSONP的局限性

JSONP 方式,当然方便强大。可是他的局限性在于,它没法完成POST请求。便是咱们将type改成post,在发送请求时,依然会是以Get的方式。

二、CORS跨域

CORS原理

CORS(Cross-Origin-Resource Sharing,跨院资源共享)是一种容许多种资源(图片,Css文字,Javascript等)在一个Web页面请求域以外的另外一个域的资源的机制。 跨域资源共享这种机制让Web应用服务器支持跨站访问控制,从而使得安全的进行跨站数据传输成为了可能。

经过这种机制设置一系列的响应头,这些响应头容许浏览器与服务器进行交流,实现资源共享。

各语言设置响应头的方法

CORS 解决方案相对于JSONP 更加灵活,并且支持POST请求,是跨域的根源性解决方案

三、代理层

JSONP 和CORS 是主流的 跨域问题 的解决方案。除了他们呐,还有一种解决方案,就是代理层。简要说一下

JS 调用本源的后台的方法(这样就不存在跨域的问题),而经过后台(任何具备网络访问功能的后台语言,ASP.NET ,JAVA,PHP等)去跨域请求资源,然后将结果返回至前台。

另外也能够看看

同源策略

JavaScript的同源策略 Redirect 1

编写一个数组去重的方法

var arr = [1,1,3,4,2,4,7] => [1,2,4,2,7]

// 方法1 双重循环,逐个判断数组中某个元素是否与其余元素相同,相同则去掉,而且索引减1,重复执行直到双重遍历完成
function DuplicateRemoval(arr){
    for(var i = 0; i < arr.length-1; i++){
        for(var j = i + 1; j < arr.length; j++){
            if(arr[i] == arr[j]){
                arr.splice(j,1);
                j--;
            }
        }
    }
    return arr;
}
var arr=[1,1,3,4,2,4,7];
console.log(DuplicateRemoval(arr));

// 方法2 借助 indexOf() 方法判断此元素在该数组中首次出现位置下标与循环的下标是否相同
function DuplicateRemoval(arr){
    for(var i = 0; i < arr.length; i++){
        if(arr.indexOf(arr[i]) !== i){
            arr.splice(i,1);
            i--
        }
    }
    return arr;
}
var arr=[1,1,3,4,2,4,7];
console.log(DuplicateRemoval(arr));	

// 方法3 利用数组的方法 filter()
var arr=[1,1,3,4,2,4,7];
var result = arr.filter( (element, index, self) => self.indexOf(element) === index );
console.log(result);

// 方法4 利用新数组 经过indexOf判断当前元素在数组中的索引若是与循环相同则添加到新数组中
var arr=[1,1,3,4,2,4,7];
function Add(arr){
    var result = [];
    for(var i = 0; i < arr.length; i++){
        if(arr.indexOf(arr[i]) === i){
            result.push(arr[i]);
        }
    }
    return result;
}
console.log(Add(arr));

// 方法5 ES6方法 Set() Set函数能够接受一个数组(或相似数组的对象)做为参数,用来初始化。
var arr=[1,1,3,4,2,4,7];
console.log([...new Set(arr)]);
复制代码

JavaScript 的数据类型都有什么

基本数据类型:String,Boolean,number,undefined,object,Null

引用数据类型:Object(Array,Date,RegExp,Function)

类型检测

typeof

typeof 运算精度只能是基础类型也就是 numberstringundefinedbooleanobject ,要注意的是 null 和数组使用 typeof 运算符获得的也是 object

console.log(typeof 123); // number
console.log(typeof 'type'); // string
console.log(typeof null); // object
console.log(typeof undefined); // undefined
console.log(typeof true); // boolean
console.log(typeof []); // object
console.log(typeof {}); // object
console.log(typeof function(){}); // function
复制代码
instanceof

用于判断一个变量是否某个对象的实例,或用于判断一个变量是否某个对象的实例

instanceof 运算符能够精确到是哪种类型的引用,但 instanceof 也有一个缺陷就是对于直接赋值的数字,字符串,布尔值以及数组是不能将其识别为NumberStringBooleanArray

console.log(123 instanceof Number); // false
console.log('type' instanceof String); // false
console.log(true instanceof Boolean); // false
console.log([] instanceof Array); // true
console.log({} instanceof Object); // true
console.log(function(){} instanceof Function); // true
复制代码
toString.call()
console.log(toString.call(123) ); // [object Number]
console.log(toString.call('type')); // [object String]
console.log(toString.call(null)); // [object Null]
console.log(toString.call(undefined)); // [object Undefined]
console.log(toString.call(true)); // [object Boolean]
console.log(toString.call([])); // [object Array]
console.log(toString.call({})); // [object Object]
console.log(toString.call(function(){})); // [object Function] 
复制代码
constructor

构造函数的属性 用来判别对建立实例对象的函数的引用,另外 constructor 能够被改写

var number = 123,
string = 'type',
boolean = true,
arr = [],
obj = {},
fn = function(){};

console.log( number.constructor); // ƒ Number() { [native code] }
console.log( string.constructor); // ƒ String() { [native code] }
console.log( boolean.constructor); // ƒ Boolean() { [native code] }
console.log( arr.constructor); // ƒ Array() { [native code] }
console.log( obj.constructor); // ƒ Object() { [native code] }
console.log( fn.constructor); // ƒ Function() { [native code] }
复制代码

也能够用 constructor.name就会返回函数名

Object.prototype.toString.call()

这个方法其实跟上面 toString()是同样的,只是上面那个方法有可能会被重写。因此用这个比较稳妥,全部对象都包含有一个内部属性[[Class]] ,这个属性不能直接访问,可是咱们能够经过Object.prototype.toString.call()

console.log(Object.prototype.toString.call(123) ); // [object Number]
console.log(Object.prototype.toString.call('type')); // [object String]
console.log(Object.prototype.toString.call(null)); // [object Null]
console.log(Object.prototype.toString.call(undefined)); // [object Undefined]
console.log(Object.prototype.toString.call(true)); // [object Boolean]
console.log(Object.prototype.toString.call([])); // [object Array]
console.log(Object.prototype.toString.call({})); // [object Object]
console.log(Object.prototype.toString.call(function(){})); // [object Function] 
复制代码

须要注意的是IE6/7/8中 Object.prototype.toString.apply(null)返回“[object Object]”。

js 中 innerText/value/innerHTML三个属性的区别

<div value = "3" id="target3">
    <input type="text" value="1" id="target1">
    <span id="target2" value="2">span</span>
</div>
复制代码
var a = document.getElementById("target1");
var b = document.getElementById("target2");
var c = document.getElementById("target3");
console.log('----------');
console.log(a.value); // 1
console.log(a.innerHTML);
console.log(a.innerText);	
console.log('----------');
console.log(b.value); // undefined
console.log(b.innerHTML); // span
console.log(b.innerText); // span 
console.log('----------');
console.log(c.value); // undefined
console.log(c.innerHTML); 
// <input type="text" value="1" id="target1"><span id="target2" value="2">span</span>
console.log(c.innerText); // span
复制代码

总结:

  1. innerText是标签内的文本,输出的是字符串,它能够得到某个节点下面的全部标签内的文本
  2. innerHtml 能够得到某个DOM 节点内部的 HTML 代码
  3. value是表单元素的属性,得到是表单元素里面的值

但愿获取到页面中全部的 checkbox 怎么作?( ( 不使用第三方框架) )

html:

<input type="checkbox">
<input type="checkbox">
<input type="checkbox">
复制代码
window.onload = function(){
    var domList = document.getElementsByTagName('input');
    var checkBoxList = []; // 返回全部的 checkbox 
    var len = domList.length; // 缓存到局部变量
    while(len--){
        if(domList[len].type = 'checkbox'){
            checkBoxList.push(domList[len]);
        }
    }
    console.log(checkBoxList);	//  [input, input, input]
}
复制代码

当一个 Dom节点被点击时候,咱们但愿可以执行一个函数,应该怎么作?

<!-- 直接在 DOM 里绑定事件:-->
<div onlick = "test()">
复制代码
// 在js 里面经过 onclick 绑定
xxx.onclick = test
// 在事件里面添加绑定
addEventListener(xxx, 'click', test)
复制代码

Javascript 的事件流模型都有什么?

"事件冒泡":事件开始由最具体的元素接受,而后逐级向上传播

“事件捕捉”:事件由最不具体的节点先接收,而后逐级向下,一直到最具体的

“DOM 事件流”:三个阶段,事件捕捉,目标阶段。事件冒泡。

已知有字符串 foo=”get-element-by-id”,写一个 function 将其转化成驼峰表示法”——getElementById”。

var foo = "get-element-by-id";
function combo(msg){
    var arr = msg.split('-');
    for(var i=1;i < arr.length; i++){
        arr[i] = arr[i].charAt(0).toUpperCase()+arr[i].substr(1,arr[i].length-1);
    }
    msg = arr.join('');
    return msg;
}
console.log(combo(foo)); // getElementById
复制代码

输出今天的日期,以 YYYY-MM-DD 的方式

var date = new Date();
var year = date.getFullYear();
var month = date.getMonth() + 1;
month = month < 10 ? '0' + month : month;
var day = date.getDate();
day = day < 10 ? '0' + day : day;
console.log([year,month,day].join('-')) // 2018-11-01
复制代码

将字符串 ” <tr><td>{${$name}</td></tr>” 中的 {$id} 替换 成 10 , {$name} 替换成 Tony (使用正则表达式)

var str = '<tr><td>{$id}</td><td>{$name}</td></tr>';
str.replace(/{\$id}/g,10).replace(/{\$name}/,'Tony')
复制代码

为了保证页面输出安全,咱们常常须要对一些特殊的字符进行转义,请写一个函数 escapeHtml ,将 <, >, &, 进行转义

function escapeHtml(str){
    return str.replace(/[<>”&]/g,function(match){
        switch(match) {
            case "<":
            return '&lt';
            break;
            case ">":
            return '&gt'
            break;
            case "\”":
            return '&quot'
            break;	
            case "&":
            return '&amp'
            break;
        }
    });
}
复制代码

参考连接:

  1. JS类型转换(强制和自动的规则)
  2. JavaScript 数据类型转换(显式与隐式)
  3. DOM标准与IE的html元素事件模型区别
  4. ie和dom事件流的区别
  5. 前端面试题——call与apply方法的异同
  6. JavaScript中B继承A的方法Me丶微笑
  7. Javascript 中 做用域、闭包与 this 指针
  8. 理解JAVASCRIPT的闭包
  9. 深刻理解JS中的变量做用域
  10. 解决js函数闭包内存泄露问题的办法
  11. js中的事件委托或是事件代理详解
  12. JavaScript捕获和冒泡探讨
  13. JS阻止冒泡和取消默认事件(默认行为)
  14. javascript 原生方法对dom节点的操做,建立、添加、删除、替换、插入、复制、移动等操做
  15. JavaScript中本地对象、内置对象和宿主对象
  16. js中==和===区别
  17. 同源策略与JS跨域请求(图文实例详解)
  18. Js数组去重方法总结
  19. javascript 六种数据类型(一)
相关文章
相关标签/搜索