原生JS基础 | 面试总结

DOM 和 BOM

  • 文档对象模型(DOM):提供访问和操作网页内容的方法和接口
  • 浏览器对象模型(BOM):提供与浏览器交互的方法和接口

arguments

arguments对象不是一个 Array 。它类似于Array,但除了length属性和索引元素之外没有任何Array属性。例如,它没有 pop 方法。

转换为一个真正的Array:

1
2
3
4
5
6
var args = Array.prototype.slice.call(arguments);
var args = [].slice.call(arguments);
var args = [...arguments]; // 扩展运算符方法
// ES2015
const args = Array.from(arguments);

关于类型

  • Object.prototype.toString.call(arguments); // “[object Arguments]”
  • typeof arguments // Object

数据类型

基础数据类型

  • string
  • Number
  • Boolean
  • undefined
  • Null

引用数据类型

  • Object(Array,Date,RegExp,Function)

null 和 undefined 的异同

  • 共同点
  • 都是表示”无”
  • 用if语句时,都会被转成false
  • 不同点
  • null 是一个表示“无”的对象,转换为数字的时候是 0
  • undefined 是一个表示“无”的原始值,转换为数字时是 NaN
1
2
3
4
Number(null) // 0
Number(undefined) // NaN
5+null // 5
5+undefined // NaN
  • Null典型用法
    • 函数参数传递为空时,使用null 如 foo(null,null)
    • 原型链的终点,如 Object.getPrototypeOf(Object.prototype)
  • undefined典型用法
    • 一个定义了的变量没有赋值
    • 一个函数该给的参数没有给
    • 一个对象的某个属性没有赋值
    • 函数没有返回值时,默认为undefined

检测类型的方法

  • typeof 适用于基础类型 而不适合引用类型

    1
    2
    typeof undefined; //undefined 有效
    typeof null; //object 无效
  • instanceof 只能用来判断两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪种类型

  • constructor

    1
    2
    let f = new F()
    f.constructor == F //true
  • toString()

    1
    2
    let type = Object.prototype.toString.call(true)
    type.slice(8,-1)

类型转换,判断下列的真假

1
2
3
4
5
6
7
8
9
var undefined;
undefined == null; // true
1 == true; // true
2 == true; // false
0 == false; // true
0 == ''; // true
NaN == NaN // false
[]==![] // true
[]==false // true

执行环境和作用域

js 的作用域有几种?

  • 局部作用域 / 函数作用域(块级作用域)
  • 全局作用域

  • 作用域链
    当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。

  • 执行环境也就是函数之外那一层

无块级作用域:使用var时,变量会自动添加到最接近的环境(函数)中。

this

this有哪些应用场景

  • 作为对象方法调用

    1
    2
    3
    4
    5
    6
    7
    var test = {
    a:0,
    b:0
    get:function(){
    return this.a; // 对象方法调用
    }
    }
  • 作为构造函数的调用

    1
    2
    3
    4
    function Point(x, y){
    this.x = x;
    this.y = y;
    }
  • 使用 call 和 apply

  • 获取全局对象

参考:Javascript 的 this 用法

Tips

  • this对象是在运行时基于函数的执行环境绑定的。普通函数调用,函数被谁调用,this就指向谁。
  • 记住应当去看当前this指向哪里。普通函数定义时,它里面的this都指向全局(如案例三)。除非去根据构造函数new 一个实例,这个实例的this指向了它本身(案例六)
  • 箭头函数和普通函数的this
  • 原型链中的this:往上找
  • getter 与 setter 中的 this
  • 作为一个内联事件处理函数:当代码被内联on-event 处理函数调用时,它的this指向监听器所在的DOM元素:

普通对象函数的this

1
2
3
4
5
6
7
8
9
10
11
12
13
var fullname ='John Doe';
var obj ={
fullname:'Colin Ihrig',
prop:{
fullname:'Aurelio De Rosa',
getFullname:function(){
return this.fullname;
}
}
};
console.log(obj.prop.getFullname()); // Aurelio De Rosa
var test = obj.prop.getFullname;
console.log(test()); // John Doe

闭包或普通函数的this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 案例一:this 指向golbal/window
function f1(params) {
console.log(this);
var abigale = 1
console.log(this.abigale);//undefined
function f2() {
console.log(this.abigale);//undefined
}
return f2()
}
// 案例二:写了this
function f1(params) {
console.log(this);
this.abigale = 1 // 赋值到全局
console.log(this.abigale);//1
function f2() {
console.log(this.abigale);//1
}
return f2()
}
// 案例三:在函数修改 但其实this指向全局,它其实是改了全局的this
this.name = 'golbal'
function test(params) {
this.name = 'local'
return function () {
console.log(this.name);
}
}
test()() //local
// 案例四:this指向对象本身
var fo = {
name :3,
foo:function () {
console.log('------------------------------------');
console.log(this.name);
console.log('------------------------------------');
}
}

构造函数this

  • 如果有new关键字,this指向new出来的那个对象
    eg: 如在浏览器下执行
    1
    2
    3
    4
    5
    6
    function F(){
    console.log(this)
    }
    F(); // window
    let f = new F() // 这一步会执行F(),且this指向F,而非window

匿名函数的this

参考:匿名函数的执行环境具有全局性《JavaScript高级程序设计》P182

1
2
3
4
5
6
7
8
9
10
11
12
var obj = {
say: function () {
console.log(this) // {say:function}
setTimeout(function () {
console.log(this) // window
});
(function () {
console.log(this) // window
})()
}
}
obj.say();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var obj = {
say: function () {
console.log(this) // {say:function}
setTimeout(function () {
console.log(this) // window
});
this.a = function () {
console.log(this); //{say:function,a:function}
}
this.a()
}
}
obj.say();
```
### 严格模式下的this
```js
function test() {
'use strict';
console.log(this);
}
test(); //undefined

箭头函数中的 this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
案例:普通函数和箭头函数
var o = {
a:1,
b:2,
f: function() {
console.log( this.a + this.b); //3
}
};
// 箭头函数没有自己的this, 它的this是继承而来; 默认指向在定义它时所处的对象(宿主对象),而不是执行时的对象, 定义它的时候,可能环境是window; 箭头函数可以方便地让我们在 setTimeout ,setInterval中方便的使用this
// * 普通函数this指向执行时调用它的对象
// * 箭头函数this总是指向定义时所在的对象,而不是运行时所在的对象
var c = {
a:1,
b:2,
f: () =>{
console.log( this.a + this.b); // NaN 定义时没有this
}
};
1
2
3
4
5
6
7
8
var obj = {
say: function () {
setTimeout(() => {
console.log(this)
});
}
}
obj.say(); // obj

此时的 this继承自obj, 指的是定义它的对象obj, 而不是 window!

示例(多层嵌套的箭头函数):

1
2
3
4
5
6
7
8
9
10
11
12
var obj = {
say: function () {
var f1 = () => {
console.log(this); // obj
setTimeout(() => {
console.log(this); // obj
})
}
f1();
}
}
obj.say()

因为f1定义时所处的函数 中的 this是指的 obj, setTimeout中的箭头函数this继承自f1, 所以不管有多层嵌套,都是 obj

匿名函数,定时器中的函数,由于没有默认的宿主对象,所以默认this指向window

参考:深入理解ES6箭头函数的this以及各类this面试题总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function Parent() {
this.a = 1;
this.b = [1, 2, this.a];
this.c = { demo: 5 };
this.show = function () {
console.log(this.a , this.b , this.c.demo );
}
}
function Child() {
this.a = 2;
this.change = function () {
this.b.push(this.a);
this.a = this.b.length;
this.c.demo = this.a++;
}
}
Child.prototype = new Parent();
var parent = new Parent();
var child1 = new Child();
var child2 = new Child();
child1.a = 11;
child2.a = 12;
parent.show();
child1.show();
child2.show();
child1.change();
child2.change();
parent.show();
child1.show();
child2.show();

创建对象(类)

采用ES5创建对象/类

可以先理清js下的类

  • 类 没用于继承的话 它就是一个普通函数
  • 类 就是 构造函数
  • 构造函数 用来生成 特定类型 的对象

方法:记住各种弊端

  • 工厂模式
1
2
3
4
5
6
7
8
9
function createObject(name, age){
let obj = new Object();
obj.name = name;
obj.age = age;
obj.sayName = function(){
console.log(this.name)
}
return obj;
}

弊端:

  • 创建后无法识别是何种类型的对象
  • 构造函数模式

    1
    2
    3
    4
    5
    6
    7
    function Person(name, age){
    this.name = name;
    this.age = age;
    this.sayName = function(){
    console.log(this.name)
    }
    }

与工厂模式不同的是:

  • 不用显示new一个Object
  • 将对象方法都赋予this
  • 不用return

弊端:

  • 方法不能共享,每个实例都会创建一个新的对象
    解决办法:可以将sayName方法写于类之外,然后this.sayName=全局的sayName
    但当有很多方法时全局写太多方法会不优雅而且混乱

  • 原型式模式

    1
    2
    3
    4
    5
    6
    7
    8
    function Person(){
    }
    Person.prototype.name = 'abigale'
    Person.prototype.age = 18
    Person.prototype.sayName = function(){
    console.log(this.name)
    }

弊端:

  • 不能传参
  • 构造函数模式 + 原型式模式 结合
    • 需要传参数的用构造函数模式
    • 需要共享的用原型式模式

new 的过程经历哪些步骤

  • 第一步: 创建一个Object对象实例。
  • 第二步: 将构造函数的执行对象赋给新生成的这个实例。(也就是改变this的指向)
  • 第三步: 执行构造函数中的代码
  • 第四步: 返回新生成的对象实例

检测是否实例的各个方法

  • instanceof
  • isPrototypeOf : Person.prototype.isPrototypeOf(person)
  • Object.getPrototypeOf() : Object.getPrototypeOf(person) == Person.prototype

检测是否有某个属性

  • isOwnPrototype : person.isOwnPrototype(‘name’)
  • in : ‘name’ in person

构造函数原型实例原型链

理解:

  • 构造函数:用来在创建对象时初始化对象。特点:构造函数名一般为大写字母开头;与new运算符一起使用来实例化对象,其中属性需要用this,否则跟普通函数无差别
  • 原型:构造函数在创建的过程中,系统自动创建出来与构造函数相关联的一个空的对象。可以由构造函数.prototype来访问到。
  • 实例
  • 继承
  • 原型链:每一个对象都有自己的原型对象,原型对象本身也是对象,原型对象也有自己的原型对象,这样就形成了一个链式结构,叫做原型链。

  • 构造函数,原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。

  • 原型和实例的关系:instanceof 和 isPrototypeOf

    1
    2
    3
    // 判断A是否是B的实例,B是否是A的原型
    A instanceof B
    B.prototype.isPrototypeOf(A)
1
var p = new Person()

Person 为构造函数
p 为实例
Person.prototype 为原型

Person.prototype.constructor == Person
person.proto == Person.prototype

  • 使用new关键字调用函数(new ClassA(…))的具体步骤:

    • 创建空对象:var obj = {};
    • 设置新对象的constructor属性为构造函数的名称,设置新对象的proto属性指向构造函数的prototype对象:obj.proto = ClassA.prototype;
    • 使用新对象调用函数,函数中的this被指向新实例对象:ClassA.call(obj);  //{}.构造函数();
    • 将初始化完毕的新对象地址,保存到等号左边的变量中

参考:完整原型链详细图解(构造函数、原型、实例化对象)

prototype和proto的关系是什么

一个类的实例 的隐式原型(proto)指向 类的原型(prototype)

原型链图解

prototype

继承

  • 原型链:采用实例的方式去继承,但会有两个弊端:

    • 原型链中如果有变量是引用类型,该值会被共享
    • 子类型无法向父类型传递参数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function Father(){
    this.property = true;
    }
    Father.prototype.getFatherValue = function(){
    return this.property;
    }
    function Son(){
    this.sonProperty = false;
    }
    //继承 Father
    Son.prototype = new Father();//Son.prototype被重写,导致Son.prototype.constructor也一同被重写
    Son.prototype.getSonVaule = function(){
    return this.sonProperty;
    }
    var instance = new Son();
    alert(instance.getFatherValue());//true
  • 借用构造函数

    • 再也不会被共享,每次都是有自己一个方法有些浪费资源
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    function Father(){
    this.colors = ["red","blue","green"];
    }
    function Son(){
    Father.call(this);//继承了Father,且向父类型传递参数
    }
    var instance1 = new Son();
    instance1.colors.push("black");
    console.log(instance1.colors);//"red,blue,green,black"
    var instance2 = new Son();
    console.log(instance2.colors);//"red,blue,green" 可见引用类型值是独立的
    ```
    * 组合继承:使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承.
    ```js
    function Father(name){
    this.name = name;
    this.colors = ["red","blue","green"];
    }
    Father.prototype.sayName = function(){
    alert(this.name);
    };
    function Son(name,age){
    Father.call(this,name);//继承实例属性,第一次调用Father()
    this.age = age;
    }
    Son.prototype = new Father();//继承父类方法,第二次调用Father()
    Son.prototype.sayAge = function(){
    alert(this.age);
    }
    var instance1 = new Son("louis",5);
    instance1.colors.push("black");
    console.log(instance1.colors);//"red,blue,green,black"
    instance1.sayName();//louis
    instance1.sayAge();//5
    var instance1 = new Son("zhai",10);
    console.log(instance1.colors);//"red,blue,green"
    instance1.sayName();//zhai
    instance1.sayAge();//10
  • 原型式继承:如果你指向让一个对象与另一个对象保持类似。(有一个对象可以作为另一个对象的基础)
    但与原型模型一样,引用类型会被共享

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // Object.create 的实现方式
    function object(o)
    {
    // var F = function(){}
    // F.prototype = o;
    // return new F()
    var F = {}
    F.__proto__ = o.prototype;
    o.call(F)
    return F
    }
    A = object(B)
  • 寄生式继承:除了以另一个对象作为基础来继承,还为新对象添加新属性或方法,和借用构造函数模式相似

    1
    2
    3
    4
    5
    6
    7
    function createAnother(original){
    var clone = object(original);//通过调用object函数创建一个新对象
    clone.sayHi = function(){//以某种方式来增强这个对象
    alert("hi");
    };
    return clone;//返回这个对象
    }

闭包

考点

  • 执行环境
  • 作用域链(变量):本地活动对象 全局变量对象
  • 闭包中this的指向
  • 闭包的危害

闭包是什么

一个函数里包含着另一个函数,内部函数可以访问函数外面的变量

闭包的作用/为什么要有闭包这种东西出现

闭包通常用来创建内部变量,使得这些变量不能被外部随意修改,同时又可以通过指定的函数接口来操作

闭包有什么副作用

内存泄露
因为内部函数引用了外部函数某个值时,这个值不管函数执行不执行都在内存中,解决办法是将该值设为null

有这样一个函数,如何让b 访问不到a

1
2
3
4
function(){
var a=undefined;
function b(){
}}

js只有函数作用域,可以将a包裹在function或者IIFE中

其他题目

1
2
3
4
5
6
7
8
9
10
11
12
function f1(){
 var n=999;
 nAdd=function(){n+=1}
 function f2(){
  alert(n);
 }
 return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function creactFun(){
var result = new Array()
for(var i=0;i<10;i++){
result[i] = function(){
return i;
}
}
}
function creactFun(){
var result = new Array()
for(var i=0;i<10;i++){
result[i] = function(num){
return num; // 注意这样是错的 这样是返回一个值,上面返回是一个函数
}(i)
}
}
function creactFun(){
var result = new Array()
for(var i=0;i<10;i++){
result[i] = function(num){
return function(){
return num;
}
}(i)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var name = "the window";
var abc = {
name: "my object",
getNameFunc: function () {
return function () {
return this.name
}
}
};
var name = "the window";
var abc = {
name: "my object",
getNameFunc: function () {
return this.name
}
};

事件流模型

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

  • “DOM 事件流”:三个阶段:事件捕捉,目标阶段,事件冒泡
  • “事件捕捉”:事件由最不具体的节点先接收,然后逐级向下,一直到最具体的
  • “事件冒泡”:事件开始由最具体的元素接受,然后逐级向上传播

    事件捕获

事件捕获是从外层元素到目标元素的过程,事件冒泡是从目标元素到外层元素的过程

1
2
3
<div id="wrapper">
<button id="event">事件处理程序</button>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var wrapper = document.getElementById('wrapper');
var event = document.getElementById('event');
// 理解事件流
wrapper.addEventListener('click', function (e) {
console.log('捕获阶段执行父元素wrapper的事件处理程序');
}, true); // true - 事件句柄在捕获阶段执行
wrapper.addEventListener('click', function (e) {
console.log('冒泡阶段执行父元素wrapper的事件处理程序');
}, false); // false- 默认。事件句柄在冒泡阶段执行
event.addEventListener('click', function (e) {
console.log('捕获阶段执行子元素event的事件处理程序');
}, true);
event.addEventListener('click', function (e) {
console.log('冒泡阶段执行子元素event的事件处理程序');
}, false);
  • 输出结果
1
2
3
4
捕获阶段执行父元素wrapper的事件处理程序
捕获阶段执行子元素event的事件处理程序
冒泡阶段执行父元素wrapper的事件处理程序
冒泡阶段执行子元素event的事件处理程序

阻止冒泡

事件冒泡可以形象地比喻为把一颗石头投入水中,泡泡会一直从水底冒出水面。也就是说,事件会从最内层的元素开始发生,一直向上传播,直到document对象。

1
2
3
<div id="propagation">
<button id="proEvent">阻止冒泡处理程序</button>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 阻止冒泡
var propagation = document.getElementById('propagation');
var proEvent = document.getElementById('proEvent');
propagation.addEventListener('click', function (e) {
console.log('捕获阶段执行父元素propagation的事件处理程序');
}, true);
propagation.addEventListener('click', function (e) {
console.log('冒泡阶段执行父元素propagation的事件处理程序');
}, false);
proEvent.addEventListener('click', function (e) {
console.log('捕获阶段执行子元素proEvent的事件处理程序');
}, true);
proEvent.addEventListener('click', function (e) {
e.stopPropagation();
console.log('冒泡阶段执行子元素proEvent的事件处理程序');
}, false);
  • 输出结果
1
2
3
捕获阶段执行父元素wrapper的事件处理程序
捕获阶段执行子元素event的事件处理程序
冒泡阶段执行父元素wrapper的事件处理程序

事件委托

事件委托就是把一个元素响应事件(click、keydown……)的函数委托到另一个元素 / 委托(代理)事件是那些被绑定到父级元素的事件,但是只有当满足一定匹配条件时才会被挪。这是靠事件的冒泡机制来实现的

事件委托有点

  • 可以大量节省内存占用,减少事件注册,比如在table上代理所有td的click事件就非常棒
  • 可以实现当新增子对象时无需再次对其绑定事件,对于动态内容部分尤为合适

事件委托缺点

  • 事件代理的应用常用应该仅限于上述需求下,如果把所有事件都用代理就可能会出现事件误判,即本不应用触发事件的被绑上了事件。
1
2
3
4
5
6
7
<ul>
<li>apple</li>
<li>banana</li>
<li>peal</li>
<li>cat</li>
<li>rabbit</li>
</ul>
1
2
3
4
5
6
7
8
9
10
11
12
13
// 事件委托
var li = document.getElementsByTagName('li');
for (var i = 0; i < li.length; i++) {
li[i].setAttribute('i', i + 1);
}
var ul = document.getElementsByTagName('ul')[0];
ul.addEventListener('click', function (e) {
var target = e.target;
if (e.target && e.target.nodeName.toUpperCase() == 'LI') {
var b = e.target.getAttribute('i');
console.log('这是第' + b + '个<li>元素');
}
})
1
2
3
4
5
6
7
var table = document.getElementsByTagName('table')[0];
table.onclick= function (e) {
var target = e.target||e.srcElement;
if(target.nodeName.toLowerCase()=='td'){
console.log('你点中一行');
}
}

target 和 currentTarget 的 区别

event.target指向引起触发事件的元素,而event.currentTarget则是事件绑定的元素,只有被点击的那个目标元素的event.target才会等于event.currentTarget。

参考:event.target 和 event.currentTarget 的区别

对象

属性类型

  • 数据属性:configurale,enumerable,writable,value
1
2
3
4
5
6
7
8
let obj = {
name:'abigale',
address:'shenzhen'
}
Object.defineProperty(obj, 'name', {
writable: false
}) // 不可修改name属性
  • 访问器属性: configurale,enumerable,get,set
1
2
3
4
5
6
7
8
9
10
11
12
13
let obj = {
name:'abigale',
address:'shenzhen'
}
Object.defineProperty(obj, 'subname', {
get: function(){
return this.name
},
set: function(value){
this.name = 'Yu ' + value
}
})

对象的深拷贝

  • Object.assign()
  • JSON.parse(JSON.stringify(data))

箭头函数

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

this

  • 普通函数this指向执行时调用它的对象
  • 箭头函数this总是指向定义时所在的对象,而不是运行时所在的对象

普通函数

个人理解:可以理解为growUp并没有挂载到我们可以看到的对象里面 而是挂载到全局对象了

1
2
3
4
5
6
7
8
9
10
function Person(age) {
var _this = this;
this.age = age;
console.log(this) // Person {age: 26}
setTimeout(function growUp() {
console.log(this); // window
_this.age++;
}, 1000);
}
var p = new Person(26);

箭头函数

个人理解:因为箭头函数没有自己的作用域,所以它在定义时就挂载了Person之上了。

1
2
3
4
5
6
7
8
9
function Person(age) {
this.age = age;
setTimeout(()=> {
console.log(this); // Person
this.age++;
}, 1000);
console.log(this.age);
}
var p = new Person(26);

返回值

只有多行才需要返回值,一行则不需要

1
let c = (a,b) => a+b

无arguments

但有

1
2
3
4
5
6
function foo(n) {
var f = (...args) => args[0]+args.length;
return f(n);
}
foo(1); // 1

异步

了解JavaScript的异步吗?

  • 回调函数
  • 事件监听
  • 发布/订阅
  • Promises对象
  • setTimeOut
  • ajax

了解 Promise 吗? 简要地描述了一下 resolve、reject 函数和 then 方法

  • Promise 是 ES6 的新特性,可以解决ES6新特性的问题。Promise构造函数接受一个函数作为参数,该函数包含两个参数:resolve 和 reject
  • resolve: 如果异步操作成功,调用resolve,状态从pending 变为 resolved
  • reject: 如果异步操作失败,调用reject,状态从pending 变为 rejected
  • then: 返回一个新的Promise对象,因此可以采用链式写法

数组

数组的方法有哪些?

  • pop()
  • push()
  • shift()
  • unshift()
  • slice()
  • splice()
  • join()

字符串

  • split() // 第一个参数:以什么分割, 第二个参数:设定返回的数组长度

数组的深拷贝

  • 用push
  • 用slice

    1
    2
    3
    4
    5
    var arr = [1,2,3,4,5]
    var arr2 = arr.slice(0)
    arr[2] = 5
    console.log(arr)
    console.log(arr2)
  • JSON.stringify和JSON.parse转换
    该方法的缺点:非标准Json格式无法拷贝以及兼容性问题

判断数组的方法

1
2
3
4
5
function isArray(o){
if(Array.isArray(o) == true || Object.prototype.toString.call(o) == '[Object Array]'){
return true;
}
}

窗口位置

获取元素以下信息,可以参考
client系列
offset系列
scroll系列

offset系列 client系列 scroll系列
offsetWidth clientWidth scrollWidth
offsetHeight clientHeight scrollHeight
offsetLeft clientLeft scrollLeft
offsetTop clientTop scrollTop
  • width: document.body.clientWidth
  • height: document.body.clientHeight

给定一个元素获取它相对于视图窗口的坐标

1
2
3
4
5
6
7
8
9
10
11
调用 box.getBoundingClientRect() 返回
{
width: 122,
height: 122,
top: 30,
left: 38,
bottom: 152,
right: 160,
x: 38,
y: 30
}

因此坐标可以从中获取

参考:JavaScript获取窗口位置和元素坐标

ES6

ES6新特性,变化

对ES6新特性看法:这些特性可以使写出的代码更加简洁。块级作用域的出现 ,使变量的命名更加安全和规范。新增的模块特性使语言本身能够实现静态化模块依赖,相比于使用requireJS等动态模块来说有更高的效率。只是从现有的规范的模块移植到ES6原生的模块还需要借助模块转换 工具(或者手动更改模块的写法)

  • let的作用域:变量只有在let的作用块有效 和const
    var:所在函数作用域 变量提升
    let:所在作用快 不会代码提升
  • 数组的解构 对象的解构:从数组或对象中提取值,对它们进行赋值
  • 对象的扩展 :proto Object.setPrototypeOf(object,prototype) Object.getPrototypeOf()
  • Set数组:成员没有重复值 Map 键值对
  • 函数默认值 rest参数
  • 箭头函数:简化了写法;this指向所指对象 ;不能使用argument;不能使用call apply bind
  • for..of 循环
  • class 对象
  • Promise 是异步编程的一种解决方案
  • Module:import export

模块管理

  • CommonJS 为同步,AMD 为异步。
  • CommonJS 是一中规范,NodeJS中所采用的规范。
  • ES6 标准为JavaScript新语法,我们不需要做引入。
  • CommonJS 和 AMD 为运行时加载,而 ES6 为编译时加载。

参考: 前端模块化

函数节流和函数反抖

函数防抖(debounce)

当调用动作过n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间

1
2
3
4
5
6
7
8
9
10
11
function _debounce(fn,wait) {
var timer = null;
return function(){
clearTimeout(timer)
timer = setTimeout(()=>{
fn()
},wait)
}
}
window.onscroll = _debounce(_log,500)

函数节流(throttle)

预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function _throttle(fn,wait,time){
var previous = null; //记录上一次运行的时间
var timer = null;
return function(){
var now = new Date();
if(!previous) previous = now;
//当上一次执行的时间与当前的时间差大于设置的执行间隔时长的话,就主动执行一次
if(now - previous > time){
clearTimeout(timer);
fn();
previous = now;// 执行函数后,马上记录当前时间
}else{
clearTimeout(timer);
timer = setTimeout(function(){
fn();
},wait);
}
}
}
function _log(){
console.log(1)
}
window.onscroll = _debounce(_log,500,2000)

参考: 函数节流与函数防抖

resize和scroll事件的性能优化

1
2
3
4
5
6
7
window.addEventListener('resize', () => {
if(this.timeout)
clearTimeout(this.timeout)
this.timeout = setTimeout(() => {
console.log(11);
}, 1000);
})

正则表达式

字符 等同于 描述
. [^\n\r] 除了换行和回车之外的任意字符
\d [0-9] 数字字符
\D [^0-9] 非数字字符
\s [ \t\n\x0B\f\r] 空白字符
\S [^ \t\n\x0B\f\r] 非空白字符
\w [a-zA-Z_0-9] 单词字符(所有的字母)
\W [^a-zA-Z_0-9] 非单词字符
代码 类型 描述
? 软性量词 出现零次或一次
* 软性量词 出现零次或多次(任意次)
+ 软性量词 出现一次或多次(至道一次)
{n} 硬性量词 对应零次或者n次
{n,m} 软性量词 至少出现n次但不超过m次
{n,} 软性量词 至少出现n次(+的升级版)

贪婪匹配 和 非贪婪匹配

1
2
3
4
5
源字符串:aa<div>test1</div>bb<div>test2</div>cc
正则表达式一:<div>.*</div>
匹配结果一:<div>test1</div>bb<div>test2</div>
正则表达式二:<div>.*?</div>
匹配结果二:<div>test1</div>(这里指的是一次匹配结果,所以没包括<div>test2</div>)

参考:正则基础之——贪婪与非贪婪模式

全文单词首字母大写

1
2
3
4
let a = "i am elle dsa jdfkhdsjghdf"
let d = a.replace(/\b([a-z])([a-z]*)\b/ig,(str,$1,$2)=>{
return $1.toUpperCase()+$2.toLowerCase()
})

写一个 function,清除字符串前后的空格。(兼容所有浏览器)

使用自带接口 trim(),考虑兼容性:

1
2
3
4
5
6
7
8
9
if (!String.prototype.trim) {
String.prototype.trim = function() {
return this.replace(/^\s+/, "").replace(/\s+$/,"");
}
}
// test the function
var str = " \t\n test string ".trim();
alert(str == "test string"); // alerts "true"

aa_bb_cc 变为 aaBbCc

方法一:正则:注意正则表达式中加不加括号的区别

1
2
3
4
5
6
7
let str = "aa_bb_cc";
let newStr = str.replace(/_[a-z]/g, (str, index, allStr) => {
return str[1].toUpperCase();
});
console.log(newStr);

方法二:采用正则

1
2
3
4
5
6
7
8
9
10
let reg = /([a-z])([a-z]*)_?/gi;
let newStr = "aa_bb_cc".replace(reg, (str, p1, p2) => {
return p1.toUpperCase() + p2;
});
let newStr1 = newStr.replace(/^(\w)(\w+)$/, (str, p1, p2) => {
return p1.toLowerCase() + p2;
});
console.log(newStr1);

方法三:采用数组分割

1
2
3
4
5
6
7
8
9
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;
}

var numberArray = [3,6,2,4,1,5]; 实现对该数组的倒排,输出[5,1,4,2,6,3];实现对该数组的降序排列,输出[6,5,4,3,2,1]

1
2
3
4
5
6
7
8
9
10
11
12
13
// 倒排
numberArray.reverse();
// 降序
numberArray.sort(function(a, b) {
if (a > b) {
return -1;
} else if (a < b) {
return 1;
} else {
return 0;
}
});

输出今天的日期,以 YYYY-MM-DD 的方式,比如今天是 2017 年 9 月 13 日,则输出 2017-09-13

1
2
3
4
5
6
7
8
9
10
11
var d = new Date();
// 获取年,getFullYear()返回4位的数字
var year = d.getFullYear();
// 获取月,月份比较特殊,0是1月,11是12月
var month = d.getMonth() + 1;
// 变成两位
month = month < 10 ? '0' + month : month;
// 获取日
var day = d.getDate();
day = day < 10 ? '0' + day : day;
alert(year + '-' + month + '-' + day);

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

1
2
”<tr><td>{$id}</td><td>{$id}_{$name}</td></tr>
.replace(/{\$id}/g, ’10’).replace(/{\$name}/g, ‘Tony’);

写一个函数,实现{}括号里面的替换

当正则表达式里有字符串时,采用new RegExp的方法

1
2
3
4
5
6
7
8
9
10
function temp(str,obj) {
let re,newStr=str;
Object.keys(obj).forEach(it => {
re = new RegExp(`{(${it})}`,'g')
str = str.replace(re,obj[it])
});
return str;
}
let a = temp('我的名字是{name},我在{address}',{name:'abigale',address:'广东深圳'})

兼容性

  • JavaScript兼容性问题

    • 标准的事件绑定中绑定事件的方法函数为 addEventListener,而IE使用的是attachEvent
    • 事件处理中非常有用的event属性获得亦不相同,获得目标元素ie为e.srcElement 标准浏览器为e.target
    • 在ie中是不能操作tr的innerHtml的
    • ie日期函数处理与其它浏览器不大一致,比如: var year= new Date().getYear(); 在IE中会获得当前年,但是在firefox中则会获得当前年与1900的差值。
    • 获得DOM节点的方法有所差异,其获得子节点方法不一致。IE:parentElement.children;Firefox:parentNode.childNodes
    • 当html中节点缺失时,IE和Firefox对parentNode的解释不同。例如:
      <form> <table> <input/> </table> </form>IE:input.parentNode的值为空节点 Firefox:input.parentNode的值为form 解决方法:Firefox中节点没有removeNode方法,必须使用如下方法 node.parentNode.removeChild(node)
    • 关于AJAX的实现上亦有所不同;
  • 解决浏览器兼容性一些常用的库

    • normalize.css : 解决CSS兼容问题
    • excanvas.js : 解决IE6-IE8不能采用canvas问题
    • html5shiv.js : 解决IE6-IE8不能使用html5问题
    • respond.js : 解决响应式布局问题

参考:解决浏览器兼容性一些常用的库

其他

原生 js 怎么获取某个类名的所有元素

  • document.querySelectorAll()
  • document.getElementByClassName()

  • querySelector()返回文档中匹配指定 CSS 选择器的一个元素,如

    • document.querySelector(‘p’)
    • document.querySelector(‘.aa’)
    • document.querySelector(‘#aa’)