前端技术总结二--JS篇
JS篇
1、 js函数声明的两种方式
直接声明:函数可以在声明函数的作用域内任一地方调用。函数解析阶段赋值给标识符 f .
function f (){ console.log(2); }
函数表达式:函数只能在声明之后调用。函数运行的阶段才赋值给变量 f 的。
var f = function() { console.log(1); } f();
2、 普通函数、构造函数区别
1) 构造函数也是一种普通函数,习惯上首字母大写
2) 作用也不一样(构造函数用来新建实例对象)
3) 调用方式不一样
a.普通函数的调用:直接调用 person();
b.构造函数的调用:需要使用new关键字来调用 new Person();
4) 内部用this 来构造属性和方法
5) 构造函数的执行流程
立刻在堆内存中创建一个新的对象、将新建的对象设置为函数中的this、逐个执行函数中的代码、将新建的对象作为返回值
6) 用instanceof 可以检查一个对象是否是一个类的实例,是则返回true;所有对象都是Object对象的后代,所以任何对象和Object做instanceof都会返回true
3、 继承
1) 借用构造函数继承:使用call或apply方法,将父对象的构造函数绑定在子对象上
2) 原型继承:将子对象的prototype指向父对象的一个实例
3) 组合继承:将原型链继承和借用构造函数继承组合到一起,使用原型链对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承
借用构造函数(类式继承)
借用构造函数虽然解决了刚才两种问题,但没有原型,则复用无从谈起。
原型链继承的缺点
字面量重写原型会中断关系,使用引用类型的原型,并且子类型还无法给超类型传递参数。
组合式继承
组合式继承是比较常用的一种继承方法,其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又保证每个实例都有它自己的属性。
4、 闭包
定义:声明在一个函数中的函数,叫做闭包函数。
特点:内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回了之后。
特点:
ü 让外部访问函数内部变量成为可能;
ü 局部变量会常驻在内存中;
ü 可以避免使用全局变量,防止全局变量污染;
ü 会造成内存泄漏(有一块内存空间被长期占用,而不被释放)。
ü 每次外部函数执行的时候,外部函数的引用地址不同,都会重新创建一个新的地址
5、 Js原型,原型链
* 原型对象也是普通的对象,是对象一个自带隐式的 __proto__ 属性,原型也有可能有自己的原型,如果一个原型对象的原型不为null的话,我们就称之为原型链。
* 原型链是由一些用来继承和共享属性的对象组成的(有限的)对象链。
一、对象中的原型_proto_
JavaScript中的对象是基于原型的。原型是其他对象的基础,它定义并实现了一个新创建的对象必须包含的成员列表。原型对象为所有对象实例所共享。
对象通过一个内部属性绑定到它的原型。在FireFox、Safari、和Chrome浏览器中,这个属性_proto_对开发者可见。一旦你创建一个内置对象(比如Object和Array)的实例,它们就会自动拥有一个Object实例作为原型。
因此,对象可以拥有两种成员类型:实例成员和原型成员。实例成员直接存于对象实例中,原型对象则从对象原型继承而来。
这就是为什么明明没有为对象定义 toString 方法,但是却可以在对象上调用此方法的原由。因为toString()是由对象person继承来的原型成员。
同样可以用 hasOwnProperty() 方法来判断对象是否包含特定的实例成员。要确定对象是否包含特定属性,可以用 in 操作符。
var book = { title: "High Performance JavaScript", author: "Nicholas C.Zakas" } console.log(book.hasOwnProperty('title')); //true console.log(book.hasOwnProperty('toString')); //false console.log("title" in book); //true console.log("toString" in book); //true
使用 in 操作符时,两种情况都会返回true,因为它既会搜索实例也会搜索原型。
二、函数中的原型prototype
每个函数创建后都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,这个对象包含了基于此函数创建的所有实例共享的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。(若原型对象中的值发生变化,继承了此原型对象的实例的对应值也会发生变化。不过如果该实例的某个继承来的属性被已经被重新赋值的话,便不会变化)。
//构造函数的两种写法 //1. 构造函数中的 this 绑定的是实例化后的对象,否则指向的是全局变量window var Person = function(){ this.name = 'Kuro-P'; this.age = 22; this.say = function(){ console.log('Hello'); } } var p1 = new Person(); console.log(p1.name); //Kuro-P Person(); console.log(name); //Kuro-P //2.写到原型对象上,与第一种写法作用相同,只不过是不实例化时,全局中不存在对应属性 var Person = function(){ }
Person.prototype.name = 'Kuro-P'; Person.prototype.age = 22; Person.prototype.say = function(){ console.log('Hello'); } var p2 = new Person(); console.log(p2.name); //Kuro-P Person(); console.log(name); //报错:name is not defined
6、 语法糖
语法糖(Syntactic sugar):指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。
7、 字面量
表示如何表达这个值,一般除去表达式,给变量赋值时,等号右边都可以认为是字面量。
1) 数字字面量:有整数字面量(十进制、16进制、八进制),浮点数字面量(要记住e),Infinity,NaN
2) 字符串的字面量:,必须用双引号、单引号包裹起来。字符串被限定在同种引号之间;也即,必须是成对单引号或成对双引号。转义字符,\n 回车换行 \t tab缩进
8、 eval()的作用
把字符串参数解析成JS代码并运行,并返回执行的结果。如:
eval("2+3");//执行加运算,并返回运算值。
eval("var age=10");//声明一个age变量
应该避免使用eval,不安全,非常耗性能(2次,一次解析成js语句,一次执行)。
9、 apply, call和bind区别?
三者都可以把一个函数应用到其他对象上,注意不是自身对象.apply,call是直接执行函数调用,bind是绑定,执行需要再次调用.apply和call的区别是:apply接受数组作为参数,而call是接受逗号分隔的无限多个参数列表,
10、 事件冒泡、捕获及委托
js事件冒泡:javascript的事件传播过程中,当事件在一个元素上出发之后,事件会逐级传播给先辈元素,直到document为止,有的浏览器可能到window为止,这就是事件冒泡现象。
js事件捕获:事件捕获恰好与事件冒泡相反,它从顶层祖先元素开始,直到事件触发元素。js事件捕获一般通过DOM2事件模型addEventListener来实现的:target.addEventListener(type, listener, useCapture)。第三个参数默认设置为false,表示在冒泡阶段触发事件,设置为true时表示在捕获阶段触发。
js事件委托:事件委托又可以叫事件代理,事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。jquery中的on事件就是一个利用事件委托批量绑定事件。$(selector).on(event,childSelector,data,function)
11、 浅拷贝和深拷贝
基本数据类型:赋值,赋值之后两个变量互不影响
引用数据类型:赋址,两个变量具有相同的引用,指向同一个对象,相互之间有影响
浅拷贝:
Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
Array.prototype.slice()方法返回一个新的数组对象,这一对象是一个由 begin和 end(不包括end)决定的原数组的浅拷贝。
深拷贝:
JSON.parse(JSON.stringify(object)) 、递归
常用的还有jQuery.extend() 和 lodash.cloneDeep()
12、 事件侦听器函数
addEventListener 绑定监听
removeEnentListener 移除监听
stopPropagation 阻止冒泡
preventDefault 阻止默认事件
event.target || event.srcElement 获取事件目标
13、 CommonJS、ES module区别
CommonJS规范:
CommonJS是nodejs规范。CommonJS 模块使用require()和module.exports
ESmodule规范:
使用import加载模块,返回export 或export default对象
区别:
commonJS 输出的是一个值的拷贝;ES Modules 生成一个引用,等到真的需要用到时,再到模块里面去取值,模块里面的变量,绑定其所在的模块。
commonjs为“运行时加载”。ES6 这种加载称为“编译时加载”或者静态加载
require可以再代码中引用,而import只能在模块顶部使用
14、 判断 js 类型的方式
1. typeof
可以判断出'string','number','boolean','undefined','symbol'
但判断
typeof(null) 时值为
'object'; 判断数组和对象时值均为
'object'
2. instanceof
原理是构造函数的 prototype 属性是否出现在对象的原型链中的任何位置
function A() {} let a = new A(); a instanceof A //true,因为 Object.getPrototypeOf(a) === A.prototype;
3. Object.prototype.toString.call()
常用于判断浏览器内置对象,对于所有基本的数据类型都能进行判断,即使是 null 和 undefined
4. Array.isArray()用于判断是否为数组
14、 什么时候不可以用箭头函数 https://www.jianshu.com/p/f9959ae5e959
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。*
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。*
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
1、 对象方法中,不适用箭头函数
const calculator = { array: [1, 2, 3], sum: () => { console.log(this === window); // => true return this.array.reduce((result, item) => result + item); } }; console.log(this === window); // => true // Throws "TypeError: Cannot read property 'reduce' of undefined" calculator.sum();
箭头函数没有自己的this,对象不能构成单独的作用域所以箭头函数的this指向window.
解决的办法是,使用函数表达式或者方法简写(ES6 中已经支持)来定义方法。
2、原型方法中,不适用箭头函数。this指向window对象
3、定义构造函数
4、动态上下文中的回调函数
15、函数防抖
区别
他们都是可以防止一个函数被无意义的高频率调用
区别在于:
函数防抖:是函数在特定的时间内不被再调用后执行
函数节流:是确保函数特定的时间内至多执行一次
应用场景:
1、高频触发的事件监听回调:比如onscroll, onresize, oninput, touchmove等; 2、用户名,手机号,邮箱输入验证时的输入框搜索自动补全事件,搜索框搜索输入,只需用户最后一次输入完,再发送请求; 3、频繁操作点赞和取消点赞; 4、浏览器窗口大小改变后,只需窗口调整完成后,再执行resize事件中的代码,防止重复渲染;
代码:
需注意this指向
let num = 1; let content = document.getElementById("content"); function count() { content.innerHTML = num++; }; //防抖函数(最后执行) //触发事件后函数不会立即执行,而是在 n 秒后执行 function debounceEnd(func, wait) { let timer; return function() { //如果限定时间wait内有移动,则清除上一次的定时器 if (timer) clearTimeout(timer); timer = setTimeout(() => { func.apply(this, arguments) }, wait) } } //防抖函数(开始执行) //触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果 function debounceStart(func, wait) { let timer; return function() { if (timer) clearTimeout(timer); let callNow = !timer; timer = setTimeout(() => { timer = null; }, wait) if (callNow) func.apply(this, arguments); } } // 合成版 /** * @desc 函数防抖 * @param func 目标函数 * @param wait 延迟执行毫秒数 * @param immediate true - 立即执行, false - 延迟执行 */ function debounceAll(func, wait, immediate) { let timer; return function() { if (timer) clearTimeout(timer); if (immediate) { let callNow = !timer; timer = setTimeout(() => { timer = null; }, wait); if (callNow) func.apply(this, arguments); } else { timer = setTimeout(() => { func.apply(this, arguments); }, wait) } } } content.onmousemove = debounceAll(count, 1000, true)
16、函数节流(throttle)
函数节流:一个函数执行一次后,只有大于设定的执行周期后才会执行第二次
1、搜索联想(keyup)
2、计算鼠标移动的距离(mousemove)
3、射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
5、监听滚动事件判断是否到页面底部自动加载更多:给 scroll 加了 debounce 后,只有用户停止滚动后,才会判断是否到了页面底部;如果是 throttle 的话,只要页面滚动就会间隔一段时间判断一次.
// 时间戳版 function throttle(func, wait) { let previous = 0; return function() { let now = Date.now(); let context = this; let args = arguments; if (now - previous > wait) { func.apply(context, args); previous = now; } } }
17、js小数运算出现的问题(精度丢失)
原因:js number类型运算都需要先将十进制转二进制
但小数点后的位数转二进制会出现无限循环的问题,只能舍0入1,所以会出现小数点丢失问题。
解决方法:
(1)Number.EPSILON:JavaScript 能够表示的最小精度。
Number.EPSILON === Math.pow(2, -52)
(2)toFix()
(3) *10的倍数转换为整数计算。