学习专题
对象继承
- 原型链继承(引用值共享的问题)
- 构造函数继承(没有办法拿到原型上的方法)
- 组合继承(伪经典继承)
- 寄生组合继承(经典继承)
- 圣杯模式(
Buffer) extends继承(es6)- 拷贝继承
原型链继承
缺点:引用值共享
//实例对象sub1有能力访问自身的原型 sub1 -> Sub.prototype//Sub.prototype = new Super()//new Super()实例对象 有能力访问自身的原型 new Super -> Super.prototypefunction Super() {this.a = '111';}Super.prototype.say = function () {console.log('222');}function Sub() {}Sub.prototype = new Super();var sub1 = new Sub();console.log(sub1.a); //111console.log(sub1.say()); //222
引用值共享问题
function Super() {this.a = [1, 2, 3, 4];}Super.prototype.say = function () {console.log('222');}function Sub() {}Sub.prototype = new Super();var sub1 = new Sub();var sub2 = new Sub();sub1.a.push(5);//按道理两个实例化对象不会影响属性console.log(sub1.a); //[1, 2, 3, 4, 5]console.log(sub2.a); //[1, 2, 3, 4, 5]
构造函数继承
//构造函数继承//解决引用值共享的问题function Super() {this.a = [1, 2, 3, 4];}Super.prototype.say = function () {console.log('222');}function Sub() {var that = this;// this.a = [1, 2, 3, 4];Super.call(that);}var sub1 = new Sub();var sub2 = new Sub();//更改原始值// sub1.a = '333';//更改引用值sub1.a.push(5);//测试console.log(sub1.a);console.log(sub2.a);
组合继承(伪经典继承)
//组合继承function Super() {this.a = [1, 2, 3, 4];}Super.prototype.say = function () {console.log('222');}//借用function Sub() {var that = this;//这里会重复调用SuperSuper.call(that);}//原型链继承Sub.prototype = new Super();var sub1 = new Sub();var sub2 = new Sub();//测试console.log(sub1.say());console.log(sub2.say());
寄生组合继承(经典继承)
缺点:有机会被重写导致无法访问自身原型上的属性和方法
//寄生组合继承(经典继承)//Object.create()/*** es5以下不支持* 兼容性方案* @参数 目标对象的原型* @返回值 新的继承后的实例化对象*/if (!Object.create) {Object.create = function (proto) {function F() {}F.prototype = proto.prototype;return new F();}}function Super() {this.a = [1, 2, 3, 4];}Super.prototype.say = function () {console.log('222');}//借用function Sub() {var that = this;//这里会重复调用SuperSuper.call(that);}//如果在Sub.prototype赋值前定义方法会被覆盖掉//缺点:Sub.prototype自身的属性和方法无法访问Sub.prototype.say = function(){console.log('444')}//API继承Super原型Sub.prototype = Object.create(Super.prototype);var sub1 = new Sub();var sub2 = new Sub();// //更改原始值// sub1.a = '333';// //更改引用值// sub1.a.push(5);//测试// console.log(sub1.a);// console.log(sub2.a);// console.log(sub1.say());// console.log(sub2.say());
toString
Object.prototype.toString.call()
3种判断数组的方法:
constructorinstanceof:不能判断原始值Object.prototype.toString
var a = [] || {};console.log(a.constructor); //Array(){}console.log(a instanceof Array); //truevar string = Object.prototype.toString.call(a);console.log(string); //[object Array]Object.prototype = {toString: function () {//call(a) -> a.toString()this.toString(); //[object Array]}}
var arr = new Array(1, 2, 3);console.log(arr.toString()); //1 2 3console.log(arr); //[1, 2, 3]console.log(Object.prototype.toString.call(arr)); //[object Array]
以上推出Object.prototype.toString.call()结果的用法,此方法可以判断后端发送过来的数据的数据类型
var str = Object.prototype.toString;var trueTip = '[object Array]';if (str.call(a) === trueTip) {console.log('是数组'); //是数组} else {console.log('不是数组');}
预加载
预加载:等图片加载完毕才放进html里
//预加载var oDiv = document.getElementsByTagName('div')[0];var imgSrcArr = ['http://www.xxx.com/1.jpg','http://www.xxx.com/1.jpg','http://www.xxx.com/1.jpg'];imgSrcArr.forEach(function (elem) {var oImg = new Image();oImg.src = elem;oImg.style.width = '100%';//图片加载完毕插入到HTMLoImg.onload = function () {oDiv.appendChild(oImg);}});
懒加载
懒加载:滚动页面加载图片,不滚动时不会加载图片
源码地址:https://gitee.com/kevinleeeee/img-lazy-load-demo
function imgLazyLoad(images) {var imgLen = images.length,n = 0;return function () {// 找到可视区域高度var cHeight = document.documentElement.clientHeight,//找到滚动高度并兼容IEsTop = document.documentElement.scrollTop || document.body.scrollTop,imgItem;for (var i = n; i < imgLen; i++) {imgItem = images[i];//每张图片的offsetTop < cHeight + sTop证明出现在可视区域之内if (imgItem.offsetTop < cHeight + sTop) {//拿到当前项地址赋值给srcimgItem.src = imgItem.getAttribute('data-src');//清除原来的data-src属性imgItem.removeAttribute('data-src');n++;}}}}
异步加载
JavaScript脚本 是由浏览器帮忙设置的
目的:为了不占用加载页面的时间
怎么设置异步加载脚本?
怎么写一个异步加载程序?
什么时候要用异步加载?
工具类函数/跟DOM不相关的库(里面的函数在需要调用的时候才执行)/按需加载
动态创建script标签什么时候开始下载文件?
当document.createElement('script').src的时候已经开始下载文件
什么时候才能正确的执行下载的文件?
当创建的脚本放入到html里面的时候才开始执行
关于defer:
注:IE8及以下使用
标签属性,让标签变成异步加载另开辟其他线程,不影响css/dom解析
特点:
异步加载不阻塞但不会立即执行,等dom树构建解析完毕才执行
关于async:
注:
w3c标准,html5,IE9及以上支持- 异步加载操作时 不能进行DOM文档操作
标签属性 async="async"
自己写一个异步加载:
//企业级异步加载写法//主动创建script就会变成异步来加载//此写法会阻塞window.onload()//window.onload()会等所有程序加载完毕才会执行,是弊端<script type="text/javascript">var s = document.createElement('script');s.type = 'text/javascript';s.src = 'utils.js';document.body.appendChild(s);// console.log(s);// <script type="text/javascript" src="utils.js"></script></script>
问题:能否在页面onload全部加载完毕的情况下再进行异步加载呢?
//希望onload完成之后再进行异步加载//一般来说,异步加载的<script>标签都放在head里面的上方(function () {function async_load() {var s = document.createElement('script'),//找到第一个<script>标签oScript = document.getElementsByTagName('script')[0];s.type = 'text/javascript';//异步加载该文件s.src = 'utils.js';//动态把新建的<script>标签插入到第一个找到<script>标签前面oScript.parentNode.insertBefore(s, oScript);}//事件处理的方式使当出发onload事件后,再执行异步加载的函数程序//兼容IE写法if (window.attachEvent) {window.attachEvent('onload', async_load);} else {window.addEventListener('load', async_load, false);}})();
防抖节流
函数防抖解决:
- 对于在事件被触发
n秒后再执行的回调(延迟执行) - 如果在这
n秒内再次触发事件,计时器频繁重新开始计时
防抖存在问题:
- 污染全局
- 初次触发事件时,会延迟执行
//封装//fn函数//time延迟时间//是否为第一次访问function debounce(fn, time, triggleNow){var t = null,res;//希望返回新的函数//万一返回的函数带有参数,带上参数argsvar debouced = function(){var _self = this,args = arguments;if(t){clearTimeout(t);}if(triggleNow){//exec是否执行//!null = truevar exec = !t;//延迟执行t = setTimeout(function(){t = null;});//说明是首次访问if(exec){//直接执行res = fn.apply(_self, args);}}else{t = setTimeout(function(){res = fn.apply(_self, args);}, time);}return res;}//强制取消防抖debounced.remove = fuction(){clearTimeout(t);t = null;}return debounced;}
函数节流解决:
- 事件被触发,
n秒之内只执行一次处理函数
function throttle(fn, delay){var t = null,begin = new Date().getTime();return function(){var _self = this,args = arguments,cur = new Date().getTime();clearTimeout(t);if(cur - bigin >= delay){fn.apply(_self, args);begin = cur;}else{t = setTimeout(function(){fn.apply(_self, args);}. delay);}}}
放大模式
模块化的放大模式(augmentation):把依赖的模块注入到程序中
好处:多人共同写同一模块
var mod1 = (function (mod) {var test1 = function () {};var test2 = function () {}return {test1,test2}})(mod2);
bind/call/apply
重写call
特点:
- 如果一个函数跟上
call(),说明函数执行 - 函数的
this指向call的第一个参数 call第2个参数开始是函数的参数列表
function test(){console.log(this);//{a:1, b:2}console.log(arguments);//Arguments['zhangsan', 'lisi']}test.call({a:1, b:2}, 'zhangsan', 'lisi');
注意:ES6模块化中自动使用严格模式
//非严格模式底下function test(){console.log(this);//this -> window}//严格模式下//this -> undefined
对于一个方法来说,谁调用它,默认函数内部的this指向就指向谁
var obj = {a: 1,b: 2,test(){console.log(this);//this -> obj}}obj.test();
window.test = function(){console.log(this);//this -> window}function test(){console.log(this);//this -> window}window.test();
问题:如何实现改变this指向?
默认情况: 函数内部this指向调用者
myCall函数内部的this指向调用者函数本身- 将第一个参数
ctx对象里定义一个新的函数originFn - 将
myCall函数内部的this和ctx.originFn的函数指向同一个引用地址 - 这样就可以使
originFn指向调用者ctx - 从而实现内部
this指向第一个参数ctx对象
问题:如何拿到第二参数开始到结束的所有参数作为调用函数的实参列表?
- 在
myCall函数内部定义args数组容器 - 从第一项开始
for循环arguments里的参数 - 收集每一项到
args数组里 - 利用
eval()执行字符串程序eval("ctx.originFn(" + args + ")") - 将
args收集的参数展开平铺到fn的实参中去 - 实现拿到调用函数的实参列表
- 最后返回
evel()方法的结果到外界实现调用函数会有返回值
源码:
https://gitee.com/kevinleeeee/my-call-vite-demo
重写apply
特点:
- 如果一个函数跟上
apply(),说明函数执行 - 函数的
this指向apply的第一个参数 apply第2个参数开始是函数的数组参数列表apply只能取到第二个参数,第三个参数开始到最后忽略apply第二个参数传递null,undefined不报错但argument长度为0apply第二个参数是原始值时会报错apply第二个参数是对象或函数时arugument长度为0
源码地址:
https://gitee.com/kevinleeeee/my-apply-vite-demo
重写bind
特点:
- 如果一个函数跟上
bind(),说明函数不执行 - 返回一个新的函数, 执行新的函数会改变
this, 传参可以拿到参数 bind可以分离调用函数的参数bind接受一部分参数,返回的新函数接受一部分参数- 函数的
this指向bind的第一个参数 bind第2个参数开始是函数的参数列表- 实例化返回的新函数
this指向是调用函数构造的实例 - 实例应该继承构造函数上的原型属性
var obj1 = { a: 1, b: 2 };var t = test.bind(obj1, 'rose', 'jack');//返回的新函数`this`指向是调用者t();//{a: 1, b: 2} Arguments(2) ['rose', 'jack', callee: (...), Symbol(Symbol.iterator): ƒ]//实例化返回的新函数`this`指向是调用函数构造的实例new t();//test {} Arguments(2) ['rose', 'jack', callee: (...), Symbol(Symbol.iterator): ƒ]
//实例应该继承构造函数上的原型属性test.prototype.myLove = 'lisi';var t = test.bind(obj1, 'rose', 'jack');new t();//print:test {}[[Prototype]]: ObjectmyLove: "lisi"
源码地址:
https://gitee.com/kevinleeeee/my-bind-vite-demo
new
案例:重写new
对实例化构造函数的new关键词进行重写
关于实例化的过程:
function C(){this.a = a;this.b = b;}new C();//1.this指向一个空对象//2.在this.a/this.b时,会往空对象里增加{a: a, b: b}//3.空对象里还增加一个原型属性{__proto__: C.prototype}//4.c.prototype{ constructor: ƒ C()}
问题:如何模拟实例化过程?
new关键字无法模拟,在实例化时传入的参数1是构造函数, 其它参数顺位传入
var test = myNew(Test, 1, 2);
源码地址:
https://gitee.com/kevinleeeee/my-new-vite-demo
循环
循环遍历迭代的区别
- 循环:语言层面的语法 重复执行一段程序的方案
- 遍历:业务层面的做法 观察或获取集合中的元素的一种做法
- 迭代:实现层面的上的概念 实现遍历的底层方案其实就是迭代
在ECMAScript3中,没有针对可迭代对象的具体的遍历方法,所以在ECMAScript5中增加:
- 7个专门针对数组的遍历方法
- 一个
for..in对象的遍历方法
for in用于遍历对象或类数组拿到可枚举的键名,它的作用是用于调试,更方便的去检测对象的属性
for of只用于可迭代对象(Array,Map,Set,String,TypedArray,arguments等),它依赖Symbol.Iterator,创建一个迭代循环,调用自定义迭代钩子,为每个不同的属性的值执行语句
Symbol.Iterator为每一个对象定义默认的迭代器,迭代器可以被for of循环使用,注意对象原型上没有该迭代器
对象操作
EMCAScript委员会 14种底层对象操作方法(需要背)
方法一:获取原型
内置方法[[GetPrototypeOf]]
var proto = Object.getPrototypeOf(obj);console.log(proto); 通过底层API方法获取console.log(obj.__proto__); 通过对象本身的原型容器获取console.log(Object.protype); 通过对象原型获取
方法二:设置原型
内置方法[[SetPrototypeOf]]
Object.setPrototypeOf(obj, {c: 3, d: 4});console.log(obj);/*** {* a: 1,* b: 2,* __proto__:* c: 3,* d: 4* }*/
方法三:获取对象的可拓展性
内置方法[[IsExtensible]]
var extensible = Object.isExtensible(obj);console.log(extensible); //true
方法四:获取自有属性
内置方法[[GetOwnProperty]]
Object.setPrototypeOf(obj, {c: 3, d: 4});console.log(Object.getOwnPropertyNames(obj));//['a', 'b'] 返回属性集合数组
方法五:禁止拓展对象
内置方法[[PreventExtensions]]
Object.preventExtensions(obj);obj.c = 3; //禁止增加属性delete obj.a; //可删除属性console.log(obj); //对象没有变化
方法六:拦截对象操作
内置方法[[DefineOwnProperty]]
Object.defineProperty(obj);
方法七:判断是否是自身属性
内置方法[[HasPropert]]
console.log(obj.hasOwnPropert('a')); //true
方法八: 获取
内置方法[[Get]]
console.log('a' in obj); //trueconsole.log(obj.a); //1
方法九:设置
内置方法[[Set]]
obj.a = 3;obj['b'] = 4;console.log(obj);
方法十:删除
内置方法[[Delete]]
delete obj.a;console.log(obj);
方法十一:枚举
内置方法[[Enumerate]]
for(var k in obj){console.log(obj[k]); //1 2 可枚举}
方法十二:获取键集合
内置方法[[OnPropertyKeys]]
console.log(Object.keys(obj));//['a', 'b'] 返回属性集合数组
方法十三:函数调用
function test(){...}test();obj.test2 = function(){...}obj.test2();
方法十四:实例化
function Test(){...}new Test();
ES5方法重写
问题:为什么需要重写ES5中的方法?
在老的项目中(国家单位,银行,政府网站等)是在低版本IE浏览器里用ES3实现的,对ES5不兼容,所以要写成ES3能兼容的方法,能在老的项目中跑起来
需要重写的方法有:
forEachmapfilterreducereduceRighteverysomedeepClone:重写以上方法时需要用到
源码地址:
