这是一套JS面经

644 阅读45分钟

JS面试宝典

简述浏览器中JS的事件循环机制?

js在浏览器中是单线程的

js会按照这样顺序执行

有程序运行程序,如果没有程序会调度微任务,如果没有微任务,才会去调度宏任务;

微任务:Promise,mutation 产生的

宏任务:setTimeout setInterval 产生的

开发中经常遇到的难题

第一类:使用一些接口类的Api,比如数据类型不对,会导致一些bug,需要我们去和后端沟通

第二类:兼容性问题,有一些方法在不同的场景下运行,效果不一样;然后我们做的时候需要做一些兼容性的处理

第三类:我们经常会用到的一些组件,比如vue组件,elementUI,vant,但是这些组件呢,不是万能的,有一些功能不是那么好用,需要我们进行二次封装。

get和post的区别

首先我们经常在Ajax请求的时候经常用到

第一点:get用来获取,post用来发送

比如我们想在某个搜索引擎上搜索相关信息,就用get方法

而我们进入了一个注册页面、订单页面,想把大量的信息发送到服务器上;用post方法

第二点: 安全性

get 安全性较低,post安全性较高

get 方式会将我们搜索的关键字和一些参数,明文呈现在地址栏中,所以我们说他的安全性较低

post方式将查询参数保存在请求体中

第三点:缓存

get方式,它会被缓存在浏览器当中

post方式,不会在我们的浏览器中缓存

第四点:大小

get方式传输大小约2kb,相对较小

post方式无上限

说说对闭包的理解和闭包的作用

闭包是什么?

闭包就是能够读取其他函数内部变量的函数,子函数访问我们父函数的变量

闭包的特性:

函数内再嵌套函数

内部函数可以引用外层的参数和变量

参数和变量不会被垃圾回收机制回收

闭包的理解

使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。在js中,函数即闭包,只有函数才会产生作用域的概念

闭包的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中 闭包的另一个用处,是封装对象的私有属性和私有方法

优点:避免全局变量的污染

缺点:包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露

// 闭包实际应用中主要用于封装变量 收敛权限
function isFirstLoad() {
    var _list = []
    return function (id) {
        if (_list.indexOf(id) >= 0) {
            return false
        } else {
            _list.push(id)
            return true
        }
    }
}
// 使用
var firstLoad = new isFirstLoad()
firstLoad(10) //true
firstLoad(10) //false
firstLoad(30) //true

说说This对象的理解

this关键字

单独使用,this 表示全局对象;全局调用函数在函数中,this 表示全局对象(严格模式下:this 是undefined)

以构造函数的形式调用时,this 是新创建的那个对象

在方法中,this 表示该方法所属的对象

在事件中,this 表示接收事件的元素

箭头函数:箭头函数的 this 看外层是否有函数(有,外层this就是内层this;无,this是全局对象)

call()、apply() 和 bind()方法可以将 this 引用到任何对象。

通常意义上 this 永远指向最后调用它的那个对象

特殊情况:new操作符会改变函数this的指向问题

首先new关键字会创建一个空的对象,将this指向这个对象,这样的话函数内部的this就会被这个空的对象替代

特殊情况:this遇到return

如果返回值是一个对象,那么 this 指向的就是那个返回的对象,如果返回值不是一个对象那么

this 还是指向函数的实例。

//返回的是对象,this指向返回的对象
        function fn() {
            this.user = '追梦子';
            return {};
        }
        var a = new fn;
		//{}.user
        console.log(a.user); //undefined 

        function fn() {
            this.user = '追梦子';
            return 1;
        }

        var a = new fn;
        console.log(a.user); //追梦子
        
        function fn() {
            this.user = '追梦子';
            return undefined;
        }
        var a = new fn;
        console.log(a.user); //追梦子

改变 this 的指向

改变 this 的指向我总结有以下几种方法:

  • 使用 ES6 的箭头函数
  • 在函数内部使用 _this = this
  • 使用 applycallbind

面向对象以及原型和原型链

说到原型,原型链我们不得不提一下,面向对象;

面向对象: 将需求抽象成一个对象,然后对其成员进行分析;本质就是创建对象(封装、继承、多态)

创建对象的第一步是创建构造函数,第二步通过构造函数创建对象实例。

原型prototype:

我们又叫prototype原型对象,每一个构造函数都有一个prototype 属性,这个对象的所有属性和方法,都会被构造函数所拥有。

我们一般把不变的方法,定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。

对象原型__proto__

对象都会有一个属性 proto 指向构造函数的 原型对象

之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 proto 原型的存在

原型链

任何对象都有原型对象,也就是prototype属性,

原型对象也是一 个对象,有__proto__属性,__proto__指向造函数的 prototype 原型对象

这样一层一层往上找,就形成了一条链,我们称此为原型链;

对象的顶级原型为null, 即Object.prototype.proto == null

作用域链

作用域链

作用域链 :由子级作用域返回父级作用域中寻找变量,就叫做作用域链

一般情况下,变量取值到 创建 这个变量 的函数的作用域中取值。如果本身没有,找父级,如果父级也没呢?再一层一层向上寻找,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是 作用域链 。

作用域中取值,这里强调的是“创建”,而不是“调用”;即所谓的"静态作用域

如何延长作用域链?

作用域链是可以延长的

延长作用域链:

执行环境的类型只有两种,全局和局部(函数)。但是有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除

具体来说就是执行这两个语句时,作用域链都会得到加强

1、try - catch 语句的 catch 块;会创建一个新的变量对象,包含的是被抛出的错误对象的声明

2、with 语句。with 语句会将指定的对象添加到作用域链中

== 和 ===,null 和 undefined的区别

==:  值相等,类型可以不同
===: 值相等且类型相同

null: 表示暂且空值,未来有值,一般用于释放引用类型数据
undefined: 表示未定义,一般是指一个变量只是生声明而没有赋值

在验证 null 时,一定要使用===,因为==无法分别 null 和 undefined

0,NAN,null,undefinded,'',falseif语句中会强制转化为 false

js中undefined的几种情况

1、变量声明且没有赋值;

2、获取对象中不存在的属性时;

3、函数需要实参,但是调用时没有传值,形参是undefined;

4、函数调用没有返回值或者return后没有数据,接收函数返回的变量是undefined。

//变量声明了的但是未初始化
var a;
console.log(a);//undefined

//变量声明提升
console.log(a);//undefined
var a = 1;

//使用 .访问对象中没有的属性
var obj = {};
console.log(obj.name);//undefined

//函数中定义了形参,但是执行函数时没有传入实参
function test(a){
	console.log(a);
}
test();//undefined

//对象中的方法中的匿名函数的函数体为空(相当于第四点)
var obj = {
	testFun:function(){}
};
console.log(obj.testFun());//undefined

//一个函数的函数体为空时,在打印它的调用时,打印出 undefined
function test(){};
console.log(test());//undefined

//函数作为返回值时,其返回值函数没有设置返回值
function test(){
	return function(){
		console.log(1);
	}
}
console.log(test()());// 1  undefined

call,apply,bind区别

由于this 永远指向最后调用它的那个对象

所以

call( this指向的对象, 参数1, 参数2, 参数3, ...) apply(this指向的对象, [参数1, 参数2, 参数3, ...]) bind(this指向的对象, 参数1, 参数2, 参数3, ...)

区别:

相同点:

改变函数调用时内部this的指向,第一个参数都是函数内部this指向的对象

不同点:

call从第二个参数开始为函数的参数,而且参数是单一传递,不能以参数数组传递;

apply从第二个参数开始为函数的参数,而且第二个参数为数组,该数组包含函数的所有参数

bind从第二个参数开始为函数的参数,而且参数是单一传递,不能以参数数组传递,并且返回一个绑定函数内部this指向的函数(即使用时不会立即调用一次返回的函数 )

bind 是创建一个新的函数,我们必须要手动去调用

改变 this 的指向其他方法

判断一个值是什么类型有哪些方法?

typeof 运算符(对象、数组、函数单纯使用 typeof 是不行的)

instanceof 运算符

console.log(foo instanceof Foo) //true

Object .prototype .toString 方法

Object.prototype.toString.call(arr); //"[object Array]"

判断是否是数组

ES5(有5种方法去判断)

  1. instanceof

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上

const arr= []
instanceof arr === Array // true
  1. 原型链(constructor)

原型链(constructor返回创建该对象的函数,也就是我们常说的构造函数)一般情况下,除了 undefinednull,其它都能使用 constructor 判断类型,返回对象相对应的构造函数

arr.constructor === Array;
  1. isPrototypeOf

使用Object的原型方法isPrototypeOf,判断两个对象的原型是否一样, isPrototypeOf() 方法用于测试一个对象是否存在于另一个对象的原型链上。

Array.prototype.isPrototypeOf(arr);
或
const arr = []
Object.prototype.isPrototypeOf(arr, Array.prototype) // true
  1. Object.getPrototypeOf()

Object.getPrototypeOf()方法返回指定对象的原型(内部[[Prototype]]属性的值)

const arr = []
Object.getPrototypeOf(arr) === Array.prototype // true

5.call / apply

借用Object原型的call或者apply方法,调用toString()是否为[object Array]

const arr = []
Object.prototype.toString.call(arr) === '[object Array]' // true

const obj = {}
Object.prototype.toString.call(obj) // "[object Object]"

ES6(有isArray方法去判断)

Array. isArray() 用于确定传递的值是否是一个Array

Array.isArray([1, 2, 3]);
// true
Array.isArray({foo: 123});
// false
Array.isArray("foobar");
// false
Array.isArray(undefined);
// false

故有:

  1. arr .constructor === Array;(返回创建该对象构造函数)[undefined,null]
  2. arr instanceof Array;(是否出现在某个实例对象的原型链)
  3. Object .getPrototypeOf(arr) === Array .prototype;(返回指定对象的原型)
  4. Object .prototype .toString .call(arr) === '[object Array]';
  5. Array .prototype. isPrototypeOf(arr);(判断两个对象的原型)
  6. Array .isArray(arr);

怎么判断两个对象相等?

使用 Object .getOwnPropertyNames 获取对象所有键名数组

判断两个对象的键名数组是否相等

遍历键名,判断键值是否都相等

ES6

Object.entries(object1).toString() === Object.entries(object2).toString();

把两个对象转译成字符串进行对比

JSON.stringify(object1) === JSON.stringify(object2);
function isObjectValueEqual(a, b) {
    // 判断两个对象是否指向同一内存,指向同一内存返回 true 
    
    if (a === b) return true 
    
    // 获取两个对象键值数组
    let aProps = Object.getOwnPropertyNames(a) ;
    let bProps = Object.getOwnPropertyNames(b) ;
    
    // 判断两个对象键值数组长度是否一致,不一致返回false 
    if (aProps.length !== bProps.length) return false 
    
    // 遍历对象的键值
    for (let prop in a) { 
        
        // 判断 a 的键值,在 b 中是否存在,不存在,返回 false
        if (b.hasOwnProperty(prop)) { 
            
            // 判断 a 的键值是否为对象,是则递归,不是对象直接判断键值是否相等,不相等返回 false 
            if (typeof a[prop] === 'object') { 
                
                if (!isObjectValueEqual(a[prop], b[prop])) return false;
                
            } else if (a[prop] !== b[prop]) {
                return false }
        } else { return false } }return true }console.log(isObjectValueEqual(obj1, obj2)) // false

判断隐式类型转换?

构造函数和类的区别

构造函数

通过new关键声明,

创建对象时使用new操作符。这里,会有一道JS经典面试题,new操作符执行了那些步骤:

(1)创建一个空对象;

(2)将构造函数的作用域赋值给新对象、this指向新对象

(3)执行构造函数中的代码(为新对象添加属性)

(4)返回新对象

特点:

  1. 类里面有一个constructor方法,即构造方法;this关键字代表实例对象。也就是说ES5的构造函数Point,对应ES6的Point的构造方法。
  2. 对于普通方法如toString()方法,不需要加上function关键字,直接把函数定义放进去即可。
  3. 方法之间不需要逗号分隔,加了会报错。
  4. 使用的时候,直接对类使用new命令,跟构造函数的用法完全一致,并且类必须用new调用,否则会报错
  5. 类的所有方法都是定义在类的prototype属性上面。

区别

与ES5不同,类的变量不会被提升,也就是说对象只能在类的定义之后才能创建。

类的调用必须要使用new,而普通的构造函数可以当作普通函数来使用。

ES6语法定义的属于class类,并不是function函数,所以ES6中的class不能被JavaScript程序预解析,也就是说不能提前调用.

ES5中的function可以提前调用,但是只是有属性没有方法

ES6中constructor构造器中专门定义属性和属性的关键词,属于在构造器中定义实例化对象的属性和属性值,小括号里面的变量就是class类的参数

ES5中直接在function里面定义实例化对象的属性和属性值

ES6中构造函数的方法是在class类里面定义的,虽然没有特别声明,但是函数方法也是定义在prototype中

ES5是在函数外通过prototype的方式来构造函数的

继承机制

ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面 Parent.apply(this)

ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this

JS创建对象的几种方式?

对象字面量的方式( var = {} )

通过构造函数方式创建( var obj = new Object() )

通过Object.create()方式创建( var obj = Object .create(Object .prototype); )

怎么遍历对象的属性?

1、遍历自身可枚举的属性 (可枚举,非继承属性) Object .keys() 方法,返回键名

/**Array 对象**/ 
var arr = ['a','b','c']; 
console.log(Object.keys(arr)); // ['0','1','2'] 
/**Object 对象**/ 
var obj = {foo:'bar',baz:42};
console.log(Object.keys(obj));// ["foo","baz"]

2、遍历自身的所有属性

Object .getOwnPropertyNames() 方法,

该方法返回一个由指定对象的所有自身属性组成的数组(包括不可枚举属性但不包括 Symbol 值作为名称的属性)

3、 for in 遍历对象的属性

数组中的 forEach 和 map 的区别?

相同点

都是循环遍历数组中的每一项 ,每次执行匿名函数都支持 3 个参数,参数分别是 item(当前每一

项), index(索引值),arr(原数组)

匿名函数中的 this 都是指向 window 只能遍历数组 都不会改变原数组

区别

map 方法

1.map 方法返回一个新的数组,数组中的元素为原始数组调用函数处理后的值

2.map 方法不会对空数组进行检测,map 方法不会改变原始数组。

forEach 方法

1.forEach 方法用来调用数组的每个元素,将元素传给回调函数

2.forEach 对于空数组是不会调用回调函数的。 无论 arr 是不是空数组,forEach 返回的都是 undefined。这个方法只是将数组中的每一项作为 callback 的参数执行一次

for in 和 for of 的区别

1、推荐在循环对象属性的时候使用 for...in,在遍历数组的时候的时候使用 for...of

2、for...in 循环出的是 key,for...of 循环出的是 value

3、注意,for...of 是 ES6 新引入的特性。修复了 ES5 引入的 for...in 的不足

4、for...of 不能循环普通的对象,需要通过和 Object .keys()搭配使用

事件模型?

Dom0级事件模型:事件不会传播,即没有事件流的概念(捕获:同类型事件从外到内依次触发,一直到目标元素。 冒泡:从内到外依次触发)

DOM2级事件模型(捕获阶段=>目标阶段=>冒泡阶段)

两种传播方式的来源:W3C 推行 DOM2 级事件

阻止事件冒泡、默认行为?

阻止事件冒泡的方法

w3c 方法是:event.stopPropagation(); 事件处理过程中,阻止冒泡事件,但 不会阻止默认行为

IE 则是使用 event .cancelBubble = true

function bubbles(e){ 
    var ev = e || window.event;
    if(ev && ev.stopPropagation) { //非 IE 浏览器 
        ev.stopPropagation(); 
    } else {
        //IE 浏览器(IE11 以下)
        ev.cancelBubble = true;
    }
console.log("最底层盒子被点击了") }

阻止默认行为:

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

///假定有链接<a href="http://caibaojian.com/" id="testA" >caibaojian.com</a>
var a = document.getElementById("testA");	
a.onclick =function(e){ 
    if(e.preventDefault){ 
        e.preventDefault(); 
    }else{
        window.event.returnValue == false; 
    }
}

谈谈事件委托的理解?

事件代理用到了两个在 JavaScript 事件中常被忽略的特性:事件冒泡以及目标元素。

事件代理,又称之为事件委托。即是把原本需要绑定的事件委托给父元素,让父元素担当事件监听的职务。事件代理的原理是 DOM 元素的事件冒泡。使用事件代理的好处是可以提高性能。避免了把事件处理器添加到多个子级元素上。当我们需要对很多元素添加事件的时候,可以通过将事件添加到它们的上级元素而将事件委托给上级元素来触发处理函数。

可以大量节省内存占用,减少事件注册,比如在 table 上代理所有 td 的 click 事件就非常棒。

可以实现当新增子对象时无需再次对其绑定。

优点

优点:

1、减少事件注册,节省内存。

2、在 table 上代理所有 td 的 click 事件。

3、在 ul 上代理所有 li 的 click 事件。

4、简化了 dom 节点更新时,相应事件的更新。

5、不用在新添加的 li 上绑定 click 事件。

6、当删除某个 li 时,不用移解绑上面的 click 事件

缺点:

1、事件委托基于冒泡,对于不冒泡的事件不支持

2、层级过多,冒泡过程中,可能会被某层阻止掉。

3、理论上委托会导致浏览器频繁调用处理函数,虽然很可能不需要处理。所以建议就近委托,

比如在 table 上代理 td,而不是在 document 上代理 td。

4、把所有事件都用代理就可能会出现事件误判。比如,在 document 中代理了所有 button 的

click 事件,另外的人在引用改 js 时,可能不知道,造成单击 button 触发了两个 click 事件

事件三要素

1、事件源、就是你点的那个 div,触发的对象

2、事件类型、表示动作,比如点击、滑过等

3、事件处理函数(事件处理程序)

绑定事件和解除事件的区别

事件绑定

dom .addEventListener (事件名,回调函数,是否冒泡);

解除绑定事件的封装

dom .removeEventListener(事件名,回调函数,是否冒泡);

JavaScript 中的作用域、预解析与变量声明提升?

作用域

1、全局作用域

2、局部作用域

3、块级作用域

预解析

JavaScript 代码的执行是由浏览器中的 JavaScript 解析器来执行的。JavaScript 解析器执行 JavaScript 代码的时候,分为两个过程:预解析过程和代码执行过程

预解析过程:

1.把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值

2.把函数的声明提升到当前作用域的最前面,只会提升声明,不会提升调用

3.先提升 var,在提升 function

变量提升与函数提升的区别

变量提升::将变量声明提至当前scope的最顶部,赋值语句留在原地!所以在赋值语句前调用会出现undefined 函数提升:函数提升:分为两种,一种是纯函数提升,一种是函数表达式提升,函数表达式实质上就是变量提升,另外函数提升的优先级比变量提升高

常见的 es5中的继承方法有哪些

l 借用构造函数继承: Parent.call(this, sex)

function Parent(sex) {
        this.sex = sex;
    }
Parent.prototype.getSex = function() {
        return this.sex;
    }

function Son(sex, name) {
        Parent.call(this, sex)
        this.name = name
    }

var son = new Son('men', '洛克王国')
    console.log(son)

l 原型链继承:Son.prototype = new Parent()

function Parent(sex) {
        this.sex = sex ? sex : 'men'
    }
Parent.prototype.getSex = function() {
        return this.sex
    }

function Son(name) {
        this.name = name
    }
    Son.prototype = new Parent()

    var son1 = new Son('奥特曼')
    console.log(son1.getSex())

l 组合继承:

function Parent(sex) {
        this.sex = sex;
    }
Parent.prototype.getSex = function() {
        return this.sex
    }

    function Son(name, sex, age) {
        Parent.call(this, sex)
        this.name = name
        this.age = age
    }

    Son.prototype = Object.create(Parent.prototype)
    Son.prototype.constructor = Son

    var son = new Son('奥特曼', 'men', 12)
    console.log(son)
    console.log(son.getSex())

ES6 的继承和 ES5 的继承的区别

ES5 的继承是通过原型或者是构造函数机制来实现

ES6 用过 class 关键字定义类,里面有构造方法,类之间通过 extends 关键字实现,子类必须在 constructor 方法中调用 super 方法

class B extends A { constructor() { super() }}

封装ajax

  let ajax = function (options) {

            // 定义默认的参数
            let defaults = {
                type: "get",
                url: "",
                data: {},
                header: { "Content-Type": "application/x-www-form-urlencoded" },
                screen: function (res) {
                    console.log(res);
                },
                error: function (err) {
                    console.log(err);
                }
            }

            // 合并对象
            Object.assign(defaults, options);


            // 1.创建ajax请求
            let xhr = new XMLHttpRequest();

            // 处理参数
            let params = "";
            for (let key in options.data) {
                params += "&" + key + "=" + options.data[key];
            }

            // 判断请求方式
            if (defaults.type == "get") {
                //有参数
                if (params != "") {

                    // 输入地址已经有?
                    if (defaults.url.includes("?")) {

                        defaults.url += params;

                    } else {

                        defaults.url += "?" + params;
                    }
                }

                //2.设置方式,和地址
                xhr.open(defaults.type, defaults.url);
                // 3.发送get请求
                xhr.send();

            } else if (defaults.type == "post") {

                console.log(defaults);

                //2.设置方式,和地址
                xhr.open(defaults.type, defaults.url);

                // 获取defaults里面的请求头信息
                let contentType = defaults.header["Content-Type"];

                // 设置请求头
                xhr.setRequestHeader("Content-type", contentType);

                // 判断请求头
                if (contentType == "application/x-www-form-urlencoded") {

                    // 3、发送post请求
                    xhr.send(defaults.data);


                } else if (contentType == "application/json") {

                    // 3、发送post请求
                    xhr.send(JSON.stringify(defaults.data));

                }

            }
            console.log(defaults);

            // 4.获取客户端和服务端响应
            xhr.onreadystatechange = function () {
                if (xhr.status == 200 && xhr.readyState == 4) {

                    defaults.screen(xhr.responseText)
                } else {
                    defaults.error(xhr);
                }
            }
        }

        ajax({
            type: "get",
            url: "./txt.json",
            data: {
                id: "1001",
                key: "key"
            }
        })

Ajax 的优点

最大的一点是页面无刷新,用户的体验非常好。

使用异步方式与服务器通信,具有更加迅速的响应能力。

基于标准化的并被广泛支持的技术,不需要下载插件或者小程序。

Ajax 的原则是“按需取数据”,可以最大程度的减少冗余请求,和响应对服务器造成的负担

ajax 的缺点

Ajax 不支持浏览器 back 按钮

安全问题 Ajax 暴露了与服务器交互的细节

对搜索引擎的支持比较弱

破坏了程序的异常机制。不容易调试

解决跨域问题?

首先了解下浏览器的同源策略 同源策略 / SOP 是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到 XSS、 CSFR 等攻击。

所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个 ip 地址,也非同源。

跨域方案

通过 jsonp跨域

document .domain + iframe 跨域

node .js 中间件代理跨域

后端在头部信息里面设置安全域名解决跨域

jsonp

原理:动态创建一个 script 标签。利用 script 标签的 src 属性不受同源策略限制。因为所有 的 src 属性和 href 属性都不受同源策略限制。可以请求第三方服务器数据内容。

步骤

去创建一个 script 标签

script 的 src 属性设置接口地址

接口参数,必须要带一个自定义函数名 要不然后台无法返回数据

通过定义函数名去接收后台返回数据

var script = document.createElement("script"); 

//script 的 src 属性设置接口地址 并带一个 callback 回调函数名称 

script.src = "HTTP://127.0.0.1:8888/index.php?callback=jsonpCallback"; 

//插入到页面 

document.head.appendChild(script); 

//通过定义函数名去接收后台返回数据 

 function jsonpFn(url,data,cbName,cbFn){
        // cbName   cb/callback
        //随机的名字
        let fnName = "ellen_"+Math.random().toString().substr(2);
        window[fnName] = cbFn;
        let path = url+"?"+o2u(data)+"&"+cbName+"="+fnName;
      
        let o = document.createElement("script");
        o.src = path;
        document.querySelector("head").appendChild(o);
    }

//注意 

jsonp 返回的数据是 json 对象可以直接使用 

//Ajax 

取得数据是 json 字符串需要转换成 json 对象才可以使用。 


CORS:跨域资源共享

我们都知道,jsonp也可以跨域,那为什么还要使用CORS

jsonp只可以使用 GET 方式提交

不好调试,在调用失败的时候不会返回任何状态码

安全性,万一假如提供jsonp的服务存在页面注入漏洞,即它返回的javascript的内容被人控制的。那么结果是什么?所有调用这个jsonp的网站都会存在漏洞。于是无法把危险控制在一个域名下…所以在使用jsonp的时候必须要保证使用的jsonp服务必须是安全可信的

CORS

跨域资源共享,他允许浏览器向跨源服务器发送XMLHttpRequest请求,从而克服啦 AJAX 只能同源使用的限制,目前所有最新浏览器都支持该功能,但是万恶的IE不能低于10

原理:服务器设置响应头之后,浏览器将会允许跨域请求

限制:浏览器需要支持 HTML5,可以支持 POST,PUT 等方法兼容 ie9 以上

CORS需要浏览器和服务器同时支持,整个 CORS通信过程,都是浏览器自动完成不需要用户参与,对于开发者来说,CORS的代码和正常的 ajax 没有什么差别,浏览器一旦发现跨域请求,就会添加一些附加的头信息,

浏览器将CORS请求分成两类:简单请求和非简单请求

简单请求

凡是同时满足以下两种情况的就是简单请求,反之则非简单请求,浏览器对这两种请求的处理不一样

请求方法是以下方三种方法之一

  • HEAD
  • GET
  • POST

HTTP的头信息不超出以下几种字段

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type

非简单请求

非简单请求则是不满足上边的两种情况之一

深浅拷贝

  • 浅拷贝:仅仅是复制了引用,彼此之间的操作会互相影响
  • 深拷贝:在堆中重新分配内存,不同的地址,相同的值,互不影响
  • 总的来说,深浅拷贝的主要区别就是:复制的是引用还是复制的是实例

浅拷贝实现

Object.assign() 方法

Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象source复制到目标对象。它将返回目标对象target。

Object.assign 会跳过那些值为 nullundefined 的源对象

let a = { b: {c:4} , d: { e: {f:1}} }
let g = Object.assign({},a)

当只有一层结构的时候,Array的slice和concat方法的方法看起来像是深拷贝,但其实都是浅拷贝。

当一层时
//Array.prototype.slice(),Array.prototype.concat()
let a = [1, 2, 3, 4];
let b = a.slice();
let c = a.concat();
console.log(a === b); // -> false
console.log(a === c); // -> false

a[0] = 5;
console.log(a); // -> [5, 2, 3, 4]
console.log(b); // -> [1, 2, 3, 4]
console.log(c); // -> [1, 2, 3, 4]

当有多层时
let a = [[1, 2], 3, 4];
let b = a.concat();
let c = a.concat();
console.log(a === b); // -> false
console.log(a === c); // -> false

a[0] = 5;
console.log(a); // -> [[0, 2], 3, 4]
console.log(b); // -> [[0, 2], 3, 4]
console.log(c); // -> [[0, 2], 3, 4]

深拷贝实现

JSON.parse()和JSON.stringify()

let copyObj = JSON.parse(JSON.stringify(obj));

说说 ajax、fetch、axios 之间的区别

l Ajax 请求

本身是针对 MVC 的编程,不符合现在前端 MVVM 的浪潮

基于原生的 XHR 开 发, XHR 本身的架构不清晰,已经有了 fetch 的替代方案

JQuery 整个项目太大,单纯使用 ajax 却要引入整个 JQuery 非常的不合理(采取个性化打包的方案又不能享受 CDN 服务)

l fetch 请求

fetcht 只对网络请求报错,对 400 , 500 都当做成功的请求,需要封装去处理

fetch 默认不会带 cookie ,需要添加配置项

fetch 不 支持 abort ,不支持超时控制,使用 setTimeout 及 Promise .reject 的实的超时控制并不能阻止请求过程继续在后台运行,造成了量的浪费

fetch 没有办法原生监测请求的进度,而 XHR 可以

l axios 请求

从浏览器中创建 XMLHttpRequest

从 node.js 发出 http 请求

支 持 Promise API

拦截请求和响应

转换请求和响应数据

取消请求

自动转换 JSON 数据

客户端支持防止 CSRF/XSRF

cookie、session、localStorage、sessionStorage的区别?

session

session数据保存在服务器中,session比cookie更安全,对于用户来说,session数据是不可见的

Session用于保存每个用户的专用信息,变量的值保存在服务器端,通过SessionID来区分不同的客户。

cookie

cookie数据保存在浏览器中,只能保存字符串类型

单个cookie保存数据大小不能超过4k, 浏览器一般会限制一个站点最多保存20个cookie数据

cookie一般用于保存服务器生成的sessionId, 以便记录用户身份状态,

cookie:可设置失效时间,没有设置的话,默认是关闭浏览器后失效

cookie:每次都会携带在HTTP头中,cookie是以文本的方式保存在客户端,每次请求时都带上它

localStorage和sessionStorage:仅在客户端(即浏览器)中保存,不参与和服务器的通信

localStorage

localStorage 和 sessionStorage都是HTML5的新特性,都是用于在浏览器存储数据的。存数据默认都是字符串的形式

localStorage是本地存储,一旦保存数据,用户没有手动删除,将会永久保存。

sessionStorage

sessionStorage是会话存储,保存数据后,如果用户关闭当前会话窗口,则数据会随之删除

New 操作符具体干了什么

创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型

属性和方法被加入到 this 引用的对象中

新创建的对象由 this 所引用,

并且最后隐式的返回 this

说说 JSON的了解?

  1. 拥有与js类似的语法

  2. 可以将JSON数据结构解析成js对象

  3. 与XML数据结构对比,提取数据更简单

  4. JSON.parse(string) :接受一个 JSON 字符串并将其转换成一个 JavaScript 对象。

    JSON.stringify 第一个对象参数  将某个值序列化为一个字符串值。
    JSON.stringify 第二个数组参数,指定需要转成字符串的属性
     JSON.stringify 第三个函数作为参数,用来更改默认的字符串化的行为
    
    JSON.stringify({ a:1, b:2 }, ['a'])
    // '{"a":1}'
    JSON.stringify方法的第二个参数指定,只转a属性。
    
    JSON.parse方法可以接受一个处理函数,用法与JSON.stringify方法类似
    
  5. JSON.stringify(obj) :接受一个 JavaScript 对象并将其转换为一个 JSON 字符串。

js 如何处理防抖和节流

什么是防抖函数

就是指触发事件后,在 n 秒内函数只能执行一次,如果触发事件后在 n 秒内又触发了事件,则会重新计算函数延执行时间。

如何解决问题

  1. 函数防抖的要点,是需要一个 setTimeout 来辅助实现,延迟运行需要执行的代码。
  2. 如果方法多次触发,则把上次记录的延迟执行代码用 clearTimeout 清掉,重新开始计时。
  3. 若计时期间事件没有被重新触发,等延迟时间计时完毕,则执行目标代码。

防抖函数使用场景

函数防抖一般用在什么情况之下呢?一般用在,连续的事件只需触发一次回调的场合。具体有:

  1. 搜索框搜索输入。只需用户最后一次输入完,再发送请求;搜索联想,用户在不断输入值时,用防抖来节约请求资源
  2. 用户名、手机号、邮箱输入验证;
  3. 浏览器窗口大小改变后,只需窗口调整完后,再执行 resize 事件中的代码,防止重复渲染。
  4. window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次

防抖函数的实现

function doit(fn,waitime){
    var timer = null;
    return function(){
        if(timer !== null){
            clearTimeout(timer);
        }
        timer = setTimeout(fn,waitime);
    }
}
    
function print(){
    console.log("print");
}
    
window.addEventListener("resize",doit(print,1000));

什么是节流函数

所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。 节流会稀释函数的执行频率。

如何解决问题

就是让一个函数无法在很短的时间间隔内连续调用,只有当上一次函数执行后过了你规定的时间间隔,才能进行下一次该函数的调用。以impress上面的例子讲,就是让缩放内容的操作在你不断改变窗口大小的时候不会执行,只有你停下来一会儿,才会开始执行。

节流函数适用场景

  • 鼠标不断点击触发,mousedown(单位时间内只触发一次)
  • 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断

节流函数的实现

var lock=true;
funtion (){
    if(!lock) return;
    lock=false;
    setTimeout(function(){lock=true;},2000);
}

介绍下 Set、Map 的区别

Set 和 Map 主要的应用场景在于 数据重组数据储存

Set 是一种叫做集合的数据结构,Map 是一种叫做字典的数据结构

Set

  • 成员唯一、无序且不重复
  • [value, value],键值与键名是一致的(或者说只有键值,没有键名)
  • 可以遍历,方法有:add、delete、has

Map

  • 本质上是键值对的集合,类似集合
  • 可以遍历,方法很多可以跟各种数据格式转换

集合 与 字典 的区别:

  • 共同点:集合、字典 可以储存不重复的值
  • 不同点:集合 是以 [value, value]的形式储存元素,字典 是以 [key, value] 的形式储存

setTimeout、Promise、async/await 的区别

  1. setTimeout的回调函数放到宏任务队列里,等到执行栈清空以后执行;

  2. promise.then里的回调函数会放到微任务队列里,等宏任务里面的同步代码执行完再执行;

  3. async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体

//执行顺序 同步=>微任务=>宏任务
async function async1() {
	console.log('async1 start');
	await async2();
	console.log('asnyc1 end');
}

async function async2() {
	console.log('async2');
}

console.log('script start');

setTimeout(() => {
	console.log('setTimeOut');
}, 0);

async1();

new Promise(function (reslove) {
	console.log('promise1');
	reslove();
}).then(function () {
	console.log('promise2');
})

console.log('script end');


1.执行console.log('script start'),输出script start;
2.执行setTimeout,是一个异步动作,放入宏任务异步队列中;
3.执行async1(),输出async1 start,继续向下执行;
4.执行async2(),输出async2,并返回了一个promise对象,await让出了线程,把返回的promise加入了微任务异步队列,await让出线程。后面的代码被堵塞,跳出async1函数。所以async1()下面的代码也要等待上面完成后继续执行;
5.执行 new Promise,输出promise1,然后将resolve放入微任务异步队列;
6.执行console.log('script end'),输出script end;
7.到此同步的代码就都执行完成了,然后去微任务异步队列里去获取任务
8.接下来执行resolve(async2返回的promise返回的),输出了async1 end。
9.然后执行resolve(new Promise的),输出了promise2。
10.最后执行setTimeout,输出了settimeout

//script start
//async1 start
//async2
//promise1
//script end
//asnyc1 end
//promise2
//setTimeOut

说说栈和堆的理解,以及它们的区别?

栈内存:存储的都是局部变量,栈内存的更新速度很快,因为局部变量的生命周期都很短

堆内存:对象,凡是 new 建立的都是在堆中,堆中存放的都是实体,实体用于封装数据,

栈和堆的区别

栈内存存储的是局部变量而堆内存存储的是实体

栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短

栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收

前端如何处理大量数据

懒加载+分页

首先针对最主要的减少瞬间渲染量,逐步由简入繁尝试:

自动触发的延时渲染(setTimeout和setInterval都可以,注意及时跳出循环即可)

切页触发加载

虚拟列表(按需加载)

实际上就是在首屏加载的时候,只加载可视区域内需要的列表项,当滚动发生时,动态通过计算获得可视区域内的列表项,并将非可视区域内存在的列表项删除

如果接着又问,如果我要对这些大量数据做计算做处理呢,同时又不能让页面崩掉、假死。该如何操作呢?

因为js是单线程运行的,在遇到一些需要处理大量数据的js时,可能会阻塞页面的加载,造成页面的假死。这时我们可以使用worker来开辟一个独立于主线程的子线程来进行哪些大量运算。这样就不会造成页面卡死。也说明 worker可以用来解决大量运算是造成页面卡死的问题。

如何实现冒泡排序

如何数组去重

Set结构去重(ES6用法)

[...new Set(array)]

遍历,将值添加到新数组,用indexOf()判断值是否存在,已存在就不添加,达到去重效果

  
    let unique= arr =>{
         let newA=[];
        arr.forEach(key => {
           if( newA.indexOf(key)==-1 ){ 
               //遍历newA是否存在,如果存在返回非-1值,跳过
             newA.push(key);
           }
        });
        return newA;
    }
// 这个方法不能分辨NaN,会出现两个NaN。是有问题的,下面那个方法好一点。

利用for嵌套for,然后splice去重(ES5中最常用)

function unique(arr){            
        for(var i=0; i<arr.length; i++){
            for(var j=i+1; j<arr.length; j++){
                if(arr[i]===arr[j]){         //第一个等同于第二个,splice方法删除第二个
                    arr.splice(j,1);
                    j--;
                }
            }
        }
return arr;
}
//NaN和{}没有去重,null直接消失了

forEach遍历,然后利用Object.keys(对象)返回这个对象可枚举属性组成的数组,这个数组就是去重后的数组

    const unique = arr => {
        var obj = {}
        arr.forEach(value => {
            obj[value] = 0;//这步新添加一个属性,并赋值,如果不赋值的话,属性会添加不上去
        })
        return Object.keys(obj);
        //`Object.keys(对象)`返回这个对象可枚举属性组成的数组,这个数组就是去重后的数组
    }    

如何转化成驼峰表示法

如何伪数组=>真实数组

  1. 使用 Arrray .from()--ES6

  2. [].slice .call( eleArr) 或则 Array.prototype.slice.call(eleArr)

let eleArr = document.querySelectorAll('li');
Array.from(eleArr).forEach(function(item){ 
    alert(item); 
});

let eleArr = document.querySelectorAll('li');
[].slice.call(eleArr).forEach(function(item){ 
    alert(item); 
});

如何js 数组去重,能用几种方法实现

利用 for 嵌套 for,然后 splice 去重

function unique(arr) {
	for (var i = 0; i < arr.length; i++) {
		for (var j = i + 1; j < arr.length; j++) {
				if (arr[i] == arr[j]) { //第一个等同于第二个,splice 方法删除第二个
					arr.splice(j, 1);
					j--;
				   }
			}
	}
	return arr;
}

var arr = [1, 1, 'true', 'true', true, true, 15, false, undefined, undefined, null]
console.log(unique(arr))

利用 ES6 Set 去重(ES6 中最常用)

function unique(arr) {
	return Array.from(new Set(arr));
     return [...new Set(arr)];
}

利用 indexOf 去重

function unique(arr) {
  if (!Array.isArray(arr)) {
 		console.log('type error!')
         return
	}
var array = [];
 for (var i = 0; i < arr.length; i++) {
     if (array.indexOf(arr[i]) === -1) {
           array.push(arr[i])
		}
	}
	return array;
}

var arr = [1, 1, 'true', 'true', true, true, 15, undefined, undefined, null]
console.log(unique(arr))

利用 sort()去重

function unique(arr) {
	if (!Array.isArray(arr)) {
		console.log('type error!')
		return;
	}
arr = arr.sort()
var arrry = [arr[0]];
for (var i = 1; i < arr.length; i++) {
	if (arr[i] !== arr[i - 1]) {
		arrry.push(arr[i]);
		}
	}
return arrry;
}

var arr = [1, 1, 'true', 'true', true, true, 15, undefined, undefined, null]
console.log(unique(arr))

利用对象的属性不能相同的特点进行去重

function unique(arr) {
	if (!Array.isArray(arr)) {
		console.log('type error!')
		return
	}
	var arrry = [];
	var obj = {};
	for (var i = 0; i < arr.length; i++) {
		if (!obj[arr[i]]) {
			arrry.push(arr[i])
			obj[arr[i]] = 1
		} else {
			obj[arr[i]]++
			}
		}
	return arrry;
}

var arr = [1, 1, 'true', 'true', true, true, 15, undefined, undefined, null]
console.log(unique(arr))

利用 includes 去重

 function unique(arr) {
 	if (!Array.isArray(arr)) {
		console.log('type error!')
		return
	}
	var array = [];
	for (var i = 0; i < arr.length; i++) {
		if (!array.includes(arr[i])) {//includes 检测数组是否有某个值
			array.push(arr[i]);
		}
	}
	return array
}

var arr = [1, 1, 'true', 'true', true, true, 15, undefined, undefined, null]
console.log(unique(arr))

如何实现数组的随机排序?

//方法一
var arr = [1,2,3,4,5,6,7,8,9,10];
function randSort1(arr){
    for(var i = 0,len = arr.length;i < len; i++ )
    { var rand = parseInt(Math.random()*len); 
     var temp = arr[rand]; 
     arr[rand] = arr[i];
     arr[i] = temp;
    }
    return arr; 
}
console.log(randSort1(arr));
}
//方法二
var arr = [1,2,3,4,5,6,7,8,9,10];
function randSort2(arr){ 
    var mixedArray = [];
    while(arr.length >0){ 
        var randomIndex = parseInt(Math.random()*arr.length);
        mixedArray.push(arr[randomIndex]);
        arr.splice(randomIndex, 1);
    } 
    return mixedArray;
} 
console.log(randSort2(arr));
//方法三
var arr = [1,2,3,4,5,6,7,8,9,10];
arr.sort(function(){
    return Math.random() - 0.5; 
})
console.log(arr);

如何实现多维数组降维

1)数组字符串化 
let arr = [[222, 333, 444], [55, 66, 77] ] 
arr=arr.toString().split(',')
console.log(arr); // ["222", "333", "444", "55", "66", "77"]2)[].concat.apply + some
//利用 arr.some 判断当数组中还有数组的话,
const flatten = (arr) => {
  while (arr.some(item => Array.isArray(item))){
    arr = [].concat.apply([], arr);
  }
  return arr;
}
const arr = [1, [2, [3, 4]]];
console.log(flatten(arr));

(3function flatten(arr){
  while(arr.some(item => Array.isArray(item))){
    arr = [].concat(...arr);
  }
  return arr;
}

const arr = [1, [2, [3, 4]]];
console.log(flatten(arr));


如何提取URL的参数

有这样一个 URL:item.taobao.com/item.htm?a=… JS 程序提取 URL 中的各个 GET 参数(参数名和参数个数不确定),将其按 key-value 形式返回到一个 json 结构中,

如{a: "1", b: "2", c: "", d: "xxx", e: undefined}(必会)

如何格式化输出今天日期

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

var d = new Date(); 

// 获取年,getFullYear()返回 4 位的数字 

var year = d .getFullYear(); 

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);}

JavaScript 动画和 CSS3 动画有什么区别?

css动画优点:

浏览器可以对动画进行优化

浏览器使用与 requestAnimationFrame 类似的机制,

requestAnimationFrame 比起 setTimeout,setInterval 设置动画的优势主要是

requestAnimationFrame 会把每一帧中的所有 DOM 操作集中起来,在一次重

绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,

这个频率为每秒 60 帧。

在隐藏或不可见的元素中 requestAnimationFrame 不会进行重绘或回流,这当然就意味着更少的的 CPU,gpu 和内存使用量

强制使用硬件加速 (通过 GPU 来提高动画性能)

代码相对简单,性能调优方向固定

对于帧速表现不好的低版本浏览器,CSS3 可以做到自然降级,而 JS 则需要撰写额外兼容代码。

缺点:

运行过程控制较弱,无法附加事件绑定回调函数。CSS 动画只能暂停,不能在动画中寻找一个特定的时间点,不能在半路反转动画,不能变换时间尺度,不能在特定的位置添 加回调函数或是绑定回放事件,无进度报告。

代码冗长。想用 CSS 实现稍微复杂一点动画,最后 CSS 代码都会变得非常笨重

css 动画和 js 动画的差异

代码复杂度,js 动画代码相对复杂一些 。

动画运行时,对动画的控制程度上,js 能够让动画,暂停,取消,终止,css 动画不能添加事件。

动画性能看,js 动画多了一个 js 解析的过程,性能不如 css 动画好

JS 动画优点:

JavaScript 动画控制能力很强, 可以在动画播放过程中对动画进行控制:开始、暂停、回放、终止、取消都是可以做到的。

动画效果比 css3 动画丰富,有些动画效果,比如曲线运动,冲击闪烁,视差滚动效果,只有 JavaScript 动画才能完成。

CSS3 有兼容性问题,而 JS 大多时候没有兼容性问题。

缺点:

JavaScript 在浏览器的主线程中运行,而主线程中还有其它需要运行的 JavaScript脚本、样式计算、布局、绘制任务等,对其干扰导致线程可能出现阻塞,从而造成丢帧的情况。

代码的复杂度高于 CSS 动画

总结:如果动画只是简单的状态切换,不需要中间过程控制,在这种情况下,css 动画是优选方案。它可以让你将动画逻辑放在样式文件里面,而不会让你的页面充斥JavaScript 库。然而如果你在设计很复杂的富客户端界面或者在开发一个有着复杂 UI状态的 APP。那么你应该使用 js 动画,这样你的动画可以保持高效,并且你的工作流也更可控。所以,在实现一些小的交互动效的时候,就多考虑考虑 CSS 动画。对于一 些复杂控制的动画,使用 JavaScript 比较可靠。

dom 节点的 Attribute 和 Property 有何区别?

1、什么是 Property

每 个 DOM 节点都是一个 object 对象,它可以像其他的 js Object 一样具有自己的

property 和 method,所以 property 的值可以是任何数据类型,大小写敏感,原则上

property 应该仅供 js 操作,不会出现在 html 中(默认属性除外:

id/src/href/className/dir/title/lang 等),和其他 js object 一样,自定义的 property

也会出现在 object 的 for…in 遍历中

2、什么是 Attribute

attribute 出现 在 dom 中,js 提供了 getAttribute/setAttribute 等方法来获取和改

变它的值,attribute 的值只能是字符串且大小写不敏感,最后作用于 html 中,可以影响

innerHTML 获取的值。可以通过访问 dom 节点的 attributes 属性来获取 改节点的所有的

attribute。(在 IE<9 中,attribute 获取和改变的实际上是 property。)

l attribute 是 dom 元素在文档中作为 html 标签拥有的属性;

l property 就是 dom 元素在 js 中作为对象拥有的属性。

l 对于 html 的标准属性来说,attribute 和 property 是同步的,是会自动更新的

l 但是对于自定义的属性来说,他们是不同步的

3、两者之间的区别是:

3.1)自定义的 Property 与 Attribute 不同步,不相等

3.2)非自定义的 DOM property 与 attributes 是有条件同步的

3.3)非自定义的属性(id/src/href/name/value 等),通过 setAttribute 修改其特性值

可以同步作用到 property 上,而通过.property 修改属性值有的(value)时候不会同步到

attribute 上,即不会反应到 html 上(除以下几种情况,非自定义属性 在二者之间是同步的)。

dom 结构操作怎样添加、移除、移动、复制、创建和查找节点?

1、创建新节点

createDocumentFragment()

//创建一个 DOM 片段

createElement()

//创建一个具体的元素

createTextnode()

//创建一个文本节点

2、添加、移除、替换、插入

appendChild()

removeChild()

replaceChild()

insertBefore() //并没有 insertAfter()

3、查找

getElementsByTagName()

//通过标签名称

getElementsByName()

//通过元素的 Name 属性的值(IE 容错能力较强,

会得到一个数组,其中包括 id 等于 name 值的)

getElementById()

//通过元素 Id,唯一性

event 对象的常见应用?(必会)

1、event.preventDefault(); // 阻止默认行为,阻止 a 链接默认的跳转行为

2、event.stopPropagation(); // 阻止冒泡

3、event .stopImmediatePropagation(); // 按钮绑定了 2 个响应函数,依次注册 a,b 两个事件,点击按钮,a 事件中加 event. stopImmediatePropagation()就能阻止 b 事件

4、event. current Target // 早期的 ie 不支持,当前绑定的事件

5、event.target

DOM 和 BOM 的区别

BOM

BOM 即浏览器对象模型。

BOM 没有相关标准

BOM 的最根本对象是 window

DOM

DOM 是 Document Object Model 的缩写,即文档对象模型。

DOM 是 W3C 的标准。

DOM 最根本对象是 document(实际上是 window. document)

通用事件绑定/ 编写一个通用的事件监听函数?

function bindEvent(elem, type, selector, fn) {
    if (fn == null) { 
        fn = selector;
        selector = null;
    }
 elem.addEventListner(type,
    function(e) {
     var target;
     if (selector) {
         target = e.target;
         if (target.matches(selector)) {
             fn.call(target, e);
         } 
     } else {fn(e); } }) }
// 使用代理 
var div1 = document.getElementById('div1'); 
bindEvent(div1, 'click', 'a', 
          function(e) { 
    console.log(this.innerHTML);
 }); 
// 不使用代理
var a = document.getElementById('a1'); bindEvent(div1, 'click', function(e) { console.log(a.innerHTML); })

link和@import有什么区别

link是一个html标签
link是用于加载一个css文件
link写在html文档中的

@import用于在css文件中加载其他的css文件
@import是写在css样式表中的

3、js数据类型

js数据类型分为原始数据类型和引用数据类型

	原始数据类型
		number(数值), string(字符串), boolean(布尔), null(空), undefined(未定义), symbol

	引用数据类型
		function(函数), array(数组), object(对象)

2、浏览器的渲染页面机制

浏览器解析HTML文档和CSS样式表构建DOM树和和CSSOM树

浏览器结合DOM树和CSSOM树构建渲染树

浏览器在渲染树内对每个渲染元素进行布局处理,计算出每一个元素的大小和位置

遍历渲染树将实际的像素显示到屏幕

1、bootstrap4响应式断点范围

	超大PC xl: x >= 1200px
	pc lg: x >= 992px
	平板 md: x >= 768px
	手机或者手机横屏 sm:  x >= 576px
	超小屏幕手机: x < 576

2、img标签的alt和title有什么区别?

alt:图片加载失败,会显示的文本
title:无论图片加载成功或者失败,当前鼠标悬浮的时候就会显示提示的文本信息

3、一个CSS文件如果过大的话,加载会很慢,占用过大带宽,如果解决?

1、将CSS文件拆分多个CSS文件
2、压缩css
3、去取冗余的样式
4、减少css选择器层级

1、函数节流和函数防抖

函数节流:限制一个函数在一定时间内只能执行一次

函数防抖:触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行的等待时间

函数防抖使用场景: 
	实时搜索 比如 oninput事件
	改变窗口大小: 比如 onresize事件
	滚动:比如 onscroll

1、同源策略 协议相同,域名相同,端口相同

不同源
	只要协议、域名、端口其中之一不同
	不同源就会产生跨域

同源策略主要作用
	限制不同源的服务器相互访问,提高浏览器访问网页的安全性

2、jsonp

jsonp是json的一种使用模式,可用于解决主流浏览器的跨域数据访问的问题

jsonp实现原理:利用script标签不受同源策略限制的特点,实现跨域请求


jsonp实现步骤:

	创建一个script

  将请求地址赋值给script的src属性

  将后台约定回调函数名称以参数的形式挂在请求地址中

  将一个全局函数作为后台约定回调函数的值

  后台响应的数据在全部函数中处理

1、CSS重绘和回流

CSS重绘
	当页面的元素需要更新属性,新的属性对元素的外观,风格的产生影响,这个更新的过程称为重绘

	比如设置元素的backgroundcolor等等,都会重绘元素

CSS回流
	当页面的元素因为尺寸,位置、布局,隐藏等改变而需要浏览器重新构建的过程,这个重构的过程称为回流

	比如设置元素的widthheightfloatposition等等,都会回流元素

2、typeof与instanceof的区别?

typeof 返回值是一个字符串,用来说明变量的数据类型
typeof 一般只能返回 number, boolean, string, function, object, undefined, symbol
typeof 不能用于判断 null, {}, []

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

3、js继承常用有哪些继承方式?

类式继承:子构造函数的原型对象继承父构造函数以及父构造函数的原型

构造函数式继承:子构造函数继承父构造函数,不能继承父构造函数的原型

组合式继承:类式继承 + 构造函数式继承

多继承:子构造函数继承多个父构造函数

1、 var 、const、let的区别

	var
			用于变量声明,
			可重新声明,
			可重新赋值,
			可同时声明多个变量,
			可只声明没有赋值,
			没有块级作用域,
			具有函数作用域,
			具有声明提前

	const
			用于常量声明,
			不可重新声明,
			不可重新赋值,
			可同时声明多个常量,
			声明必须赋值,
			具有块级作用域,
			具有函数作用域,
			没有声明提前

	let
			用于变量声明,
			不可重新声明,
			可重新赋值,
			可同时声明多个变量,
			可只声明没有赋值,
			具有块级作用域,
			具有函数作用域,
			没有声明提前

	块级作用域:比如if else语句体,for语句体,switch语句体,while语句体,do while语句体
		if () {
			此处就是块级作用域
			var a = 1; //没有块级作用域
			const b = 2; //具有块级作用域
		}

2、元素事件触发经历哪两个阶段以及如何阻止事件冒泡? 捕获触发以及冒泡触发

捕获触发:从根元素开始,到目标元素终止(目标元素就是点击的元素)

冒泡触发:从目标元素开始,到根元素终止

事件触发过程只能触发一次,要么在捕获阶段触发,要么在冒泡阶段触发


阻止事件冒泡
	IE8或者IE8以下版本的浏览器使用 事件对象.cancelBubble = true  来阻止事件冒泡

	其他浏览器使用 事件对象.stopPropagation() 来阻止事件冒泡

2、前端如何跳转页面

利用a标签的href属性跳转页面

使用location跳转页面,跳转方式有三种
	location.href
	location.assign()
	localtion.replace()

将 '35a42A晨0fb87' 变成 '8754320'

编写一个计算一个数 m 的 n 次幂通用函数


function pow(m, n) {
	//m: 底数
	//n: 幂

	//return 计算之后的结果;
}

调用例子, 36 次幂
var result = pow(3, 6);

下午 2、编写一个在指定范围 m 查找所有 质数 的通用函数, 返回一个由所有 质数 组成的数组

function findPrime(m) {

	//m: 指定范围


​	
​	//return [];

}

调用例子, 在 300 内查找所有质数
var result = findPrime(300);

笔试题,上机一边写一边讲

1、编写一个 过滤对象字段 的通用方法


function filterObject(o, arr) {
	//o: 过滤的对象
	//arr: 需要过滤的字段//return 过滤字段之后的对象;
}

//调用例子
var o1 = {
	a: 1,
	b: 10,
	c: 12,
	d: 'hello'
};

var arr = ['b', 'd'];

var result = filterObject(o1, arr); 

console.log('result ==> ', result); // {a: 1, c: 12}

下午 编写一个 查找数组元素 通用方法, 找到返回元素的下标, 找不到返回 -1


	function findElementByIndex(arr, ele) {
	
		//arr: 原数组
		//ele: 查找数组的元素


		//return;
	
	}


	//调用例子
	var result = findElementByIndex(['a', 'hello', true, 2], 'hello');
	console.log('result ==> ', result);

笔试题,上机一边写一边讲

1、向Number构造函数添加一个判断一个数是否是小数方法

Number.isDecimal = function (num) {


    // return true / false
}

//调用例子
var isYes1 = Number.isDecimal(2.8);
console.log('isYes1 ==> ', isYes1);


下午
2、向Number构造函数添加处理一个数的千分位函数

Number.thousand = function (num) {


}

var n = Number.thousand(1998120712);
console.log('n ==> ', n); // 1,998,120,712

笔试题,上机一边写一边讲

1、查找零件

	条件1、一定生成的好的零件比坏的零件多

	条件2、随机生成n个零件,如果随机数在0<= x < 0.5之间生成好的零件,随机数在0.5 <= x < 1之间生成坏的零件;
	好的零件名称具有good的标记,坏的零件名称具有bad标记,每个零件的名称不能相同。

	条件3、随机指定一个零件序号,假如是 第 1013 个零件,问该零件是好的零件,还是坏的零件

1、添加一个数组原型方法随机排序数组

笔试题,上机一边写一边讲

1、添加一个数组原型方法 排序对象数组

Array.prototype.sortByKey = function (key, isAsc) {
		
		//key: 需要排序的对象键名
		//isAsc: 是否升序排序,true: 升序排序,false: 降序排序, 默认为true



}


调用例子
let products = [
	{id: 5, name: '苹果', price: 4.5},
	{id: 12, name: '山竹', price: 3},
	{id: 8, name: '百香果', price: 12},
	{id: 52, name: '青脆李', price: 8}
];

//按照价格排序
products.sortByKey('price', true);

//按照id排序
products.sortByKey('id', false);

上机一边写一边讲

1、在100内查找有关7的数

下午 随机生成n位验证码(n >= 3), 至少含有一个大写字母,至少含有一个小写字母,至少含有一个数字 let codes = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

function generateValidCode(n, filter){
	//n: 验证码个数
	//filter: 去除的生成验证码字符
}

调用例子, 以下生成的验证码不能含有 'i', 'o', 'O', '0', '1', 'l'这些字符 generateValidCode(6, ['i', 'o', 'O', '0', '1', 'l']);

面试题,口述题

1、AMD和CMD的区别 AMD是requireJS在推广过程中对模块化定义的规范 CMD是seaJS在推广过程中对模块化定义的规范 AMD和CMD是基于浏览器使用并且是异步执行

2、HTML语义化标签 html语义化标签让页面的内容结构化,结构更清晰,便于对浏览器、搜索引擎解析 搜索引擎的爬虫也依赖于HTML标记来确定上下文和各个关键字的权重,利于SEO 阅读源代码的人对网站更容易将网站分块,便于阅读维护理解

3、link和@import的区别 link属于XHTML标签,而@import是css提供的 页面被加载时,link会同时被加载,而@import引用的css会等到页面被加载完再加载 @import只在IE5以上才能识别,而link是XHTML标签,无兼容问题 link方式的样式的权重高于@import的权重

编程题, 逻辑练习

一天晚上,车厘子去附近的水果店买苹果,奸诈的商贩采取了捆绑交易,只提供6个每袋和8个每袋的包装(包装不可拆分),6个装每袋14元,8个装每袋20元 可是车厘子现在只想购买恰好n个苹果,车厘子想购买尽量少的袋数方便携带。 如果不能购买恰好n个苹果,车厘子将不会购买。

现在车厘子欲购买28个苹果,求车厘子买了几袋6个装和几袋8个装的苹果,共花费多少钱?

注: 不购买苹果,则返回 -1

编程题, 逻辑练习

查找暗黑字符串和纯净字符串

假如查找的连续子符为abc, 现有字符串dghfbcabvabchcacbbaabcabcjd, 若连续3个字符为 a、 b、 c, 该连续3个字符为纯净字符串,若连续3个字符不是a、b、c,该连续3个字符为暗黑字符串,问字符串字符串dghfbcabvabchcacbbaabcabcjd共有几个暗黑字符串和几个纯净字符串?

面试题,口述题

1、JSON和XML的区别

体积:JSON相对于XML数据的体积小,传递速度更快些

交互:JSON与js的交互更加方便,更容易解析处理,更好的数据交互

可读性:JSON数据比XML更加具有可读性,易理解性

传输速度:JSON的速度远远要快于XML

最后一句

这是沉曦的学习心得!若有不正,还望斧正。希望掘友们不要吝啬对我的建议哦,回见。