浏览器内核:
谷歌 blink
IE trident
火狐 gecko
opera blink
safari webkit
盒模型
盒模型是由content padding margain border组成的
ie盒模型 box-sizing:border-box
标准盒模型 box-sizing:content-box
js数据类型 如何存储
常用基本类型 number string boolean null undefined
es6新增:symbol(表示独一无二的值) bigint(用来操作大的整数)
引用数据类型 对象 函数 数组
基本类型存储在栈中 按值访问 引用类型存储在栈和堆中 栈中存的是指针 指向堆中的实体的起始地址
js的数据类型的转换
转换布尔值 Boolean()
转化为数字 Number() parseInt() parseFloat()
转化为字符串 .toString() String()
判断js数据类型
typeof
console.log(typeof 2)//number
可以用来判断基本数据类型 除了null
typeof 是一个操作符,其右侧跟一个一元表达式,并返回这个表达式的数据类型。返回的结果用该类型的字符串(全小写字母)形式表示,包括以下 7 种:number、boolean、symbol、string、object、undefined、function 等。
typeof ''; // string 有效
typeof 1; // number 有效
typeof Symbol(); // symbol 有效
typeof true; //boolean 有效
typeof undefined; //undefined 有效
typeof null; //object 无效
typeof [] ; //object 无效
typeof new Function(); // function 有效
typeof new Date(); //object 无效
typeof new RegExp(); //object 无效
instanceof
console.log([] instanceof Array) true
可以精准判断引用数据类型
instanceof 只能用来判断两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪种类型
constructor
console.log((2).constructor === Number)
console.log((‘2’).constructor === String)
可以判断基本数据类型 引用数据类型 如果改了对象的原型 就不能准确判断
Object.prototype.toString.call()
Object.prototype.toString.call('') ; // [object String]
Object.prototype.toString.call(1) ; // [object Number]
Object.prototype.toString.call(true) ; // [object Boolean]
Object.prototype.toString.call(Symbol()); //[object Symbol]
Object.prototype.toString.call(undefined) ; // [object Undefined]
Object.prototype.toString.call(null) ; // [object Null]
Object.prototype.toString.call(new Function()) ; // [object Function]
Object.prototype.toString.call(new Date()) ; // [object Date]
Object.prototype.toString.call([]) ; // [object Array]
Object.prototype.toString.call(new RegExp()) ; // [object RegExp]正则对象
Object.prototype.toString.call(new Error()) ; // [object Error]
Object.prototype.toString.call(document) ; // [object HTMLDocument]
Object.prototype.toString.call(window) ; //[object global] window 是全局对象 global 的引用
console.log(Object.prototype.toString.call('2'))//[object Number]
判断基本数据类型 引用数据类型 它可以精准判断所传入参数的数据类型
js有哪些内置对象
常用的内置对象
1.String(字符串对象),
2.Number(数值对象),
3.Boolean(布尔对象),
4.Function(函数对象),
-
Array(数组对象),
-
Math(数学对象),
9.Date(日期对象),
内置对象都有自己的属性名和方法
undefined和undeclared的区别
undefined声明变量没有赋值
undeclared变量没有声明
null和undefined区别
null是一个空的对象
undefined 是声明一个变量 没有赋值
作用域 作用域链
作用域 变量作用的范围(定义变量的区域)
作用域链 要查找一个变量 先在当前执行环境找 找不到 到父级作用域找 一层一层的找 就形成作用域链
js创建对象的方式
1.new object
var person = new Object();
person.age = 66;
console.log(person)
2.字面量
let person = {};
person.age = 66;
console.log(person)
3.工厂模式 封装函数 函数里面new一个对象 并且return
const person = (age)=>{
let o = new Object();
o.age = age;
return o;
} let person2 = person(30)
console.log(person2)
4.构造函数
借助this给对象属性赋值
function Person(age){
this.age = age;
console.log(this)
}
let person2 = new Person(30)
console.log(person2)//Person {age: 30}
字面量创建对象的弊端 假如创建好多个对象 就会写很多重复代码
所以产生了 工厂模式创建对象 把创建对象的过程封装在函数体内 通过函数的调用直接生成对象
5.原型创建对象
function Person(){
} Person.prototype.age = 20
let person2 = new Person()
console.log(person2.age)//20
6.原型结合构造函数模式
function Person(name){
this.name = name;
}
Person.prototype.age = 20
let person2 = new Person('99')
console.log(person2)//20
console.log(person2.age)//20
几种创建对象的比较
继承
继承的主要思路就是利用原型链
-原型链继承
-借用构造函数继承
-组合继承 将原型链和构造函数组合起来
js 获取原型的方法
function R(){
}
let one=new R();
//构造函数 获取原型对象
console.log(R.prototype)
//实例上获取 原型对象
console.log(Object.getPrototypeOf(one))
console.log(one.proto)
console.log(one.constructor.prototype)
(作用域 作用域链 原型 原型链 继承 this promise await
应用场景)
谈谈你对this
this设计的初衷 是在函数内部使用 用来指代当前的运行环境
//严格模式和非严格模式的区别 严格模式代码规范 执行效率高
"use strict";
//全局环境下 this指向window
var name = 'liu'
function sayName(){
console.log(this)
console.log(55) }
sayName();
//函数里面的this指向 最后调用他的那个对象
function foo() {
console.log(this) }
var obj1 = {
age:666, foo: foo };
foo();//this
obj1.foo(); // obj1
//构造函数中的this指向new出来的新对象
var name = "Jake";
function testThis(name){
this.name = name;
this.sayName = function () {
console.log(this) }
}
var result1 = new testThis(33);
var result2 = new testThis(99);
result1.sayName()//指向result1
result2.sayName()//指向result2
1.全局范围内 this指向window 匿名函数的this指向window
2.函数内的this指向最后调用他的那个对象
3.构造函数内this指向new出来的新的对象
构造函数new的过程:
var a = new Foo("zhang","jake");
new Foo{
var obj = {};//新建一个对象
obj.proto = Foo.prototype;//给新对象的内部属性赋值,构造原型链(将对象的隐式原型指向构造函数的显示原型 ) var result = Foo.call(obj,"zhang","jake");//执行函数Foo,执行过程中内部this指向新创建的对象obj(这里使用call改变this 指向) return typeof result === 'obj'? result : obj;//返回新创建的对象,如果Foo内部显示返回对象类型数据,则返回该数据,执行 结束,否则返回新创建的对象obj }
4.call,apply和bind中的this
this的强绑定 改变函数执行时的this指向
var name = '333'
function foo(x,y){
console.log(this.name);
console.log(x+y);
} var obj = {
name:'999' }
foo(1,2);//window
foo.call(obj,1,2);//指向obj 参数逗号分隔 可以有多个参数
foo.apply(obj,[1,2]);//指向obj 参数放进数组 可以有多个参数
foo.bind(obj,1,2)();//指向obj 类似call 但是返回的是一个函数 不会执行 另外的两个会执行
5.箭头函数里的this始终指向函数定义时的this 而不是执行的时候
var number = 1;
var obj = {
number:2,
showNumber:function(){
this.number = 3;
(function(){
console.log(this.number);//匿名函数 })();
console.log(this.number);//普通函数 }
};
//箭头函数 this在定义位置的this 不能改变this的指向
let test2 = {
a:function(){ console.log(this) },
b:()=>{ console.log(this) }
} test2.a()//test2
test2.b()//window
6.window.setTimeout()和window.setInterval()中函数里的this默认window
obj.showNumber();//1 3
call/apply/bind的区别
call/apply改变了函数的this指向后马上执行该函数
bind则是返回改变this后的函数,不执行该函数
手写bind call apply 此处省略3000字
什么是闭包 为什么要用他
闭包是有权访问一个函数作用域的变量的一个函数(在一个函数内又定义一个函数,这个函数就是闭包)
用途:创建私有变量
变量不会被销毁/回收
function a(){
var n = 0;
function add(){
console.log(++n);
} return add
} var a1 = a();
a1(); //2 第二次调用n变量还在内存中
a1(); //2 第二次调用n变量还在内存中
什么是dom和bom
dom文档对象模型 操作html 处理网页内容
bom浏览器对象模型 api alert console window对象
bom window dom doncument 有什么区别
dom是为了操作文档出现的api document是其中的一个对象
bom是为了操作浏览器出现的api window是其中的一个对象
三种事件模型
事件 是用户操作网页时发生的交互动作或本身的一些操作,现代浏览器一共有三种事件模型
element.addEventListener(type, function, useCapture)
//【事件类型】,【事件处理程序】,【可选。布尔值,指定事件是否在捕获或冒泡阶段执行。true:事件句柄在捕获阶段执行;false:默认。事件句柄在冒泡阶段执行】
dom0级事件模型(原始事件模型) demo2事件模型 ie事件模型
原始事件模型:没有事件流 只能绑定一个事件 逻辑和显示没有分离 不推荐使用
实现方法: (1)在html代码中直接指定属性值:
(2)在js代码中为 document.getElementsById("demo").onclick = doSomeTing()
demo2级事件模型 一次事件的完整过程包括三步 捕获 执行目标元素的监听函数 冒泡 在捕获和冒泡阶段 依次检查途径的每个节点 如果该节点注册了相应的监听函数,就执行监听函数
event.stopPropagation() 阻止事件冒泡
ie事件模型
一次事件有两个过程 事件处理阶段 和事件冒泡阶段 attachEvent绑定事件
事件 是用户操作网页时发生的交互动作或者网页本身的一些操作,现代浏览器一共有三种事件模型。
DOM0级模型:这种模型不会传播,所以没有事件流的概念,但是现在有的浏览器支持以冒泡的方式实现,它可以在网页中直接定义监听函数,也可以通过 js属性来指定监听函数。这种方式是所有浏览器都兼容的。
IE 事件模型: 在该事件模型中,一次事件共有两个过程,事件处理阶段,和事件冒泡阶段。事件处理阶段会首先执行目标元素绑定的监听事件。然后是事件冒泡阶段,冒泡指的是事件从目标元素冒泡到 document,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。这种模型通过 attachEvent 来添加监听函数,可以添加多个监听函数,会按顺序依次执行 。 DOM2 级事件模型: 在该事件模型中,一次事件共有三个过程,第一个过程是事件捕获阶段。捕获指的是事件从 document 一直向下传播到目标元素,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。后面两个阶段和 IE 事件模型的两个阶段相同。这种事件模型,事件绑定的函数是 addEventListener,其中第三个参数可以指定事件是否在捕获阶段执行。
事件委托
又叫事件代理 利用事件冒泡的机制 把子元素绑定的事件 绑定在父元素上面由父元素监听函数统一处理子元素事件 减少内存的消耗
什么是事件传播
当事件发生在dom元素 该事件并不完全发生在那个元素上
事件传播有三个阶段
捕获阶段–事件从 window 开始,然后向下到每个元素,直到到达目标元素事件或event.target。
目标阶段–事件已达到目标元素。
冒泡阶段–事件从目标元素冒泡,然后上升到每个元素,直到到达 window。
什么是事件捕获
什么是事件冒泡
dom操作 怎样添加 移除 移动 复制 创建 查找节点
(1)创建新节点
createDocumentFragment() //创建一个DOM片段
createElement() //创建一个具体的元素
createTextNode() //创建一个文本节点
复制代码
(2)添加、移除、替换、插入
appendChild(node)
removeChild(node)
replaceChild(new,old)
insertBefore(new,old)
复制代码
(3)查找
getElementById();
getElementsByName();
getElementsByTagName();
getElementsByClassName();
querySelector();
querySelectorAll();
复制代码
(4)属性操作
getAttribute(key);
setAttribute(key, value);
hasAttribute(key);
removeAttribute(key);
js数组和字符串有哪些原生的方法 列举下
图片 微信
常用正则
ajax是什么
我对 ajax 的理解是,它是一种异步通信的方法,通过直接由 js 脚本向服务器发起 http 通信,然后根据服务器返回的数据,更新网页的相应部分,而不用刷新整个页面的一种方法。//1:创建Ajax对象
var xhr = window.XMLHttpRequest?new XMLHttpRequest():new ActiveXObject('Microsoft.XMLHTTP');// 兼容IE6及以下版本 //2:配置 Ajax请求地址
xhr.open('get','index.xml',true); //3:发送请求
xhr.send(null); // 严谨写法 //4:监听请求,接受响应
xhr.onreadysatechange=function(){ if(xhr.readySate==4&&xhr.status==200 || xhr.status==304 ) console.log(xhr.responsetXML) }
promise 封装 // promise 封装实现:
function getJSON(url) { // 创建一个 promise 对象 let promise = new Promise(function(resolve, reject) { let xhr = new XMLHttpRequest();
// 新建一个 http 请求 xhr.open("GET", url, true);
// 设置状态的监听函数 xhr.onreadystatechange = function() { if (this.readyState !== 4) return;
// 当请求成功或失败时,改变 promise 的状态 if (this.status === 200) { resolve(this.response); } else { reject(new Error(this.statusText)); } };
// 设置错误监听函数 xhr.onerror = function() { reject(new Error(this.statusText)); };
// 设置响应的数据类型 xhr.responseType = "json";
// 设置请求头信息 xhr.setRequestHeader("Accept", "application/json");
// 发送 http 请求 xhr.send(null); });
return promise; } js延迟加载
js 的加载、解析和执行会阻塞页面的渲染过程,因此我们希望 js 脚本能够尽可能的延迟加载,提高页面的渲染速度。 我了解到的几种方式是:
将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。
给 js 脚本添加 defer属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样。
给 js 脚本添加 async属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async 属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行。
动态创建 DOM 标签的方式,我们可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入 js 脚本。
原型 原型链
我们先使用构造函数创建一个对象:
function Person() { }
var person = new Person();
person.name = 'Kevin';
console.log(person.name) // Kevin
在这个例子中,Person 就是一个构造函数,我们使用 new 创建了一个实例对象 person。
执行上下文
简而言之,执行上下文是评估和执行 JavaScript 代码的环境的抽象概念。每当 Javascript 代码在运行的时候,它都是在执行上下文中运行。
js代码在执行过程中的准备工作
全局执行上下文
函数执行上下文
我们当作 执行上下文 是当前代码执行的一个环境与范围
作用域作用域链
promise 解决地狱回调的问题
let p = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('a')
},3000) //异步代码
})
p.then(res=>{console.log(res)})
promise demo
var promise1 = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('foo');
}, 300);
}); promise1.then(function(value) {
console.log(value);
// foo
});
console.log(promise1);
await 可以把处理异步操作
await demo
不用await的写法 比如要顺序请求两个接口 两个接口有依赖关系
普通解决方式1
methods: {
// 获取所属地
getLocation(phoneNum) {
return axois.post('/location', {phoneNum});
},
// 根据属地获取充值面额列表
getFaceList(province, city) {
return axois.post('/location', {province, city});
},
// 采取链式的调用方法
getFaceResult() {
this.getLocation(this.phoneNum)
.then(res => {
if (res.status === 200 && res.data.success) {
let province = res.data.province;
let city = res.data.city;
this.getFaceList(province, city)
.then(res => {
if(res.status === 200 && res.data.success) {
this.faceList = res.data
}
})
}
})
.catch(err => {
console.log(err)
})
}
}
await 解决方式
methods: {
// 获取所属地
getLocation(phoneNum) { return axois.post('/location', {phoneNum}); },
// 根据属地获取充值面额列表
getFaceList(province, city) { return axois.post('/location', {province, city}); },
// 采取async await 方式调用
async getFaceResult() {
// 异常需要通过try catch补货
try {
let location = await this.getLocation(this.phoneNum);
// 程序会等待上一个请求完成才进行下一条的语句执行
if (location.data.success) {
let province = location.data.province;
let city = location.data.city;
let result = await this.getFaceList(province, city);
if (result.data.success) {
this.faceList = result.data;
}
}
} catch(err) {
console.log(err);
}
}
}
防抖
节流
跨域
深浅拷贝是针对引用数据类型 基本数据类型不存在深浅拷贝
浅拷贝 只是复制引用 没有复制真正的值(两个变量会互相影响)
demo
const originArray = [1,2,3,4,5];
const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
const cloneArray = originArray;
const cloneObj = originObj;
console.log(cloneArray); // [1,2,3,4,5]
console.log(originObj); // {a:'a',b:'b',c:Array[3],d:{dd:'dd'}}
cloneArray.push(6);
cloneObj.a = {aa:'aa'};
console.log(cloneArray); // [1,2,3,4,5,6]
console.log(originArray); // [1,2,3,4,5,6]
console.log(cloneObj); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}}
console.log(originArray); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}}
上面的代码是最简单的利用 = 赋值操作符实现了一个浅拷贝,可以很清楚的看到,随着 cloneArray 和 cloneObj 改变,originArray 和 originObj 也随着发生了变化。
深拷贝
深拷贝就是对目标的完全拷贝,不像浅拷贝那样只是复制了一层引用,就连值也都复制了。
只要进行了深拷贝,它们老死不相往来,谁也不会影响谁。
目前实现深拷贝的方法不多,主要是两种:
1.利用JSON 对象中的 parse 和 stringify
2.利用递归来实现每一层都重新创建对象并赋值
第一种实现方式 只适合简单的情况
const originArray = [1,2,3,4,5];
const cloneArray = JSON.parse(JSON.stringify(originArray));
console.log(cloneArray === originArray); // false
const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
const cloneObj = JSON.parse(JSON.stringify(originObj));
console.log(cloneObj === originObj); // false
cloneObj.a = 'aa';
cloneObj.c = [1,1,1];
cloneObj.d.dd = 'doubled';
console.log(cloneObj); // {a:'aa',b:'b',c:[1,1,1],d:{dd:'doubled'}};
console.log(originObj); // {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
第一种实现方式 不适合复杂的情况 比如属性里面有函数 就会导致属性丢失
const originObj = {
name:'axuebin',
sayHello:function(){ console.log('Hello World'); } }
console.log(originObj); // {name: "axuebin", sayHello: ƒ}
const cloneObj = JSON.parse(JSON.stringify(originObj));
console.log(cloneObj); // {name: "axuebin"}
第二种方式 递归 就是对每一层的数据都实现一次 创建对象->对象赋值 的操作
function deepClone(source){
const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象
for(let keys in source){ // 遍历目标
if(source.hasOwnProperty(keys)){
if(source[keys] && typeof source[keys] === 'object'){ // 如果值是对象,就递归一下
targetObj[keys] = source[keys].constructor === Array ? [] : {};
targetObj[keys] = deepClone(source[keys]);
}else{ // 如果不是,就直接赋值
targetObj[keys] = source[keys];
}
}
} return targetObj; }
深浅拷贝
浅拷贝和赋值不一样
function shallowCopy(src) {
var dst = {};
for (var prop in src) {
if (src.hasOwnProperty(prop)) {//判断自身的属性是否存在
dst[prop] = src[prop];
}
} return dst; }
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象
深拷贝 浅拷贝的参考
www.jianshu.com/p/35d69cf24…
Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
var obj = { a: {a: "kobe", b: 39} };
var initalObj = Object.assign({}, obj);
initalObj.a.a = "wade";
console.log(obj.a.a); // wade
注意:当object只有一层的时候,是深拷贝
let obj = {
username: 'kobe' };
let obj2 = Object.assign({},obj);
obj2.username = 'wade';
console.log(obj);//{username: "kobe"}
浅拷贝 Array.prototype.concat()
let arr = [1, 3, {
username: 'kobe' }];
let arr2=arr.concat();
arr2[2].username = 'wade';
console.log(arr);
浅拷贝 Array.prototype.slice()
let arr = [1, 3, {
username: ' kobe' }];
let arr3 = arr.slice();
arr3[2].username = 'wade'
console.log(arr);
实现深拷贝的方法
1.JSON.parse(JSON.stringify(obj))
2.递归
用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。
这种方法虽然可以实现数组或对象深拷贝,但不能处理函数
递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。
跨域
1.jsonp
2.cors 跨域资源共享 后端/服务端设置 Access-Control-Allow-Origin 就可以开启 CORS
header('Access-Control-Allow-Origin:*');//允许所有来源访问
header('Access-Control-Allow-Method:POST,GET');//允许访问的方式
3.反向代理
4.node.js代理服务器
CORS支持所有类型的HTTP请求,是跨域HTTP请求的根本解决方案
JSONP只支持GET请求,JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。
不管是Node中间件代理还是nginx反向代理,主要是通过同源策略对服务器不加限制。
日常工作中,用得比较多的跨域方案是cors和nginx反向代理
不同域名之间的访问,需要跨域才能正确请求。跨域的方法很多,通常都需要后台配置,不过 Vue-cli 创建的项目,可以直接利用 Node.js 代理服务器,通过修改vue proxyTable接口实现跨域请求
函数防抖:在事件触发n秒后再执行回调 如果n秒内又被触发,则重新计时
防抖demo
//模拟一段ajax请求
function ajax(content) {
console.log('ajax request ' + content) }
function debounce(fun, delay) {
return function (args) {
let that = this
let _args = args
clearTimeout(fun.id)
fun.id = setTimeout(function () {
fun.call(that, _args)
}, delay)
}
}
let inputb = document.getElementById('debounce')
let debounceAjax = debounce(ajax, 500)
inputb.addEventListener('keyup', function (e) {
debounceAjax(e.target.value)
})
节流:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
demo
function throttle(fun, delay) {
let last, deferTimer
return function (args) {
let that = this
let _args = arguments
let now = +new Date()
if (last && now < last + delay) {
clearTimeout(deferTimer)
deferTimer = setTimeout(function () {
last = now
fun.apply(that, _args)
}, delay)
}else {
last = now
fun.apply(that,_args)
}
}
}
let throttleAjax = throttle(ajax, 1000)
let inputc = document.getElementById('throttle')
inputc.addEventListener('keyup', function(e) {
throttleAjax(e.target.value)
})
总结 函数防抖和函数节流都是防止某一时间频繁触发,但是这两兄弟之间的原理却不一样。
函数防抖是某一段时间内只执行一次,而函数节流是间隔时间执行。
结合应用场景 debounce 防抖(可用于憓家搜索下拉 实时搜索)
search搜索,用户在不断输入值时,用防抖来节约请求资源。
window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
throttle 节流
鼠标不断点击触发,mousedown(单位时间内只触发一次)
监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
手写 call apply bind
call的用法
fn.call(obj,a,b,...)
fn.apply(obj,[a,b,...])
fn.bind(obj,a,b,...)()
this的指向
1.全局作用域下 this指向window
console.log(this) //window
2.普通函数里面的this指向最后调用他的对象
var a = 10;
var obj = {
a: 20 }
function fn() {
console.log(this.a); }
fn(); // 10
fn.call(obj); // 20
3.bind call apply改变this指向
var a = 66;
var obj = {a:99}
function fn(){
console.log(this.a) }
fn()//66
fn.call(obj)//99
4.构造函数的this指向new出来的对象
function Person(age, name) {
this.age = age;
this.name = name
console.log(this) // 此处 this 分别指向 Person 的实例对象 p1 p2
}
var p1 = new Person(18, 'zs')
console.log(p1)//Person {age: 18, name: "zs"}
var p2 = new Person(18, 'ww')
Person()//window
5,箭头函数的里面的this 不能改变 指向箭头函数定义时的this 也就是外层的this
demo 区分普通函数的this和箭头函数的this
箭头函数
window.name = 'win';
const obj1 = {
name: 'joy',
getName: () => {
console.log(this);
console.log(this.name);
}
};
obj1.getName();//win
普通函数
window.name = 'win';
const obj1 = {
name: 'joy',
getName: function() {
console.log(this);
console.log(this.name);
}
};
obj1.getName();//joy
什么是闭包 闭包应用场景(在循环中的输出问题)
var data = []
for(var i = 0; i < 3; i++) {
data[i] = function() {
console.log(i)
} }
data0//3
data1//3
data2//3
笔试题 输出1 2 3
使用let
var data = [];
for (let i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
}; }
返回一个匿名函数赋值
for (var i = 0; i < 3; i++) {
data[i] = (function (num) {
return function(){
console.log(num);
}
})(i); }
es6
async await 处理异步操作
export导出 import导入
命名导出 命名导入
var a = 1
var b = 2
export {a, b}
import {a, b} from 'a.js'
默认导出 默认导入
export default class A {}
import A from 'a.js'
动态导入 跟路由懒加载不一样
import('a.js').then(value => console.log(value))
promise 异步开发的重要组成部分
概念
所谓 Promise,就是一个容器对象,里面保存着某个未来才会结束的事件(异步事件)的结果。Promise 是一个构造函数,它有三个特点:
-
Promise 有三个状态:pending(进行中)、fulfilled(成功)和 reject(失败),并且状态不受外部影响。
-
状态一旦改变就无法修改,并且状态只能从 pending 到 fulfilled 或者是 pending 到 reject。
-
Promise 一旦创建就会立即执行,不能中途取消。
vue双向绑定的原理
Object.defineProperty()监听对象的字段 动态添加的字段不能监听
Proxy 叫做代理器,监听对象
路由模式
虚拟don
通信方式
前端常见的安全问题
跨站脚本攻击 xss 前端对输入内容进行过滤
跨站请求伪造 csrf 请求的域名进行过滤
前端报错分析
代码错误
请求资源错误
跨域
浏览器输入url全过程
输入地址。
DNS解析。DNS解析就是一个网址到IP地址的转换 找到目标服务器的ip地址
TCP连接。找到目标服务器的ip地址后 就会建立tcp链接(tcp三次握手 为什么?)
发送http请求。(请求头,请求行,请求体)
返回http响应。(相应头,相应行,相应体)
浏览器解析渲染页面。
断开连接。(tcp四次挥手 为什么?)
重绘和回流 会消耗浏览器性能
浏览器渲染过程
-
解析 HTML,生成 DOM 树(DOM)
-
解析 CSS,生成 CSSOM 树(CSSOM)
-
将 DOM 和 CSSOM 合并,生成渲染树(Render-Tree)
-
计算渲染树的布局(Layout)
-
将布局渲染到屏幕上(Paint)
重绘 损耗小 一些样式的改变不影响布局的时候
回流 损耗大 影响布局
跨域 终极
jsonp
webSocket
cors
代理
什么是cdn
http请求的方法
常用post get
不常用 options head delete
post get区别
post比get安全性高
post传输的数据量比get大
状态码:有三位数字组成,第一个数字定义了响应类型
1xx:指示信息,表示请求已接受,继续处理
2xx:成功,表示请求已成功接受,处理
3xx:重定向
4xx:客户端错误
5xx:服务端错误
HTTP和HTTPS的区别如下: (1)https协议需要到ca申请证书,一般免费证书很少,需要交费。
(2)http的信息是明文传输,https 则是具有安全性的ssl加密传输协议。
(3)http和https用的端口不一样,前者是80,后者是443。
(4)http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。 seo优化
使用语义化标签 img加上alt属性 a标签加上title属性
少用iframe
最全面试 查漏补缺 github.com/i-want-offe…
每个对象都有 proto 属性,但只有函数对象才有 prototype 属性
实现new
function Dog(name){
this.name = name
} Dog.prototype.sayName = function(){
console.log(this.name)
}
// 上面是本身Dog
function _new(fn,...args){ // ...args为ES6展开符,也可以使用arguments
//先用Object创建一个空的对象,
const obj = Object.create(fn.prototype) //fn.prototype代表 用当前对象的原型去创建
//现在obj就代表Dog了,但是参数和this指向没有修改
const rel = fn.apply(obj,args)
//正常规定,如何fn返回的是null或undefined(也就是不返回内容),我们返回的是obj,否则返回rel
return rel instanceof Object ? rel : obj
} var _newDog = _new(Dog,'这是用_new出来的小狗') _newDog.sayName()
手写new
http和tcp的区别
http是应用层协议
tcp是传输层协议
http是建立在tcp之上的
虚拟dom
用js对象来构造一个dom树插入到文档中
当状态更新的时候 对比新树和旧树的区别
把变化的部分更新到真实dom树里
vue相关面试
vue-cli3 webpack配置
1.配置别名 用一个符号代替一个路径
config.resolve.alias
.set('@', resolve('src'))
.set('_c', resolve('src/components'))
2.配置打包后的文件夹名称 (outputDir: 'cli3') 3.本地调试的时候 配置代理服务 解决跨域问题 (proxy)
vue路由定义
export default new Router({//每个页面都要有对应的路由地址
mode:'history',//路由模式
routes: [{
path: '/',
redirect: '/entrance',
},
{
path: '/entrance',//路由地址 页面上显示的
name: 'entrance',//路由名字 可以
component: () => import('@/components/publicComponents/entrance')//对应的组件
},]
})
vue跳转路由 获取路由参数
<router-link to="apple"> to apple</router-link>//这个是声明式 this.$router.push()是编程式
路由地址跳转(常用)
if(_this.$route.path === '/preQualification/preQualificationList'){//跳转路由
_this.$router.push({path: '/preQualification/preQualificationDetails',query: {id: id}});
}
_data.i_id=_this.$route.query.id;//获取路由的参数
路由名字跳转
if(_this.$route.name === '/preQualification/preQualificationList'){//跳转路由
_this.$router.push({name: '/preQualification/preQualificationDetails',params: {id: id}});
}
_data.i_id=_this.$route.params.id;//获取路由的参数
vue路由的钩子
第一种:是全局导航钩子:
常用的两种钩子
router.beforeEach((to,from,next) =>{}),作用:跳转前进行判断拦截。
router.afterEach((to,from,next) =>{}),作用:跳转后进行判断拦截。
第二种:组件内的钩子
第三种:单独路由独享组件
router.beforeEach((to, from, next) => {
const isLogin = sessionStorage.getItem('isLogin'); //获取本地存储的登陆信息
if (to.name == 'login') { //判断是否进入的login页
if (isLogin == "true") { //判断是否登陆
next({ name: 'a'}); //已登录,跳转首页(a页面)
} else {
next(); //没登录,继续进入login页
}
} else { //如果进入的非login页
if (isLogin == "true") { //同样判断是否登陆
next(); //已登录,正常进入
} else {
next({ name: 'login'}); //没登录,跳转到login页
}
}
});
js获取dom
document.getElementById("box");//返回对象
document.getElementsByClassName("box");//返回对象集合
document.getElementsByTagName("p");
document.getElementsByTagName("*");//all
//里面写的是css选择器
document.querySelector(".box");//返回第一个符合条件的 不是集合
document.querySelectorAll("*");//是集合 全部
wx.createSelectorQuery().select('#the-id')//小程序 获取元素
vue路由的组件有几种
<router-link :to=‘‘ class=‘active-class‘> //路由声明式跳转 ,active-class是标签被点击时的样式
<router-view> //渲染路由的容器
<keep-alive> //缓存组件
vue的生命周期
<body>
<div id="app">
<h3 id="h3">{{msg}}</h3>
<input type="button" value="修改msg" @click="msg='No'">
</div>
<script>
var vm = new Vue({
el:'#app',
data:{
msg:'ok',
},
methods:{
show(){
console.log('执行了show方法')
}
},
**组件创建期间的4个钩子函数**
一、第一个生命周期函数,表示实例完全被创建之前,会执行这个函数
在beforeCreate生命周期函数执行的时候,data 和 methods 中的数据还没有被初始化
beforeCreate() {
console.log(this.msg) //undefind
this.show() //is not defind
},
二、第二个生命周期函数
在 created 中,data 和 methods 都已经被初始化好了!
如果要调用 methods 中的方法,或者操作 data 中的数据,最早,只能在 created 中操作
created() {
console.log(this.msg) //ok
this.show() //执行了show方法
},
三、第三个生命周期函数,表示模板已经在内存中编译完成,但是尚未把模板渲染到页面中
在beforeMount执行的时候,页面中的元素没有被真正替换过来,知识之前写的一些模板字符串
beforeMount() {
console.log(document.getElementById('h3').innerText) //{{msg}}
},
四、第四个生命周期函数,表示内存中的模板已经真实的挂载到页面中,用户已经可以看到渲染好的页面
这个mounted是实例创建期间的最后一个生命周期函数,当执行完 mounted 就表示,实例已经被完
全创建好了,此时,如果没有其它操作的话,这个实例,就静静地在内存中不动
mounted() {
console.log(document.getElementById('h3').innerText) //ok
},
**组件运行阶段的2个钩子函数**
五、第五个生命周期函数,表示 界面还没有被更新,但是数据肯定被更新了
得出结论:当执行 beforeUpdate 的时候,页面中的显示的数据,还是旧的,此时data 数据是最
新的,页面尚未和 最新的数据保持同步
beforeUpdate() {
console.log('界面上元素的内容'+ document.getElementById('h3').innerText) //没有执行,因为数据没改变
console.log('data 中的msg数据是:' + this.msg)
},
六、第六个生命周期函数
updated事件执行的时候,页面和 data 数据已经保持同步了,都是最新的
updated() {
console.log('界面上元素的内容'+ document.getElementById('h3').innerText) //No
console.log('data 中的msg数据是:' + this.msg) //No
},
**第七个 和 第八个销毁阶段的函数在上面的图片中**
})
</script>
</body>
vue的路由模式
hash history hash模式下url会有# history没有 底层的实现原理不一样 hash是通过监听onhashChange()事件实现的 hash模式背后的原理是onhashchange事件,可以在window对象上监听这个事件: window.onhashchange = function(event){ console.log(event.oldURL, event.newURL); let hash = location.hash.slice(1); document.body.style.color = hash; } history是通体pushstate()和replacestate实现 history.pushState({color:'red'}, 'red', 'red')
vue computed和watch的区别
computed 是计算属性 watch 监听数据
vue的通信方式
父子之间通信 用props $emit() 非兄弟之间 可以用vuex
vue slot用过吗
用过 插槽 可以扩展组件的功能
vue数据双向绑定的原理
vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
谈谈你对虚拟dom的理解
项目难点
为什么要用动态路由? 参照资料 数据转换要自己写 www.baidu.com/s?ie=UTF-8&… 在后台管理系统中 常常因为不同的角色 显示不同的菜单 还有菜单的名字 位置可以改变 针对这两种情况就会用到动态路由 菜单的数据的更改和列表的角色权限控制 动态路由 就是根据后台返回的菜单数据 转换成对应的数据格式 利用router.addRoutes(arr)方法 动态改变路由
new实现的过程
面试:
新生成一个对象
链接到原型
绑定this
返回新对象(如果构造函数有自己 retrun 时,则返回该值)
笔试:
function news(func) {
var target = {};//生成新对象
target.__proto__ = func.prototype;//实例的__proto__指向原型,构造函数的prototype也指向原型(链接到原型)
var res = func.call(target);//把函数的this绑定在了新生成的对象中
if (typeof (res) == "object" || typeof (res) == "function") {
return res;//如果传入的函数(构造函数)有自己的返回值,则返回该值
}
return target;//如果如果传入的函数(构造函数)没有自己的返回值,则返回新对象
}
function Father() {
let colors = [1,2,3];
return colors;
}
var ll = news(Father)
console.log(ll)//[]1,2,3]
call apply bind手写源码
call面试:
1:把传入的第一个参数作为 call2 函数内部的一个临时对象 content
2:给 context 对象一个属性 fn , 我称呼其为实际执行函数 context.fn ;让 this 关键字(仅仅是关键字,而不是this对象)指向这个属性 ,即 context.fn = this ; 注意 : 在这里的 this 对象指向的是调用call()函数的函数对象。
3:将传入call函数的其他参数,放入临时数组args[]
4:执行函数
5:函数执行完成后再把 content.fn 删除。返回函数执行的结果。
call笔试:
Function.prototype.call2 = function(content = window) {
content.fn = this;//this指向bar content是foo
let args = [...arguments].slice(1);//删除第一个元素 获取传进来的参数 因为第一个参数是指向this的对象
let result = content.fn(...args);//执行函数
delete content.fn;//删除函数
return result;//返回执行结果
}
let foo = {
value: 1
}
function bar(name, age) {
console.log(name)
console.log(age)
console.log(this.value);
}
bar.call2(foo, 'black', '18') // black 18 1
apply笔试:
Function.prototype.apply2 = function(context = window) {
context.fn = this
let result;
// 判断是否有第二个参数 且为数组
if(arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
bind笔试
Function.prototype.bind2 = function (context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fbound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
// 当作为构造函数时,this 指向实例,self 指向绑定函数,因为下面一句 `fbound.prototype = this.prototype;`,已经修改了 fbound.prototype 为 绑定函数的 prototype,此时结果为 true,当结果为 true 的时候,this 指向实例。
// 当作为普通函数时,this 指向 window,self 指向绑定函数,此时结果为 false,当结果为 false 的时候,this 指向绑定的 context。
self.apply(this instanceof self ? this : context, args.concat(bindArgs));
}
// 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承函数的原型中的值
fbound.prototype = this.prototype;
return fbound;
}
## 手写防抖节流(属于性能优化部分)
防抖:高频触发事件触发后多少毫秒后才会去执行函数
function debounce(fn) {
// 1、创建一个标记用来存放定时器的返回值
let timeout = null;
return function() {
// 2、每次当用户点击/输入的时候,把前一个定时器清除
clearTimeout(timeout);
// 3、然后创建一个新的 setTimeout,
// 这样就能保证点击按钮后的 interval 间隔内
// 如果用户还点击了的话,就不会执行 fn 函数
timeout = setTimeout(() => {
fn.call(this, arguments);
}, 1000);
};
}
// 测试
const task = () => { console.log('run task') }
const debounceTask = debounce(task)
window.addEventListener('scroll', debounceTask)
节流:高频触发事件在一段时间内 每隔多少毫秒执行一次函数
function throttle(fn) {
// 1、通过闭包保存一个标记
let canRun = true;
return function() {
// 2、在函数开头判断标志是否为 true,不为 true 则中断函数
if(!canRun) {
return;
}
// 3、将 canRun 设置为 false,防止执行之前再被执行
canRun = false;
// 4、定时器
setTimeout( () => {
fn.call(this, arguments);
// 5、执行完事件(比如调用完接口)之后,重新将这个标志设置为 true
canRun = true;
}, 1000);
};
}
// 测试
const task = () => { console.log('run task') }
const throttleTask = throttle(task, 1000)
window.addEventListener('scroll', throttleTask)
## 数组排序
**冒泡排序**
将数组中紧挨着的两个元素进行比较(比如:第0个和第1个进行比对),如果前一个元素比后一个元素大,那么这两个元素就调换位置,大的在后面,小的在前面;然后再将下一组数据(比如:第1个和第二个进行比对)执行相同的操作。
var _arr = [2,6,0,3,9,2];
function bubbleSort(arr){
for(var i = 0; i < arr.length; i++){//第一层循环次数控制 等于数组长度
for(var j = 0; j < arr.length-i-1; j++){//第二层循环 两个相邻元素进行比较 每一轮循环找到最大值
if(arr[j] > arr[j+1]){
var temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
//代码优化 es6 解构赋值
//[arr[j],arr[j+1]] = [arr[j+1],arr[j]]
}
}
}
return arr
}
console.log(bubbleSort(_arr))
**快速排序**
首先获取数组的中间数,然后将数组中比中间数小的值,放到数组left中,比中间数大的值放到right数组中,然后对left和right数组执行相同的操作,直到数组只有一个元素时,将所有数组和中间数合并。
var _arr = [2,6,0,3,9,2];
function quickSort(arr){
if(arr.length <= 1){
return arr
}
var _index = Math.floor(arr.length/2);
var temp = arr.splice(_index,1)[0];
var left = [];
var right = [];
for(var i = 0; i < arr.length; i++){
if(temp > arr[i]){
left.push(arr[i])
}else{
right.push(arr[i])
}
}
return quickSort(left).concat(temp,quickSort(right))
}
console.log(quickSort(_arr))
## 写出找到html页面的标签种类
var arr = Array.from(document.querySelectorAll('*')).map(function(item){
return item.nodeName
})
[...new Set(arr)]
## 实现vue数据双向绑定的原理
let obj = {}
let input = document.getElementById('input')
let span = document.getElementById('span')
// 数据劫持
Object.defineProperty(obj, 'text', {
configurable: true,
enumerable: true,
get() {
console.log('获取数据了')
},
set(newVal) {
console.log('数据更新了')
input.value = newVal
span.innerHTML = newVal
}
})
// 输入监听
input.addEventListener('keyup', function(e) {
obj.text = e.target.value
})
## 数组去重
1.遍历数组法
var arr = [2, 8, 5, 0, 5, 2, 6, 7, 2] var newArr = [] for (var i = 0; i < arr.length; i++) { if (newArr.indexOf(arr[i]) === -1) { newArr.push(arr[i]) } }
2.利用ES6中的 Set 方法去重
let arr = [1,0,0,2,9,8,3,1];
function unique(arr) {
return Array.from(new Set(arr))
}
console.log(unique(arr)); // [1,0,2,9,8,3]
## 找出数组最大值
function arrayMax(arrs) {
var max = arrs[0];
for(var i = 1,ilen = arrs.length; i < ilen; i++) {
if(arrs[i] > max) {
max = arrs[i];
}
}
return max;
}
## 找出数组最小值
function arrayMin(arrs){
var min = arrs[0];
for(var i = 1, ilen = arrs.length; i < ilen; i+=1) {
if(arrs[i] < min) {
min = arrs[i];
}
}
return min;
}
// 代码测试
var rets = [2,4,5,6,7,9,10,15];
console.log(arrayMin(rets));//2
## 浅拷贝
[...arr]
## 深拷贝
简单类型
var newObj = JSON.parse(JSON.stringify(someObj));
复杂类型
function deepCopy(obj){
//判断是否是简单数据类型,
if(typeof obj == "object"){
//复杂数据类型
var result = obj.constructor == Array ? [] : {};
for(let i in obj){
result[i] = typeof obj[i] == "object" ? deepCopy(obj[i]) : obj[i];
}
}else {
//简单数据类型 直接 == 赋值
var result = obj;
}
return result;
}