一、原型链
- 构造函数Person 通过prototype属性指向实例原型;实例原型也是一个对象,对象都有属性__proto__,实例原型通过__proto__里面的contructor指向构造函数Person。
- 构造函数Person实例person,为一个对象,通过__proto__指向实例原型
- 原型链为 实例person通过__proto__指向实例原型Person.prototype,实例原型通过__proto__指向实例原型的原型,......,最终指向根原型Object.prototype(因为Object.prototype的原型为null)
二、普通函数与箭头函数
1、区别
箭头函数是匿名函数,不能作为构造函数,不能使用new
let FunConstructor = () => {
console.log('lll');
}
let fc = new FunConstructor();
箭头函数不绑定arguments,取而代之用rest参数...解决
function A(a){
console.log(arguments);
}
A(1,2,3,4,5,8); // [1, 2, 3, 4, 5, 8, callee: ƒ, Symbol(Symbol.iterator): ƒ]
let B = (b)=>{
console.log(arguments);
}
B(2,92,32,32); // Uncaught ReferenceError: arguments is not defined
let C = (...c) => {
console.log(c);
}
C(3,82,32,11323); // [3, 82, 32, 11323]
箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值
var obj = {
a: 10,
b: () => {
console.log(this.a); // undefined
console.log(this); // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
},
c: function() {
console.log(this.a); // 10
console.log(this); // {a: 10, b: ƒ, c: ƒ}
}
}
obj.b();
obj.c();
var obj = {
a: 10,
b: function(){
console.log(this.a); //10
},
c: function() {
return ()=>{
console.log(this.a); //10
}
}
}
obj.b();
obj.c()();
箭头函数通过 call() 或 apply() 方法调用一个函数时,只传入了一个参数,对 this 并没有影响。
let obj2 = {
a: 10,
b: function(n) {
let f = (n) => n + this.a;
return f(n);
},
c: function(n) {
let f = (n) => n + this.a;
let m = {
a: 20
};
return f.call(m,n);
}
};
console.log(obj2.b(1)); // 11
console.log(obj2.c(1)); // 11
箭头函数没有原型属性
var a = ()=>{
return 1;
}
function b(){
return 2;
}
console.log(a.prototype); // undefined
console.log(b.prototype); // {constructor: ƒ}
箭头函数不能当做Generator函数,不能使用yield关键字
2、JS this指向问题
普通函数的this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象。
箭头函数比较特殊没有调用者,不存在this.箭头函数()的概念,但是它内部可以有this,而内部的this由上下文决定
例子1:
var o = {
user:"追梦子",
fn:function(){
console.log(this.user); //追梦子
}
}
o.fn();
这里的this指向的是对象o,因为你调用这个fn是通过o.fn()执行的,那自然指向就是对象o。
例子2:
var o = {
a:10,
b:{
// a:12,
fn:function(){
console.log(this.a); //undefined
}
}
}
o.b.fn();
尽管对象b中没有属性a,这个this指向的也是对象b,因为this只会指向它的上一级对象,不管这个对象中有没有this要的东西。
例子3:
var o = {
a:10,
b:{
a:12,
fn:function(){
console.log(this.a); //undefined
console.log(this); //window
}
}
}
var j = o.b.fn;
j();
这里this指向的是window,this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的。
function fn(){
this.num = 1;
}
var a = new fn();
console.log(a.num); //1
为什么this会指向a?首先new关键字会创建一个空的对象,然后会自动调用一个函数apply方法,将this指向这个空对象,这样的话函数内部的this就会被这个空的对象替代。
注: 1 .在严格版中的默认的this不再是window,而是undefined。
2. new操作符会改变函数this的指向问题。
三、JS数组相关问题
1、二维数组转为一维数组
- 利用Array.prototype.flat()实现
语法:var newArray = arr.flat(depth),参数说明:depth,可选,指定嵌套数组中的结构深度,默认值为1
特殊说明:flat()方法会移除数组中的空项。但undefined、null仍会保留。
var arr = [1, 2, undefined , 4, 5, null];
arr.flat(); // [1, 2, undefined , 4, 5, null]
- 利用apply实现
var arr1 = [[0, 1], [2, 3], [4, 5]];
function flatten(arr) { return [].concat( ...arr.map(x => Array.isArray(x) ? flatten(x) : x) ) }
var arr2 = flatten(arr1); // arr2 [0, 1, 2, 3, 4, 5]
- 利用es6
var arr1 = [[0, 1], [2, 3], [4, 5]];
function flatten(arr) { return [].concat( ...arr.map(x => Array.isArray(x) ? flatten(x) : x) ) }
var arr2 = flatten(arr1); // arr2 [0, 1, 2, 3, 4, 5]
//优点: 多维数组也可以
//比如:var arr = [[1,2],3,[[[4], 5]]]
2、数组去重
- 利用ES6 Set去重(ES6中最常用)
function unique (arr) {
return Array.from(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;
}
- 利用Object键值对去重
//将数组转换成对象
//利用对象的key值不能重复这一特性
var toObject = function(array) {
var obj = {};
for(var i=0,j=array.length;i<j;i++){
obj[array[i]] = true;
}
return obj;
}
//再将对象转换成数组
var toArray = function(obj){
var arr = [];
for(var i in obj){
arr.push(i);
}
return arr;
}
//综合前两者,完成去数组重复项方法
var uiq = function(arr){
return toArray(toObject(arr));
}
四、浅拷贝和深拷贝
浅拷贝
先拷贝,只是简单的对象引用,并没有真正的从内存中开辟一块空间。
深拷贝
将一个对象从内存中完整的拷贝一份出来,开辟一个新的区域空间来存放对象。
js中的浅拷贝
基本数据类型:名字和值都存在栈内存中
引用数据类型,名字存在栈内存中,值存在堆内存中。
实现的几种方式
Object.assign()
如果数组元素包含对象, 数组的方法concat为浅拷贝
扩展运算符
js中的深拷贝
使用JSON.parse序列化,这样子可以解决大多数情况,但不能处理函数。
使用递归,来动手写一个深拷贝。
实现的几种方式
元素全部为基础类型的数组, 数组的方法concat为深拷贝
数组的方法map、filter、slice
let a = {}, let b = JSON.pase(JSON.stringify(a));
注: JSON.stringify()转换对象过程中,undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成null(出现在数组中时)。函数、undefined 被单独转换时,会返回 undefined。
递归拷贝方法实现
// 定义一个深拷贝函数 接收目标target参数
function deepClone(target) {
// 定义一个变量
let result;
// 如果当前需要深拷贝的是一个对象的话
if (typeof target === 'object') {
// 如果是一个数组的话
if (Array.isArray(target)) {
result = []; // 将result赋值为一个数组,并且执行遍历
for (let i in target) {
// 递归克隆数组中的每一项
result.push(deepClone(target[i]))
}
// 判断如果当前的值是null的话;直接赋值为null
} else if(target===null) {
result = null;
// 判断如果当前的值是一个RegExp对象的话,直接赋值
} else if(target.constructor===RegExp){
result = target;
}else {
// 否则是普通对象,直接for in循环,递归赋值对象的所有值
result = {};
for (let i in target) {
result[i] = deepClone(target[i]);
}
}
// 如果不是对象的话,就是基本数据类型,那么直接赋值
} else {
result = target;
}
// 返回最终结果
return result;
}
五、JS数字精度丢失的问题
1、一些典型问题
- 两个简单的浮点数相加
0.1 + 0.2 != 0.3 // true
- 大整数运算
9999999999999999 == 10000000000000001 // true
9007199254740992 + 1 == 9007199254740992 // true
- toFixed不会四舍五入(Chrome)
1.335.toFixed(2) // 1.33
2、解决方案
- toFixed()
因为toFixed() 进行并转换之后是string类型的,需要在进行强制Number() 转换
Number((0.1+0.2).toFixed(2))
一些类库
math.js
- 转为整数
对于整数,前端出现问题的几率可能比较低,毕竟很少有业务需要需要用到超大整数,只要运算结果不超过 Math.pow(2, 53) 就不会丢失精度。
对于小数,前端出现问题的几率还是很多的,尤其在一些电商网站涉及到金额等数据。解决方式:把小数放到位整数(乘倍数),再缩小回原来倍数(除倍数)。
// 0.1 + 0.2
(0.1*10 + 0.2*10) / 10 == 0.3 // true
六、函数的节流与防抖
- 概念
- 节流:事件触发后,规定时间内,事件处理函数不能再次被调用。也就是说在规定的时间内,函数只能被调用一次,且是最先被触发调用的那次。
- 防抖:多次触发事件,事件处理函数只能执行一次,并且是在触发操作结束时执行。也就是说,当一个事件被触发准备执行事件函数前,会等待一定的时间,如果没有再次触发,那么就执行,如果被触发了,那就本次作废,重新从新触发的时间开始计算,并再次等待1秒,知道能最终执行。
- 使用场景
- 节流:滚动加载更多、搜索框搜索联想功能、高频点击、表单重复提交
- 防抖:搜索框搜索输入,并输入完成以后自动搜索;手机号,邮箱验证输入检测;窗口大小resize变化后,再重新渲染
- 代码实例
/*
* 函数节流
*/
function throttle(fn, wait) {
let last = 0
let dur = wait || 400
return function() {
let self = this
const current_time = new Date()
if(current_time - last > dur) {
fn.apply(self, arguments)
last =+ new Date()
}
}
}
/*
* 函数防抖
*/
function throttle(fn, wait) {
let timer
let dur = wait || 400
return function() {
clearTimeout(timer)
let self = this
let args = arguments
timer = setTimeout(function() {
fn.apply(self, args)
}, dur)
}
}
七、Github Flow
八、移动端常见问题
1.1 CSS 动画页面闪白,动画卡顿 解决方法:
- 尽可能地使用合成属性 transform 和 opacity 来设计 CSS3 动画,不使用 position 的 left 和 top 来定位
- 开启硬件加速
1.2 上下拉动滚动条时卡顿、慢
body {
-webkit-overflow-scrolling: touch;
overflow-scrolling: touch;
}
Android3+ 和 iOS5+支持 CSS3 的新属性为 overflow-scrolling
1.3 手势事件的应用
- 可使用touchstart或者touchend事件,代替click事件,用来触发移动端的点击事件
- 可使用touchmove事件代替scroll事件,来检测移动端的滑动事件。并且可以获取滚动条滚动的高度。
1.4 固定定位布局键盘挡住输入框内容
// input点击事件或获取焦点事件中
document.querySelector('#input').scrollIntoView()
1.5 如何解决phone 及 ipad 下输入框默认内阴影
element {
-webkit-appearance: none;
}
1.6 如何解决Android手机圆角失效问题
background-clip: padding-box;
1.7 在ios和Android下,如何实现触摸元素时出现半透明灰色遮罩
element {
-webkit-tap-highlight-color: rgba(255, 255, 255, 0);
}
1.8 如何解决长时间按住页面出现闪退的问题
element {
-webkit-touch-callout: none;
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。