李不要熬夜

李不要熬夜 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

李不要熬夜 发布了文章 · 4月15日

前端面试复盘:vue技术面没有难倒我,hr面却是一把挂

金三银四,怎么能少了本菜😁😁。陆续面试了几家中小公司,正好借此机会好好恶补一下基础,要是能找到一个合适的工作那就更完美啦。整理了近一次面试复盘笔记,为正在找工作的小伙伴们,提供一丢丢的帮助。祝大家都能找到money多多的工作💰💰。

概况

  • 公司:坐标深圳。
  • 面试官:特有礼貌,还先自我介绍了。
  • 面试结果:应该是hr面没过(跳的太频繁了,简历难看🤣🤣)。
  • 面试感受:技术面还行,hr面很难受。

面试题

说明:✔ 代表后面有关于该问题的重新整理。

  1. 介绍一下自己之前做过的项目。
  2. Vue的生命周期。✔
  3. CreatebeforeMount他们两之间有什么区别。
  4. Vue组件通信。✔
  5. v-ifv-show的区别以及使用场景。✔
  6. nextTick的使用场景和作用。✔
  7. Vue中的key有什么作用。✔
  8. 计算属性和watch的区别。✔
  9. 子元素上下左右居中。
  10. 生成一条0.5px的线。✔
  11. 自适应方案。
  12. remrm的区别。✔
  13. vw和百分比有什么区别。✔
  14. 合并两个数组。✔
  15. 数组去重,冒泡排序。✔
  16. Object去掉其中一项属性,delete删除对象有什么影响。✔
  17. 深浅拷贝。
  18. 防抖节流 。
  19. 从0+100怎么实现。✔
  20. 一到一百个相同的请求,后面的依赖前面一个的结果,现在要拿到第一百个结果要怎么做。
  21. 假如你在爬楼梯,楼梯一共有N层,但你每次爬楼梯只能走一步、两步或三步,计算共有多少种走法,怎么打印出所有走法?

Vue的生命周期。

组件生命周期

  1. new Vue(),初始化事件和生命周期
  2. beforeCreate$eldata都是undefined
  3. 初始化数据和方法(dataprops的响应式处理,mehods方法声明)
  4. created$elundefined,修改data不触发update
  5. 判断有没有el项(vm.$mount(el)),判断有没有模板(没有将el外层的HTML当模板),将模板编译成渲染函数,返回虚拟DOM
  6. beforeMounted$el是虚拟DOM,修改data不触发update
  7. 创建正式DOM替换虚拟DOM,挂载到页面指定容器显示
  8. mounted(可操作真实DOM
  9. 数据变更
  10. beforeUpdate
  11. 重新渲染虚拟DOM并通过DIFF算法比较差异更新真实DOM
  12. updated
  13. 调用vm.$destory()
  14. beforeDestory(清理计时器、事件)
  15. 移除数据监听、事件监听和子组件
  16. destoryed(实例不可用)

keep-alive生命周期
keep-alive包裹的组件有 activateddeactivated 两个生命周期。如<keep-alive>包裹两个组件:组件A和组件B。当第一次切换到组件A时,组件A的createdactivated生命周期函数都会被执行,切换到组件B,这时组件A的deactivated的生命周期函数会被触发;在切换回组件A,组件A的activated生命周期函数会被触发,但是它的created生命周期函数不会被触发了。

vue组件通信。

  1. 父子间通信:父亲提供数据通过属性 props传给儿子;儿子通过 $on 绑父亲的事件,再通过 $emit 触发自己的事件(发布订阅)
  2. 利用父子关系 $parent 、 $children
  3. 父组件提供数据,子组件注入。 provide 、 inject ,插件用得多。
  4. ref 获取组件实例,调用组件的属性、方法
  5. 跨组件通信 Event BusVue.prototype.bus=newVue)基于on$emit
  6. vuex 状态管理实现通信

v-if和v-show的区别以及使用场景。

区别
v-if是删除生成dom,v-show是切换dispaly的状态。
使用场景

  • v-if
  1. 某一块代码在运行时条件很少改变,使用 v-if 较好 (v-if 有更高的切换开销)
  2. 在组件上使用v-if可触发组件的生命周期函数
  3. transition结合使用 当条件变化时该指令可以触发过渡效果(用于动画切换)
  • v-show
  1. 需要非常频繁地切换某块代码,使用 v-show渲染
  2. 当条件变化时该指令触发过渡效果(用于动画切换)

nextTick的使用场景和作用。

使用场景
例:一个子组件通过v-if控制隐藏显示<t v-if='show'><t/>,当修改完显示状态后,立马通过ref去操作子组件的方法,这个时候会报错,原因在于子组件此时可能还未渲染完成,这个时候使用nextTick可以解决,他会在dom更新完成之后再去调用。
作用
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

vue中的key有什么作用。

  • key会用在虚拟DOM算法(diff算法)中,用来辨别新旧节点。
  • 不带key的时候会最大限度减少元素的变动,尽可能用相同元素。(就地复用)
  • key的时候,会基于相同的key来进行排列。(相同的复用)
  • key还能触发过渡效果,以及触发组件的生命周期

计算属性和watch的区别。

  • 处理数据的场景不同,监听器(watch)适合一个数据影响多个数据,计算属性适合一个数据受多个数据影响
  • 计算属性有缓存性,计算所得的值如果没有变化不会重复执行,但是watch会重复执行
  • 监听器选项提供了更通用的方法,适合执行异步操作或较大开销操作的情况

生成一条0.5px的线。

rem和rm的区别。

  • rem是相对于根元素字体大小
  • em是相对于自身字体大小

vw和百分比有什么区别。

  • 百分比是相对高度,相对于他的父元素而言。
  • vw永远都是相对于视窗大小的。

合并两个数组。

  1. arr1.concat(arr2)
  2. [...arr1,...arr2]
  3. 循环

数组去重,冒泡排序。

数组去重

  1. Array.from(new Set(arr))
  2. [...new Set(arr)]
    3. for循环嵌套,利用splice去重
    4. 新建数组,利用indexOf或者includes去重
    5. 先用sort排序,然后用一个指针从第0位开始,配合while循环去重
    冒泡排序
function bubbleSort (arr) {
  for (let i = 0; i < arr.length; i++) {
    let flag = true;
    for (let j = 0; j < arr.length - i - 1; j++) {
      if (arr[j] > arr[j + 1]) {
        flag = false;
        let temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
      }
    }
    if (flag) break;
  }
  return arr;
}
复制代码

这个是优化过后的冒泡排序。用了一个flag来优化,它的意思是:如果某一次循环中没有交换过元素,那么意味着排序已经完成了。

冒泡排序总会执行(N-1)+(N-2)+(N-3)+..+2+1趟,但如果运行到当中某一趟时排序已经完成,或者输入的是一个有序数组,那么后边的比较就都是多余的,为了避免这种情况,我们增加一个flag,判断排序是否在中途就已经完成(也就是判断有无发生元素交换)

Object去掉其中一项属性,delete删除对象有什么影响。

delete Object['name']
delete只能删除自有属性,不会影响原型链上的属性

从0+100怎么实现。

1、使用for循环从1加到100的总和

let sum=0;
for(var i=0;i<=100;i++){
    sum+=i;
}
复制代码

2、使用while函数从1加到1000的总和

let i=0, sum=0;
while(i<=100){
    sum+=i++;
}
复制代码

技术面的还行是因为刷了很多前端面试题,hr面还是想说大家不要和我一样跳槽频繁了,这个是硬伤。我把面试题做了一份整理,免费分享给大家,希望大家技术面和hr面双丰收!


由于篇幅原因,如有需要以上完整学习笔记PDF,可以点击这里免费自取!!

整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~小编在这谢谢大家啦!

查看原文

赞 0 收藏 0 评论 0

李不要熬夜 发布了文章 · 4月14日

javascript中BOM部分基础知识总结

一、什么是BOM

     BOM(Browser Object Model)即浏览器对象模型。

     BOM提供了独立于内容 而与浏览器窗口进行交互的对象;

     由于BOM主要用于管理窗口与窗口之间的通讯,因此其核心对象是window;

     BOM由一系列相关的对象构成,并且每个对象都提供了很多方法与属性;

     BOM缺乏标准,JavaScript语法的标准化组织是ECMA,DOM的标准化组织是W3C,BOM最初是Netscape浏览器标准的一部分。

二、学习BOM学什么

   我们将学到与浏览器窗口交互的一些对象,例如可以移动、调整浏览器大小的window对象,可以用于导航的location对象与history对象,可以获取浏览器、操作系统与用户屏幕信息的navigator与screen对象,可以使用document作为访问HTML文档的入口,管理框架的frames对象等。在这里,只介绍一些window对象等的基础知识,其中会有一些ECMAscript的知识还会说明。其他对象Location、Screen、Navigator、History不一一详细介绍了。。

BOM结构图

三、window对象

window对象是js中的顶级对象,所有定义在全局作用域中的变量、函数都会变成window对象的属性和方法,在调用的时候可以省略window。

    例:打开窗口 window.open(url,target,param);

   // url 要打开的地址

     //target  新窗口的位置   _blank  _self  _parent(父框架)

     //param  新窗口的一些设置

     //返回值,新窗口的句柄

          关闭窗口:window.close();

四、BOM零碎知识(window对象)

1.定时器
 延迟执行     setTimeout( [string | function] code, interval);
                       clearTimeout([number] intervalId);

1 <body>
 2 <input type="button" value="closeTimerId" id="btn">
 3 <script>
 4     var btn = document.getElementById("btn");
 5     var timerId = setTimeout(function () { 6         alert("23333");
 7 }, 3000);
 8 btn.onclick = function () { //在设置的时间之前(3s内)点击可以取消定时器
 9 clearTimeout(timerId); 10 } 11 </script>
12 </body>

 定时执行     var timerId = setInterval(code, interval);
 clearInterval(timerId);     //清除定时器

 倒计时案例:

 1 <body>
 2 <input type="button" value="倒计时开始10" id="btn" disabled/>
 3 <script>
 4     var btn = document.getElementById("btn");
 5     var num = 10;
 6     var timerId = setInterval(function () { 7         num--;
 8 btn.value = "到时器开始" + num; 9         if (num == 0) { 10 clearInterval(timerId); 11 btn.disabled = false; 12 btn.value = "同意,可以点了"; 13 } 14     },1000); 15 </script>    
16 </body>

2.offset系列方法

offset示意图

3.scroll系列方法

scroll示意图

4.client系列

clientX和clientY     获取鼠标在可视区域的位置     clientX = width + padding,clientY = height + padding

clientLeft     边框的宽度,若有滚动条的话,包括滚动条

client示意图

例: 获得页面可视区域的大小

1 function client() { 
2 return { 
3 clientWidth: window.innerWidth || document.body.clientWidth || document.documentElement.clientWidth || 0;
 4 clientHeight: window.innerHeight || document.body.clientHeitght || document.documentElement.clientHeight || 0; 
5                  };
 6 }

5.事件参数e

当事件发生的时候,系统会自动的给事件处理函数传递一个参数,会提供事件相关的一些数据,事件参数e浏览器的兼容性检测: e = e || window.event

6.获得计算后样式的方法

7.事件补充

  • 注册事件
  • 注册事件的性能问题
  • 移除事件
  • 事件冒泡
  • 事件捕获  事件的三个阶段
  • 事件对象的常见属性

DOM笔记里有提到注册事件和移除事件,这里着重讲事件对象,事件对象的常见属性

 7.1 target 和currentTarget

 7.2 事件冒泡

用addEventListener注册事件的时候,第三个参数是false,即是冒泡。

冒泡的好处 - 事件委托

从一个例子中说明

1 <body>
 2 <ul id="ul">
 3     <li>我是第1个li标签</li>
 4     <li>我是第2个li标签</li>
 5     <li>我是第3个li标签</li>
 6     <li>我是第4个li标签</li>
 7 </ul>
 8 <input type="button" value="insertLi" id="btn">
 9 <script>
10     var ul = document.getElementById("ul"); 11     var btn = document.getElementById("btn"); 12 //把本来应该给li注册的事件,委托给ul,只需要给一个元素注册事件
13 //动态创建的li,也会执行预期的效果
14     ul.addEventListener("click", test, false); //注册点击事件
15 btn.onclick = function () { //点击同样会有alert
16         var li = document.createElement("li"); 17 li.innerHTML = "我是新插入的li标签"; 18 ul.appendChild(li); 19 }; 20 //函数写在注册事件代码之外,提高性能
21     function test(e) { 22 alert(e.target.innerText); 23 } 24 </script>
25 </body>

当事件冒泡影响到了其他功能的实现时,需要阻止冒泡     

 阻止默认行为的执行

看一下阻止跳转到百度的例子:

 1 <body>
 2     <a href="http://www.baidu.com" id="link">百度</a>
 3 <script>
 4     var link = document.getElementById("link");
 5     link.addEventListener("click", fn, false);
 6     function fn(e) { 7         e.preventDefault();
 8         //若用return false; 不起作用,若用link.onclick = function();return false可以阻止
 9 } 10 </script>
11 </body>

 7.3 鼠标事件的参数

还有7.2中的取消事件冒泡和阻止默认行为的执行

8.正则表达式

定义:

正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。

规则:

 8.1 正则表达式对象RegExp

1 <body>
2     <a href="http://www.baidu.com" id="link">百度</a>
3 <script>
4 // var regularExpression =  new RegExp("\\d");     //第一种写法
5     var regularExpression = /\d/; //第二种写法
6     var str = "adfj23dald"; 7 console.log(regularExpression.test(str)); //test就是匹配方法,结果是true
8 </script>
9 </body>

8.2 正则之匹配

例:验证电子邮箱

//验证电子邮箱
    // abc@sohu.com
    // 11111@qq.com
    // aaaaa@163.com
    // abc@sina.com.cn 
     var reg = /^\w+@\w+\.\w+(\.\w+)?$/;
     var str = "abc@sina.com.cn";
     console.log(reg.test(str));

 8.3 正则之提取

例:找数字

1 var str = "张三: 1000,李四:5000,王五:8000。"; 
2 var reg = /\d+/g; 
3 //默认情况下,找到第一个匹配的结果就返回,后面加个g,就是全局找
 4 var arr = str.match(reg); 
5 console.log(arr);</pre>

 8.4 正则之替换

例:所有的逗号替换成句号

1 var str = "abc,efg,123,abc,123,a"; 
2 str = str.replace(/,/g,".");
 3 console.log(str);</pre>

   8.5 正则的分组( )

  在正则表达式中用( )把要分到一组的内容括起来,组分别是RegExp.$1    RegExp.$2等等

例:交换位置  源字符串"5=a, 6=b, 7=c"  要的结果"a=5, b=6, c=7"

1 var str = "5=a, 6=b, 7=c"; 
2 str = str.replace(/(\d+)=(\w+)/g, "$2=$1"); 
3console.log(str);

9.键盘事件对象

例:在切换鼠标焦点时,用enter键代替tab键

1 <body>
 2 <input type="text"><input type="text"><input id="t1" type="text"><input type="text"><input type="text"><input type="text"><inputtype="text"><input type="text"><input type="text"><input type="text">
 3 <script>
 4     var inputs = document.body.getElementsByTagName("input");
 5     for(var i = 0, length = inputs.length; i < length ; i++) {
 6         var input = inputs[i]; 7         //回车键的keyCode是13
 8         if(input.type === "text") {
 9             //按下回车,让下一个文本框获得焦点
10 input.onkeydown = function (e) { 11                 if(e.keyCode === 13) { 12                     this.nextElementSibling.focus();//focus() 他触发了onfocus事件
13 } 14 } 15 } 16 } 17 </script>
18 </body>

补充:js中的instanceof运算符介绍

判断某个变量是不是某种类型的对象

1 var num = 5;
 2 var arr = []; 
3 console.log(num instanceof Array);          //false
 4 console.log(arr instanceof Array);            //true</pre>
查看原文

赞 0 收藏 0 评论 0

李不要熬夜 发布了文章 · 4月14日

JS的封装(JS插件的封装)

JS中类的概念

类,实际上就是一个function,同时也是这个类的构造方法,new创建该类的实例,new出的对象有属性有方法。
方法也是一种特殊的对象。

类的方法

在构造方法中初始化实例的方法(就是在构造方法中直接编写方法,并new实例化)是不推荐的,消耗内存(每次实例化的时候都是重复的内容,多占用一些内存,既不环保,也缺乏效率)。
所有实例是共有的,创建多个实例不会产生新的function,推荐在类的prototype中定义实例的方法,
prototype中的方法会被所有实例公用。

1. 仿照jQuery封装类
匿名函数

(function () {
    //
})();

var Id = function (i) {
  this.id = document.getElementById(i);
};
window.$ = function (i) {
  return new Id(i);
};

console.log($('main'));
function Cat(name, color) {
  this.name = name;
  this.color = color;
}

var cat1 = new Cat('大毛', '黄色');
var cat2 = new Cat('二毛', '黑色');

Cat.prototype.a = 'aaa';
Cat.prototype.type = '猫科动物';
Cat.prototype.eat = function () {
  alert('吃老鼠');
};

cat1.eat();
cat2.eat();

console.log(cat1.name);
console.log(cat2.color);

// cat1和cat2会自动含有一个constructor属性,指向它们的构造函数。
console.log(cat1.constructor == Cat);
console.log(cat2.constructor == Cat);

// Javascript还提供了一个instanceof运算符,验证原型对象与实例对象之间的关系。
console.log(cat1 instanceof Cat);
try {
  console.log(a instanceof Cat);
} catch (e) {
  console.log(e);
}

所谓"构造函数",其实就是一个普通函数,但是内部使用了this变量。对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例对象上。
Javascript规定,每一个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。

prototype模式的验证方法
1. isPrototypeOf() 判断某个prototype对象和某个实例之间的关系
2. hasOwnProperty() 判断一个属性是本地属性还是继承自prototype对象的属性
3. in 判断是否在某个对象里

function Cat(name, color) {
  this.name = name;
  this.color = color;
}

Cat.prototype.type = '猫科动物';

var cat1 = new Cat('大毛', '黄色');
var cat2 = new Cat('二毛', '黑色');

console.log(Cat.prototype.isPrototypeOf(cat1));
console.log(Cat.prototype.isPrototypeOf(cat2));

console.log(cat1.hasOwnProperty('name'));
console.log(cat2.hasOwnProperty('type'));

console.log('name' in cat1);
console.log('type' in cat1);
查看原文

赞 0 收藏 0 评论 0

李不要熬夜 发布了文章 · 4月13日

今天聊:2~3年前端走出离职困境与舒适区

前言

工作2~3年的前端群体容易出心理问题,很大一部分原因就在于还没有建立更成熟的自我认知,也没有完成从学生到社会人身份的转变,加上前端行业的快速迭代,中小型公司技术管理的混乱,和身边同行带来的竞争压力,包括完全脱离父母且要照顾他们的压力,这个期间会让不少小伙伴感到迷茫,让他们对于前端行业产生怀疑,失去编程的乐趣,稀释奋斗的激情,产生对抗和「丧」的情绪,甚至不经意间陷入或多或少的抑郁,进而职业路越走越急,跳槽失去方寸,整个牌局赢面越来越小,非常可惜。
篇幅较长,大约需要3-5分钟,请耐心阅读

正文如下

我和身边同龄人及后台私信我的朋友聊了自身的工作情况,问题大多具有相似性,而又有每个人的特殊性:

在认知不够成熟的时候,很容易陷入所谓的困境,或者顺风顺水的停滞搁浅在舒适区,这一篇我们更关注自我认知(心理建设、价值设定)和困境突围,首先我们从困境的定义开始,舒适区则放到最后。

困境与困难到底是什么

很多时候,我们对于困境的描述,往往像下面这些:

  • 计算机基础不扎实甚至是完全没有,比如算法、数据结构、网络相关理论等等
  • JS 基础能力不行,心里没底又觉得没有力气和方向迈出去
  • 一年经验用三年,对于工作中用到的技术没有去了解内部机制
  • 学历是大专,感觉自己没有自信,未来也进不了大厂
  • 我不是科班出身,是培训的,感觉自己能力薄弱竞争不过别人
  • 没有考虑如何把代码写的更好,业务中到处是胶水代码
  • 工作环境导致的身边没有大牛,没有样板可以模仿、学习
  • 空有想法没有行动,导致收集一堆资料却从未去看过
  • 我们内部用的工具总是很老套。没有人想改变什么,没有野心,没有梦想,像一架老马车,大家都在凑合

等等不计其数的职业困惑,这些本质上是困难而非困境,我们定义为困境后,再从社区从他人这里获得的,也经常是相对碎片化的建议,比如:

  • 前端虽然工作中用不了数据结构算法等等这些基础知识,但是这些知识决定了一个人的天花板高度,并且也是大厂常常考察的一点。当然学习这些基础并不能一蹴而就,只能在平时业余时间有意识的去补齐这部分的短板。
  • 前端知识点确实很多很杂,因为并没有很好地阅读一些基础书籍,高程、你不知道的 JS、 JS 精粹这些书籍读完一定能打好一个不错的 JS 基础。
  • 工作年限越长,公司对于开发者的要求就会越高。这时候公司需求的员工是一个能写代码、能解决团队成员遇到的问题、能带人的这样一个工程师。了解技术的内部机制才能让自己不被淘汰。
  • 平时要有意识的去重构自己的代码,抽离可以复用的功能,这样做通常都能提高之后的开发效率。但是大家可能并不知道如何去优化自己的代码,一是可以学习团队中大牛的代码,看他是如何实现的;二是学习业内顶尖库的代码,看看人家是如何设计的。
  • 收集了一堆资料就当看过了,对于存在这样情况的前端工程师,可以选择想学什么再去收集相关资料,然后立马设定 TodoList 督促自己学习。

看过听过之后,可能有一两条我们坚持下去了,而其他都慢慢丢在了脑后,这背后的一个很大的原因在于,我们往往不是缺少目标,甚至也不缺驱动的原动力,我们缺乏的是对自己职业属性以及困境区分于困难的正确认知,所以认知一旦出了问题,困境就随之而来,无论它是事实上的外界物理环境造成的,还是自己内心期望与惨淡现实的落差造成,这都会持续的伤害着我们的职业成长路线,而此时困境就是我们认知的边界尚未突破的尴尬局面,在这个局面下,一切皆困境。

哪些关键认知需要建立

  • 社会属性,也即我的社会价值,当前阶段(不论是游学,实习,还是刚工作),我存在社会的价值是什么?无论从事哪个行业,无论内心是多懒散,我想我们都向往积极的活着,活着社会上,不成为社会的蛀虫,就一定有所劳动,这些劳动就是我可以为社会提供的价值,也即我存在的意义,这也是我当前从属行业它的价值。
  • 家庭属性,也即我的家庭角色,无论我是孑然一身,还是生活在完整完美的家族关系中,无论我是一穷二白,还是家产非常殷实,我的多重身份(孙子、儿女、父母、哥哥姐姐弟弟妹妹...)就决定了,要为家人带去保障,为家人带来幸福与安稳,所有这一切都是我奋斗的原动力,这一切的奋斗一定不是通过躺着实现,一定需要我的大脑和双手来让血脉的存续,来让亲人的期待都不落空。
  • 群体属性,也即我的社会关系,无论多宅,我们都是社会化的群居动物,团队合作和群体沟通始终贯穿我们终生,通过合作和沟通,我们具备了生存的外在能力,也具备了通过他人观察自己的机会,而所有的合作和沟通都是建立在资源的前提上,也就是你本身具备社交价值,无论是作为主导关系的强势主宰,还是仅仅作为倾听他人负能量的收集桶,这些价值每一年都会被不同的群体反复检验,这种检验会让我们明确知道身边聚拢越来越多优秀的人,还是寥寥无几的平层社交,通过这种比较让我们最终对自己的价值有更清楚的判定。
  • 自我属性,也即我的内心所向,是当个出家人,是当个坦克兵,是当个店老板,还是工程师,这一切社会化的工种行当,都取决于我的价值选择,我定义自己成为什么样的人,内心深处的价值是导向哪里,在不完美的世界中有那么一些美好,它们浮现到我的心中分别是哪些坚守与放弃,这都影响到我的自我定位。

从社会、家庭、群体到自己,每个人的情况都不尽相同,简单来说就是我们的三观不同,在不同的三观驱动下,我们去往的方向和路径就可能截然不同,但对于认知建立,你准备好拷问自己了么?经过拷问后获得对自己最真实的了解、理解和认同后,再来还原到下面具体困境解法里,你的答案才会坚实很多,也会更容易做出对的抉择。

脱离困境并非只有离职

当认知都清晰之后,再来看这个命题,会发现似乎所有的困境不再是困境,仅仅是生活和工作的一个状态,而困扰我们往往只是困难,比如不知道怎么夯实技术基础,不是科班选手不知道怎么学习,团队没有学习氛围怎么破...而困境是什么,困境是我们尽所有能力去克服困难后,依然陷在泥淖中无法挣脱,而如果我们并没有倾尽全力去克服所有的困难之前,怎么可以把困难视为是困境呢?

我们重点聊聊一个常见的困境,就是择业切换之前,渴望离职的时候渴望摆脱的时候,这对于超过 10 年长度的职业生涯来说,要不要走,去哪里,它是事实存在的困境。

离职一定是一条最快路径的可选项,因为整个从行业、公司文化、业务方向、职能要求、团队配置以及老板方方面面都经历一次刷新,从前的困境烟消云散。

但离职是一把双刃剑,我身边好多个朋友当初忍受不了阿里内部大公司的一些毛病,会议多乱甩锅工程师氛围政治化等等,出来干了几年,好多个最后又回去了,因为发现外面溜了一圈,每个公司都有它的问题,有的问题比阿里更严重,有的则是前景非常灰暗,当然也有一些同学跳槽的很成功,只是比例确实非常非常小,这就是择业择公司的智慧和运气了。也就是说,在这家公司所遇到的让你不爽的问题,在未来的某家公司,迟早还会换个方式让你遇到,今天逃避了这个问题,未来仍然要面对,无论是你转型了管理,还是未来自己开了公司,所有公司的问题不同但又相通,心魔这一关,永远是你的成长功课。

职场是很现实的,在哪个公司都充满了委屈、不平、揪心甚至困惑,做这样的选择一定要慎之又慎,最佳的离职状态是职业到了天花板,公司业务上进入平台期甚至衰减期蛋糕不够分,团队已经不能再给你更大的舞台和空间,技术上管理上不能再上一台阶,想做的事情都做完了而且做得不错,此时需要切换一个新赛道从 0 开始,最可惜的离职状态就是不满意组织内的人和事,且自己的整体实力并没有明显提升的前提下,情绪化的一走了之,可惜是因为这一段灰色的经历,不但不会给你的职业加分,甚至在未来的某个面试中,还会减分,以及会一定程度打乱你的职业节奏。

如果单纯是因为薪资而跳,更要慎重,切记不要逢涨就跳,当你工作满 5 年10年的时候,其实回看工作的头几年,工资是 1 万/月,还是 2 万/月,它很重要,但它不是核心问题,尤其是涨幅只有 20% 30% 的时候,大家如果身边有厉害的前端前辈,可以去问他,他身边薪资过百万的前端,有多少是跳槽高频的,至少在我的周遭,几乎都是很稳定的职业阶段,并且每个阶段都拿到了他要的结果,一个工作经验大于六七年的前端,稳定性足够强,他的下一次跳槽,薪资拿到 6+ 万/月,这些案例我的身边比比皆是,但他可以,不代表你是可以的,因为每个人的成长背景和职业路线都是独一无二的。

确实接触到了离职中的同学,几乎都处于所谓困境中,但原因各不相同,并且部分同学其实没有想清楚这个问题,更多面临的是困难而非困境,眼前的切换属于是冲动决定,情绪占第一位,甚至有的跳槽频率很高很高,后期严重伤害了职业生涯,而成功切换的总是很少数,假如下次离职,可以要找前辈请教二三,不要冲动行事。

舒适区才是最大的困境

我们看抖音,会发现有些人的技术特别娴熟,比如切菜的,比如为快递打包的,工程师的编程技能也一样,如果长时间在某一块重复性训练的话,也会唯手熟尔,比如一个常年做活动页面的同学,比如一个常年做后台表单页面的同学,这些容易重复性的领域很容易造成一种错觉,那就是这块我熟能生巧总是很快搞定,然而却不知不觉的进入到了一个舒适区,有时候会自己意识到,有时候意识不到,带来的后果是往往技术的成长停滞不前,更可怕的是在不知情的情况下度过了多年时光。

所以面对困境未必要离开,处于舒适区也未必要留下,每一个人处于这种状态下,都是很痛苦,如何把痛苦周期缩短,如果找到突破点求变或者坚持,其实都取决于我们对于自己的判断,以及对行业的判断,所有的判断加在一起,再来对每一个问题标注上优先等级,注释上自己力所能及的事项,再列出来推进解决它的详细计划,最终能否迈出去到一个更好的阶段,就取决于执行力了。

说到执行力,再送给各位几个关键词来面对所有的人生困境:脑力、体力、心力、执行力,脑力是分析是智慧是取舍是规划,体力是坚持是强度是执行力度,心力是耐力是决断力是忍受向前的抗击打能力,执行力是使命必达说到做到破釜沉舟的落地程度,这一切都需要你足够主动,足够负责,才能点爆奇迹。

不管是要离职还是继续坚持,最重要的是要走出舒适区,可能我们还只是工作了两三年,没有这种感觉,但凡事要做长远打算,按照自己的节奏去走好每一步。小编为面试的小伙伴们准备了前端面试资料和前端学习思维导图及资料,免费分享给大家,希望在想走出困境或离开舒适区时,对你有帮助。


最最后,送给大家一句话,也是一直在警惕自己的:心不随境转,才是自在人生。祝愿大家走出一个越来越上升越来越增值的职业路线。

由于篇幅问题,需要完整版资料PDF点击这里,免费下载阅读。

查看原文

赞 0 收藏 0 评论 0

李不要熬夜 发布了文章 · 4月12日

面试官的一句话点破了我:三年的前端经验只相当于人家一年的经验

前言

那一天我二十一岁,在我一生的黄金时代。我有好多奢望。我想爱,想吃,还想在一瞬间变成天上半明半暗的云。后来我才知道,生活就是个缓慢受锤的过程,人一天天老下去,奢望也一天天消失,最后变得像挨了锤的牛一样。可是我过二十一岁生日时没有预见到这一点。我觉得自己会永远生猛下去,什么也锤不了我。   ——王小波《黄金时代》

最近和一位老友见面聊天,分开后久久不能释怀,对于他发生的事感触颇深,在写这篇文章时,一度以为是在写自己。接下来的内容是以朋友的视角写的,对最近面试的剖析与反思,请大家耐心看完,没有谁的影子,只有对自己的无奈。

正文

我在一家公司待三年了,最近找工作两个星期了,小公司还好,大公司屡屡碰壁,今天在面试官问我还有什么问题要问他的时候。

我说,我比起哪些优秀者来说,缺了什么?

面试官:你给我的感觉像刚毕业的,你做事各方面态度积极,解决问题也快,但是你掌握的知识是零散的,不是系统的。你解决一个问题之后也没有去反思问题背后的知识,可以做哪些优化。
我:对,我最近也害怕我三年经验只相当于人家一年的经验。
面试官:你已经是这样了。

说出来有点害臊,反思这个东西也是痛苦的,所以可能以前我也逃避去思考,去优化,去构建自己的知识体系,然后一点点的变为平庸。

仔细想来,我也不是特别喜欢编码,但是这是我的事业,我靠它吃饭,我热爱它,我喜欢解决完问题之后的成就感。说来惭愧,都是面对百度编程。

我在一家公司待着,刚毕业的时候接触了一些小程序,后来接触vue,熟悉vue,慢慢的搭建公司的项目,熟练使用vue全家桶,三年来,我只是一个熟练的架子工而已。只是去使用,没有真正地去了解背后的实现过程和原理。像温水里的青蛙,发现自己想拼命跳出锅里的时候已经被煮熟了。

具体来说,我失败主要有下面这几个方面:

  • 基础不扎实:网络基础知识不扎实,深入问到http和网络问题就死翘翘
  • 没有对项目进行复盘总结和衍生:对于做过的项目,没有去深刻地思考怎么优化,自己可不可以实现一个类似的插件等,使用的这个插件是什么原理,只是贪图快速解决问题,就放着不管了
  • 无输入的产出代码:总是在项目中积累经验,使用惯性方法去解决问题,没有主动去系统学习,没有输入的产出代码,最后只是重复性劳动而已
  • 逃避算法:之前我的态度就是,“用不到,看不见,不管,不碰”,结果被卡主脖子

反思自己在求学路上遇到的不少学霸,都是善于归纳,总结,和吸收,举一反三的。这类人不仅是在学习上,就算在就业上,在各个行业都是蛮吃香的,他们学习东西快,上手也快。归纳总结,不断重复训练,吸收为自己的东西。

见贤思齐焉,见不贤而内自省也。

希望各位能把我当做反面教材,不想学的时候能想起我,“看看那个前端,工作三年了还像一年的,不学习就是他这种后果。”

故事结束,回到自身,我想说的是,职场上有两种状态:一种是温水煮青蛙,煮到最后被沸腾掉;另一种就是坐冷板凳,你自己再不起来越坐越冷,最后你的屁股就冰的恨不得粘在哪板凳上,你都不知道。这两个种极端都很可怕,人一定要有自知,不要陷在一个情境里面,包括一个特别好的情境,你也要自知,你要知道那一定不是常态,忧患意识很重要。还有很重要的一点,就是你一定不是说你现在就可以心安理得得享受这一切,你一定要知道,你在最高峰的时候,下面一定是下坡路,你一定要有办法,要么拽住自己,下滑的速度慢一点,要么就是要做好准备,积蓄东西,在我下滑之后,我有办法再弹起来。人生就是在战斗,每时每刻哪怕比如说不管是坐冷板凳的人还是一直在顶峰的人,都是在战斗,停不下来的。

有句话是这么说的:栽一棵树最好的时间是十年前,其次是现在。心灵鸡汤也不多讲,给大家分享干货最为实在,我结合了朋友面试题整理出了几套前端面试题资料,大家点击这里就可以直接免费领取完整版PDF啦!

为参加春招的小伙伴们也准备了一套前端面试题资料,还没有毕业的可以领取呀,未来的发展方向在前端这块肯定是能用上的,校招的小伙伴们看这里!领取完整版PDF!


篇幅有限原因,只展示了部分资料,小编建了个前端学习圈,期待大家的加入哦,希望大家能有所收获,聊得开心!欢迎大家来找小编玩!喜欢这篇文章的小伙伴请留下你们的点赞评论支持小编,小编谢谢大家伙啦。

查看原文

赞 0 收藏 0 评论 0

李不要熬夜 发布了文章 · 4月11日

问的多查的多的vue面试题整理不容错过,快看有没有你被问到的题目!

生命周期钩子函数

Vue 实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模版、挂载Dom -> 渲染、更新 -> 渲染、卸载等一系列过程,我们称这是Vue的生命周期
  • Vue中组件生命周期调用顺序
生命周期描述
beforeCreate组件实例被创建之初,组件的属性生效之前
created组件实例已经完全创建,属性也绑定,但真实dom还没有生成,$el还不可用
beforeMount在挂载开始之前被调用:相关的 render 函数首次被调用
mountedel 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子
beforeUpdate组件数据更新之前调用,发生在虚拟 DOM 打补丁之前
update组件数据更新之后
activitedkeep-alive专属,组件被激活时调用
deadctivatedkeep-alive专属,组件被销毁时调用
beforeDestory组件销毁前调用
destoryed组件销毁后调用

虚拟dom

  • 由于dom操作耗时十分长,且dom对象的体积很大,单个div的dom属性就有294个之多;
  • Virtual DOM 就是用一个原生的 JS 对象去描述一个 DOM 节点,所以它比创建一个 DOM 的代价要小很多。
  • VNode 是对真实 DOM 的一种抽象描述,它的核心定义无非就几个关键属性,标签名、数据、子节点、键值等,其它属性都是用来扩展 VNode 的灵活性以及实现一些特殊 feature 的。由于 VNode 只是用来映射到真实 DOM 的渲染,不需要包含操作 DOM 的方法,因此它是非常轻量和简单的。
  • Virtual DOM到真实的dom需要经过以下过程:VNode 的 create、diff、patch

v-model双向数据绑定原理

vue 双向数据绑定是通过 数据劫持 结合 发布订阅模式 的方式来实现的,也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变; 核心:Object.defineProperty() 方法。

v-model本质上是语法糖,v-model在内部为不同的输入元素使用不同的属性并抛出不同的事件

  • text 和 textarea 元素使用 value 属性和 input 事件
  • checkbox 和 radio 使用 checked 属性和 change 事件
  • select 字段将 value 作为 prop 并将 change 作为事件
<input v-model="sth" /><!-- 二者等价 -->
<input :value="sth" @input="sth = $event.target.value" />
复制代码

computed和watch区别

vue 插槽

设置在自组件内部的插槽像一个盒子,位置由子组件决定,放什么内容由父组件决定。

实现了内容分发,提高了组件自定义的程度,让组件变的更加灵活

  1. 默认插槽:

无需name属性,取子组件肚子里第一个元素节点作为默认插槽。

<!-- 子组件,组件名:child-component -->
<div class="child-page">
    <h1>子页面</h1>
    <slot></slot> <!-- 替换为 <p>hello,world!</p> -->
</div>

<!-- 父组件 -->
<div class="parent-page">
    <child-component>
        <p>hello,world!</p>
    </child-component>
</div>

<!-- 渲染结果 -->
<div class="parent-page">
    <div class="child-page">
        <h1>子页面</h1>
        <p>hello,world!</p>
    </div>
</div>
复制代码
  1. 具名插槽:

在多个插槽的情况下使用,利用name标识插槽。

<!-- 子组件,组件名:child-component -->
<div class="child-page">
    <h1>子页面</h1>
    <slot name="header"></slot>
    <slot></slot>  <!-- 等价于 <slot name="default"></slot> -->
    <slot name="footer"></slot>
</div>

<!-- 父组件 -->
<div class="parent-page">
    <child-component>
        <template v-slot:header>
          <p>头部</p>  
        </template>
        <template v-slot:footer>
          <p>脚部</p>
        </template>
        <p>身体</p>
    </child-component>
</div>

<!-- 渲染结果 -->
<div class="parent-page">
    <div class="child-page">
        <h1>子页面</h1>
        <p>头部</p>
        <p>身体</p>
        <p>脚部</p>
    </div>
</div>
复制代码
  1. 作用域插槽:

子组件给父组件传递数据。

<!-- 子组件,组件名:child-component -->
<div class="child-page">
    <h1>子页面</h1>
    <slot name="header" data="data from child-component."></slot>
</div>

<!-- 父组件 -->
<div class="parent-page">
    <child-component>
        <template v-slot:header="slotProps">
          <p>头部: {{ slotProps.data }}</p>
        </template>
    </child-component>
</div>

<!-- 渲染结果 -->
<div class="parent-page">
    <div class="child-page">
        <h1>子页面</h1>
        <p>头部: data from child-component.</p>
    </div>
</div>
复制代码

Vue与Angular以及React的区别?

Vue与AngularJS的区别

  • Angular采用TypeScript开发, 而Vue可以使用javascript也可以使用TypeScript
  • AngularJS依赖对数据做脏检查,所以Watcher越多越慢;Vue.js使用基于依赖追踪的观察并且使用异步队列更新,所有的数据都是独立触发的。
  • AngularJS社区完善, Vue的学习成本较小

Vue与React的区别

  • vue组件分为全局注册和局部注册,在react中都是通过import相应组件,然后模版中引用;
  • props是可以动态变化的,子组件也实时更新,在react中官方建议props要像纯函数那样,输入输出一致对应,而且不太建议通过props来更改视图;
  • 子组件一般要显示地调用props选项来声明它期待获得的数据。而在react中不必需,另两者都有props校验机制;
  • 每个Vue实例都实现了事件接口,方便父子组件通信,小型项目中不需要引入状态管理机制,而react必需自己实现;
  • vue使用插槽分发内容,使得可以混合父组件的内容与子组件自己的模板;
  • vue多了指令系统,让模版可以实现更丰富的功能,而React只能使用JSX语法;
  • Vue增加的语法糖computedwatch,而在React中需要自己写一套逻辑来实现;
  • react的思路是all in js,通过js来生成html,所以设计了jsx,还有通过js来操作css,社区的styled-component、jss等;而 vue是把html,css,js组合到一起,用各自的处理方式,vue有单文件组件,可以把html、css、js写到一个文件中,html提供了模板引擎来处理。
  • react做的事情很少,很多都交给社区去做,vue很多东西都是内置的,写起来确实方便一些,比如 reduxcombineReducer就对应vuexmodules, 比如reselect就对应vuex的getter和vue组件的computed, vuex的mutation是直接改变的原始数据,而redux的reducer是返回一个全新的state,所以redux结合immutable来优化性能,vue不需要。
  • react是整体的思路的就是函数式,所以推崇纯组件,数据不可变,单向数据流,当然需要双向的地方也可以做到,比如结合redux-form,组件的横向拆分一般是通过高阶组件。而vue是数据可变的,双向绑定,声明式的写法,vue组件的横向拆分很多情况下用mixin

$route$router的区别

  • $route是路由信息对象,包括path,params,hash,query,fullPath,matched,name等路由信息参数。
  • 而$router是路由实例对象包括了路由的跳转方法,钩子函数等。

Vue的SPA 如何优化加载速度

  • 减少入口文件体积
  • 静态资源本地缓存
  • 开启Gzip压缩
  • 使用SSR,nuxt.js

vue项目中的优化

编码阶段

  • 不要在模板里面写过多表达式
  • 尽量减少data中的数据,data中的数据都会增加gettersetter,会收集对应的watcher
  • v-ifv-for不能连用
  • 如果需要使用v-for给每项元素绑定事件时使用事件代理
  • SPA 页面采用keep-alive缓存组件
  • 频繁切换的使用v-show,不频繁切换的使用v-if
  • 循环调用子组件时添加key,key保证唯一
  • 使用路由懒加载、异步组件
  • 防抖、节流
  • 第三方模块按需导入
  • 长列表滚动到可视区域动态加载
  • 图片懒加载

SEO优化

  • 预渲染
  • 服务端渲染SSRnuxt.js

打包优化

  • 压缩代码
  • Tree Shaking/Scope Hoisting
  • 使用cdn加载第三方模块
  • 多线程打包happypack
  • splitChunks抽离公共文件
  • sourceMap优化

用户体验

  • 骨架屏
  • PWA渐进式Web应用,使用多种技术来增强web app的功能,让网页应用呈现和原生应用相似的体验。
还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。

vue有了数据响应式,为何还要diff?

核心原因:粒度

  • React通过setState知道有变化了,但不知道哪里变化了,所以需要通过diff找出变化的地方并更新dom。
  • Vue已经可以通过响应式系统知道哪里发生了变化,但是所有变化都通过响应式会创建大量Watcher,极其消耗性能,因此vue采用的方式是通过响应式系统知道哪个组件发生了变化,然后在组件内部使用diff。这样的中粒度策略,即不会产生大量的Watcher,也使diff的节点减少了,一举两得。

vue模版编译

编译的核心是把 template 模板编译成 render 函数,主要分为如下三个步骤:

  • 生成AST树
  • 优化
  • codegen

MVVM

MVVMModel-View-ViewModel缩写,也就是把MVC中的Controller演变成ViewModelModel层代表数据模型,View代表UI组件,ViewModel是View和Model层的桥梁,数据会绑定到viewModel层并自动将数据渲染到页面中,视图变化的时候会通知viewModel层更新数据。

响应式数据原理(Vue2.x & Vue3.0)

Vue2.x在初始化数据时,会使用Object.defineProperty重新定义data中的所有属性,当页面使用对应属性时,首先会进行依赖收集(收集当前组件的watcher),如果属性发生变化会通知相关依赖进行派发更细(发布订阅模式)。

vue3.0采用es6中的proxy代替Object.defineProperty做数据监听。

Proxy与Object.defineProperty的优劣对比?

Proxy的优势如下:

  • Proxy可以直接监听对象而非属性
  • Proxy可以直接监听数组的变化
  • Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等是Object.defineProperty不具备的
  • Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改
  • Proxy作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利

Object.defineProperty的优势如下:

  • 兼容性好,支持IE9

vue的$nextTick

在下次 DOM 更新循环结束之后执行延迟回调。nextTick主要使用了宏任务和微任务。根据执行环境分别尝试采用

  • Promise
  • MutationObserver
  • setImmediate
  • 如果以上都不行则采用setTimeout

定义了一个异步方法,多次调用nextTick会将方法存入队列中,通过这个异步方法清空当前队列。

vue组件中data为什么必须是一个函数

复用组件的时候,都会返回一份新的data,相当于每个组件实例都有自己私有的数据空间,不会共享同一个data对象。

vue中组件通信的方式

  1. 父传子:props
  2. 子传父:$emitref
  3. 兄弟:EventBus

vue-router

vue-router是vue的官方插件,主要用来管理前端路由。 对于 Vue 这类渐进式前端开发框架,为了构建 SPA(单页面应用),需要引入前端路由系统,这也就是 Vue-Router 存在的意义。前端路由的核心,就在于:改变视图的同时不会向后端发出请求

功能有:

  • 改变URL且不让浏览器向服务器发出请求
  • 检测URL的改变
  • 记录当前页面的状态
  • 可以使用浏览器的前进后退功能
  • URL路径决定页面如何显示

history和hash模式的区别

1. 实现原理

hash 模式和 history 模式都属于浏览器自身的特性,Vue-Router 只是利用了这两个特性(通过调用浏览器提供的接口)来实现前端路由。

2. 对比表格

区别 \ modehashhistory
监听事件hashChangepopstate
缺点# 号不好看子路由刷新404、ie9及以下不兼容
push操作window.location.assignwindow.history.pushState
replace操作window.location.replacewindow.history.replaceState
访问操作window.history.gowindow.history.go
后退操作window.history.go(-1)window.history.go(-1)
向前操作window.history.go(1)window.history.go(1)

3. 关于 popstate 事件监听路由的局限 history对象的 back(), forward() 和 go() 三个等操作会主动触发 popstate 事件,但是 pushState 和 replaceState 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。

4. 关于子路由刷新的解决方式

history模式子路由刷新会404,因此需要后端配合,将未匹配到的路由默认指向html文件

5. 浏览器(环境)兼容处理

history 模式中pushStatereplaceStateHTML5的新特性,在 IE9 下会强行降级使用 hash 模式,非浏览器环境转换成abstract 模式。

6. router-linkrouter-link点击相当于调用$router.push方法去修改url

<router-link> 比起写死的 <a href="..."> 会好一些,理由如下:

  1. 无论是 HTML5 history 模式还是 hash 模式,它的表现行为一致,所以,当你要切换路由模式,或者在 IE9 降级使用 hash 模式,无须作任何变动。
  2. 在 HTML5 history 模式下,router-link 会守卫点击事件,让浏览器不再重新加载页面。
  3. 当你在 HTML5 history 模式下使用 base 选项之后,所有的 to 属性都不需要写(基路径)了。

vue-router路由懒加载

像 vue 这种单页面应用,如果没有路由懒加载,运用 webpack 打包后的文件将会很大,造成进入首页时,需要加载的内容过多,出现较长时间的白屏,运用路由懒加载则可以将页面进行划分,需要的时候才加载页面,可以有效的分担首页所承担的加载压力,减少首页加载用时。

vue 路由懒加载有以下三种方式:

  1. vue 异步组件
  2. ES6 的 import()
  3. webpack 的 require.ensure()

1. vue 异步组件 这种方法主要是使用了 resolve 的异步机制,用 require 代替了 import 实现按需加载

export default new Router({
  routes: [
    {
      path: '/home',',
      component: (resolve) => require(['@/components/home'], resolve),
    },
    {
      path: '/about',',
      component: (resolve) => require(['@/components/about'], resolve),
    },
  ],
})
复制代码

2. ES6 的 import() vue-router 在官网提供了一种方法,可以理解也是为通过 Promise 的 resolve 机制。因为 Promise 函数返回的 Promise 为 resolve 组件本身,而我们又可以使用 import 来导入组件。

export default new Router({
  routes: [
    {
      path: '/home',
      component: () => import('@/components/home'),
    },
    {
      path: '/about',
      component: () => import('@/components/home'),
    },
  ],
})
复制代码

1. webpack 的 require.ensure() 这种模式可以通过参数中的 webpackChunkName 将 js 分开打包。

export default new Router({
  routes: [
    {
      path: '/home',
      component: (resolve) => require.ensure([], () => resolve(require('@/components/home')), 'home'),
    },
    {
      path: '/about',
      component: (resolve) => require.ensure([], () => resolve(require('@/components/about')), 'about'),
    },
  ],
})
复制代码


篇幅有限,还有没有发出来的题目都前端面试题资料里,需要完整PDF资料的小伙伴只需要点击这里就可以免费获取!

有被问到题目的小伙伴们评论区可以聊聊你是怎么回答的哦,没有被问到的更要多看熟记,说不定就会碰到啦~这篇文章对你有帮助请评论点赞支持一波,谢谢!

查看原文

赞 1 收藏 1 评论 0

李不要熬夜 发布了文章 · 4月9日

JavaScript的事件捕获和事件冒泡

事件冒泡机制

让我们先看一段代码

<!DOCTYPE html>
<html>
<head>
    <title>Event Capture and Propagation</title>
</head>
<body>
    <div id="parent" style="width:120px; height:120px; background:#090; padding:60px">
        <div id="child" style="width:120px; height:120px; background:#0CC"></div>
    </div>
    <script type="text/javascript">
    function alertName(e) {
        alert(this.id);
    }

    document.getElementById('child').onclick = alertName;
    document.getElementById('parent').onclick = alertName;
    </script>
</body>
</html>

我们在外层div和内层div的onclick事件上都绑定了alertName(e)方法,打开浏览器试试。当我们点击外层绿色部分,外层div的事件响应了;让我们点击内层蓝色部分,内层div的事件先响应,然后外层div的事件再响应。

所以我们很容易得出结论,浏览器是先找到当前被点击的元素,如果该元素上注册了onclick事件则响应;然后浏览器继续寻找被点击元素的父元素,如果其也注册了onclick事件,则该事件也响应,然后再继续向上层寻找。这就是JS的事件冒泡机制。我们换个浏览器试试,目前版本的Chrome, Safari, Firefox和IE都是同样的结果。

事件处理模型

其实事件处理有两种模型。一种叫冒泡型,也就是上面我们看到的例子,顺序是从里到外”div” -> “body” -> “html” -> “document” -> “window”。还有一种叫捕获型,顺序正好颠倒。IE是支持冒泡型,据说历史上的Netscape是支持捕获型。那我们主流的W3C标准呢?答案是都支持,默认是冒泡型。这要怎么理解?我们先来看下W3C DOM事件流的过程:

Event Capture Process

当一个事件发生时,先进入捕获阶段,也就是从最外层开始,依次向里传播事件,一直到触发事件的目标元素(上例中的div child)。随后再进入冒泡阶段,从触发事件的元素开始,依次向外传播,直到最外层元素。那么为什么我们在前面的例子中看不到事件捕获的阶段呢?这里我们要先说另外一个概念。

添加事件绑定

上面的例子是将onclick事件属性设置为alertName(e)方法,这样的事件绑定方法一般不推荐,因为会覆盖之前所绑定的其他函数。我们建议采用addEventListener方法。我们将上例中绑定事件的代码做如下改动:

    child = document.getElementById('child');
    parent = document.getElementById('parent');
    child.addEventListener("click", alertName);    // 注意,这里要写click,不是onclick
    parent.addEventListener("click", alertName);

addEventListener方法的第一个参数是事件名称,第二个参数是响应函数。打开浏览器点击下,是不是跟之前用onclick绑定的效果一样啊?那这么做的好处是什么呢?我们试试在上段代码的后面添加下面的代码:

    function alertAgain(e) {
        alert(this.id + ", again")
    }
    child.addEventListener("click", alertAgain);

再次点击内层蓝色div部分,你会看到该元素上绑定的两个点击事件方法都响应了,响应的顺序同代码中绑定的顺序一致。所以,使用addEventListener就不会覆盖掉之前已绑定的方法。

捕获阶段事件响应

介绍完addEventListener方法的作用,就要说下,这个方法其实有第三个参数的。参数名称叫useCapture,类型是布尔型。从名称里估计大家就猜到了,如果将它设为true的话,事件处理将会是捕获型的,也就是事件将会在捕获阶段响应,而不是冒泡阶段响应。我们将上面例子中addEventListener部分改动一下:

    child = document.getElementById('child');
    parent = document.getElementById('parent');
    child.addEventListener("click", alertName);    // 注意,这里要写click,不是onclick
    parent.addEventListener("click", alertName, true);
    parent.addEventListener("click", alertAgain);

测试一下,当点击内层蓝色部分时,外层div的alertName方法先响应了,然后是内层div的alertName方法,最后是外层div的alertAgain方法。也就是说,语句parent.addEventListener("click", alertName, true);注册的事件,被提前到捕获阶段响应,即最外层的响应会先被执行。

注意了,IE8之前是不支持addEventListener的,所以只能用attachEvent方法来添加事件绑定,代码如下:

    child = document.getElementById('child');
    parent = document.getElementById('parent');
    child.attachEvent("onclick", alertName);    // 注意,这里要写onclick
    parent.attachEvent("onclick", alertName);

attachEvent方法没有第三个参数,所以说老版本的IE只支持事件冒泡,而不支持事件捕获。即使是在后续的IE版本中可以调用addEventListener方法,但大家也要注意,里面的”this指针”的指向跟W3C标准浏览器不一样。父元素在冒泡过程中响应时,”this指针”将指向window对象,所以上面的例子在IE高版本中测试时,第二个”alert”内容会是undefined。哎,万恶的IE!

阻止事件传播

有时候,我们希望事件被响应一次就停了,不希望子元素(在捕获过程中)或者父元素(在冒泡过程中),再次响应同样的事件,这个要怎么做呢?我们还是看例子。这次我们要改动的是alertName方法。

    function alertName(e) {
        alert(this.id);
        stopBubble(e);
    }

    function stopBubble(e) {
        if (e && e.stopPropagation)
            e.stopPropagation();    // W3C
        else
            window.event.cancelBubble = true;  // Old IE
    }

event对象上的stopPropagation方法可以阻止事件沿父节点(冒泡阶段)或子节点(捕获阶段)进一步传播。所以经过上述改动后,我们发现点击事件只会被响应一次。注意了,如果当前处理响应的节点上还有其他事件响应函数,那还是会被调用的,stopPropagation只是阻止事件往别的节点传播。万恶的IE在以前的版本中(应该是IE8往上)不支持stopPropagation,所以要用window.event.cancelBubble=true来阻止事件传播。

总结下,我们介绍了事件处理模型中的事件捕获和事件冒泡。在W3C标准中,事件响应时先进入捕获阶段从最外层元素向内传播,一直到触发事件的目标元素;然后进入冒泡阶段从目标元素向外传播,一直到最外层元素。我们可以通过addEventListener方法的第三个参数来决定事件是由捕获阶段处理还是冒泡阶段处理。而事件对象event上的stopPropagation方法可以阻止事件再次传播。说到这里,事件处理模型到底有什么意义呢?个人认为有两个好处:

  1. 首先如果父元素里有很多子元素都要绑定同一个事件,而且事件的处理方法很类似,那我们完全可以只在父元素中绑定该事件作统一处理,这样代码会简洁很多。
  2. 事件发生时,当前元素及其传播路径上的元素都要对该事件作不同的处理,那我们可以在各自元素上绑定对于该事件各自的处理方式。这要,当事件发生时,每个元素都只管处理自己的响应函数,有点像责任链模式。

最后,要说下两个注意点:

  1. blur, focus, load, unload事件是不传播的。
  2. stopPropagation不能阻止对象的默认行为传播,比如”submit”类型按钮的提交。如果你还是想做到,那就要阻止事件的默认行为。方法如下:
    <a href="http://www.baidu.com/" id="link">Baidu</a>
    <script type="text/javascript">
    function stopDefault(e) {
        if (e && e.preventDefault)
            e.preventDefault();    // W3C
        else
            window.event.returnValue = false;  // Old IE
    }

    link = document.getElementById('link');
    link.onclick = function(e) {
       alert("Prevent open the link of Baidu");
       stopDefault(e);
    }
    </script>

event对象上的preventDefault方法可以阻止事件的默认处理行为,比如在上例中,点链接时就不会打开百度页面。

查看原文

赞 0 收藏 0 评论 0

李不要熬夜 发布了文章 · 4月9日

JavaScript 事件委托详解

基本概念

事件委托,通俗地来讲,就是把一个元素响应事件(click、keydown......)的函数委托到另一个元素;

一般来讲,会把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,当事件响应到需要绑定的元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然后在外层元素上去执行函数。

举个例子,比如一个宿舍的同学同时快递到了,一种方法就是他们都傻傻地一个个去领取,还有一种方法就是把这件事情委托给宿舍长,让一个人出去拿好所有快递,然后再根据收件人一一分发给每个宿舍同学;

在这里,取快递就是一个事件,每个同学指的是需要响应事件的 DOM 元素,而出去统一领取快递的宿舍长就是代理的元素,所以真正绑定事件的是这个元素,按照收件人分发快递的过程就是在事件执行中,需要判断当前响应的事件应该匹配到被代理元素中的哪一个或者哪几个。

事件冒泡

前面提到 DOM 中事件委托的实现是利用事件冒泡的机制,那么事件冒泡是什么呢?

在 document.addEventListener 的时候我们可以设置事件模型:事件冒泡、事件捕获,一般来说都是用事件冒泡的模型;
image.png

如上图所示,事件模型是指分为三个阶段:

  • 捕获阶段:在事件冒泡的模型中,捕获阶段不会响应任何事件;
  • 目标阶段:目标阶段就是指事件响应到触发事件的最底层元素上;
  • 冒泡阶段:冒泡阶段就是事件的触发响应会从最底层目标一层层地向外到最外层(根节点),事件代理即是利用事件冒泡的机制把里层所需要响应的事件绑定到外层;### 事件

委托的优点

1. 减少内存消耗

试想一下,若果我们有一个列表,列表之中有大量的列表项,我们需要在点击列表项的时候响应一个事件;

<ul id="list">
  <li>item 1</li>
  <li>item 2</li>
  <li>item 3</li>
  ......
  <li>item n</li>
</ul>
// ...... 代表中间还有未知数个 li

如果给每个列表项一一都绑定一个函数,那对于内存消耗是非常大的,效率上需要消耗很多性能;

因此,比较好的方法就是把这个点击事件绑定到他的父层,也就是 ul 上,然后在执行事件的时候再去匹配判断目标元素;

所以事件委托可以减少大量的内存消耗,节约效率。

2. 动态绑定事件

比如上述的例子中列表项就几个,我们给每个列表项都绑定了事件;

在很多时候,我们需要通过 AJAX 或者用户操作动态的增加或者去除列表项元素,那么在每一次改变的时候都需要重新给新增的元素绑定事件,给即将删去的元素解绑事件;

如果用了事件委托就没有这种麻烦了,因为事件是绑定在父层的,和目标元素的增减是没有关系的,执行到目标元素是在真正响应执行事件函数的过程中去匹配的;

所以使用事件在动态绑定事件的情况下是可以减少很多重复工作的。

jQuery 中的事件委托

jQuery 中的事件委托相信很多人都用过,它主要这几种方法来实现:

  • **$.on**: 基本用法: $('.parent').on('click', 'a', function () { console.log('click event on tag a'); }),它是 .parent 元素之下的 a 元素的事件代理到 $('.parent') 之上,只要在这个元素上有点击事件,就会自动寻找到 .parent 元素下的 a 元素,然后响应事件;
  • **$.delegate**: 基本用法: $('.parent').delegate('a', 'click', function () { console.log('click event on tag a'); }),同上,并且还有相对应的 $.delegate 来删除代理的事件;
  • **$.live**: 基本使用方法: $('a', $('.parent')).live('click', function () { console.log('click event on tag a'); }),同上,然而如果没有传入父层元素 $(.parent),那事件会默认委托到 $(document) 上;(已废除)

实现功能

基本实现

比如我们有这样的一个 HTML 片段:

<ul id="list">
  <li>item 1</li>
  <li>item 2</li>
  <li>item 3</li>
  ......
  <li>item n</li>
</ul>
// ...... 代表中间还有未知数个 li

我们来实现把 #list 下的 li 元素的事件代理委托到它的父层元素也就是 #list 上:

// 给父层元素绑定事件
document.getElementById('list').addEventListener('click', function (e) {
  // 兼容性处理
  var event = e || window.event;
  var target = event.target || event.srcElement;
  // 判断是否匹配目标元素
  if (target.nodeName.toLocaleLowerCase === 'li') {
    console.log('the content is: ', target.innerHTML);
  }
});

在上述代码中, target 元素则是在 #list 元素之下具体被点击的元素,然后通过判断 target 的一些属性(比如:nodeName,id 等等)可以更精确地匹配到某一类 #list li 元素之上;

使用 Element.matches 精确匹配

如果改变下 HTML 成:

<ul id="list">
  <li className="class-1">item 1</li>
  <li>item 2</li>
  <li className="class-1">item 3</li>
  ......
  <li>item n</li>
</ul>
// ...... 代表中间还有未知数个 li

这里,我们想把 #list 元素下的 li 元素(并且它的 class 为 class-1)的点击事件委托代理到 #list 之上;

如果通过上述的方法我们还需要在 if (target.nodeName.toLocaleLowerCase === 'li') 判断之中在加入一个判断 target.nodeName.className === 'class-1'

但是如果想像 CSS 选择其般做更加灵活的匹配的话,上面的判断未免就太多了,并且很难做到灵活性,这里可以使用 Element.matches API 来匹配;

Element.matches API 的基本使用方法: Element.matches(selectorString),selectorString 既是 CSS 那样的选择器规则,比如本例中可以使用 target.matches('li.class-1'),他会返回一个布尔值,如果 target 元素是标签 li 并且它的类是 class-1 ,那么就会返回 true,否则返回 false;

当然它的兼容性还有一些问题,需要 IE9 及以上的现代化浏览器版本;

我们可以使用 Polyfill 来解决兼容性上的问题:

if (!Element.prototype.matches) {
  Element.prototype.matches =
    Element.prototype.matchesSelector ||
    Element.prototype.mozMatchesSelector ||
    Element.prototype.msMatchesSelector ||
    Element.prototype.oMatchesSelector ||
    Element.prototype.webkitMatchesSelector ||
    function(s) {
      var matches = (this.document || this.ownerDocument).querySelectorAll(s),
        i = matches.length;
      while (--i >= 0 && matches.item(i) !== this) {}
      return i > -1;            
    };
}

加上 Element.matches 之后就可以来实现我们的需求了:

if (!Element.prototype.matches) {
  Element.prototype.matches =
    Element.prototype.matchesSelector ||
    Element.prototype.mozMatchesSelector ||
    Element.prototype.msMatchesSelector ||
    Element.prototype.oMatchesSelector ||
    Element.prototype.webkitMatchesSelector ||
    function(s) {
      var matches = (this.document || this.ownerDocument).querySelectorAll(s),
        i = matches.length;
      while (--i >= 0 && matches.item(i) !== this) {}
      return i > -1;            
    };
}
document.getElementById('list').addEventListener('click', function (e) {
  // 兼容性处理
  var event = e || window.event;
  var target = event.target || event.srcElement;
  if (target.matches('li.class-1')) {
    console.log('the content is: ', target.innerHTML);
  }
});

函数封装

在应对更多场景上我们可以把事件代理的功能封装成一个公用函数,这样就可以重复利用了。
结合上面的例子来实现一个函数 eventDelegate,它接受四个参数:

  • [String] 一个选择器字符串用于过滤需要实现代理的父层元素,既事件需要被真正绑定之上;
  • [String] 一个选择器字符串用于过滤触发事件的选择器元素的后代,既我们需要被代理事件的元素;
  • [String] 一个或多个用空格分隔的事件类型和可选的命名空间,如 click 或 keydown.click ;
  • [Function] 需要代理事件响应的函数;

**这里就有几个关键点:

  • 对于父层代理的元素可能有多个,需要一一绑定事件;
  • 对于绑定的事件类型可能有多个,需要一一绑定事件;
  • 在处理匹配被代理的元素之中需要考虑到兼容性问题;
  • 在执行所绑定的函数的时候需要传入正确的参数以及考虑到 this 的问题;
function eventDelegate (parentSelector, targetSelector, events, foo) {
  // 触发执行的函数
  function triFunction (e) {
    // 兼容性处理
    var event = e || window.event;
    var target = event.target || event.srcElement;
    // 处理 matches 的兼容性
    if (!Element.prototype.matches) {
      Element.prototype.matches =
        Element.prototype.matchesSelector ||
        Element.prototype.mozMatchesSelector ||
        Element.prototype.msMatchesSelector ||
        Element.prototype.oMatchesSelector ||
        Element.prototype.webkitMatchesSelector ||
        function(s) {
          var matches = (this.document || this.ownerDocument).querySelectorAll(s),
            i = matches.length;
          while (--i >= 0 && matches.item(i) !== this) {}
          return i > -1;            
        };
    }
    // 判断是否匹配到我们所需要的元素上
    if (target.matches(targetSelector)) {
      // 执行绑定的函数,注意 this
      foo.call(target, Array.prototype.slice.call(arguments));
    }
  }
  // 如果有多个事件的话需要全部一一绑定事件
  events.split('.').forEach(function (evt) {
    // 多个父层元素的话也需要一一绑定
    Array.prototype.slice.call(document.querySelectorAll(parentSelector)).forEach(function ($p) {
      $p.addEventListener(evt, triFunction);
    });
  });
}

优化

当被代理的元素不是目标元素的时候,既选择器 targetSelector 所指向的元素不是 event.target (事件目标阶段指向的元素)的时候,这时候就需要层层遍历 event.target 的 parentNode 去匹配 targetSelector 了,直到 parentSelector。

比如:

<ul id="list">
  <li><span>item 1</span></li>
  <li><span>item 2</span></li>
</ul>

还是把 li 的事件代理到 #list 之上,但这时候会发现 event.target 指向的是 li span,因此需要层层遍历外层元素去匹配,直到到代理事件的函数,我们可以用 event.currentTarget 来获取到代理事件的函数;

完整函数:

function eventDelegate (parentSelector, targetSelector, events, foo) {
  // 触发执行的函数
  function triFunction (e) {
    // 兼容性处理
    var event = e || window.event;

    // 获取到目标阶段指向的元素
    var target = event.target || event.srcElement;

    // 获取到代理事件的函数
    var currentTarget = event.currentTarget;

    // 处理 matches 的兼容性
    if (!Element.prototype.matches) {
      Element.prototype.matches =
        Element.prototype.matchesSelector ||
        Element.prototype.mozMatchesSelector ||
        Element.prototype.msMatchesSelector ||
        Element.prototype.oMatchesSelector ||
        Element.prototype.webkitMatchesSelector ||
        function(s) {
          var matches = (this.document || this.ownerDocument).querySelectorAll(s),
            i = matches.length;
          while (--i >= 0 && matches.item(i) !== this) {}
          return i > -1;            
        };
    }

    // 遍历外层并且匹配
    while (target !== currentTarget) {
      // 判断是否匹配到我们所需要的元素上
      if (target.matches(targetSelector)) {
        var sTarget = target;
        // 执行绑定的函数,注意 this
        foo.call(sTarget, Array.prototype.slice.call(arguments))
      }

      target = target.parentNode;
    }
  }

  // 如果有多个事件的话需要全部一一绑定事件
  events.split('.').forEach(function (evt) {
    // 多个父层元素的话也需要一一绑定
    Array.prototype.slice.call(document.querySelectorAll(parentSelector)).forEach(function ($p) {
      $p.addEventListener(evt, triFunction);
    });
  });
}

使用函数:

eventDelegate('#list', 'li', 'click', function () { console.log(this); });

点击后可以看到 console 出了 #list li 元素对象;

局限性

当然,事件委托也是有一定局限性的;

比如 focus、blur 之类的事件本身没有事件冒泡机制,所以无法委托;

mousemove、mouseout 这样的事件,虽然有事件冒泡,但是只能不断通过位置去计算定位,对性能消耗高,因此也是不适合于事件委托的;

文章有不对地方欢迎指正!

查看原文

赞 0 收藏 0 评论 0

李不要熬夜 发布了文章 · 4月8日

JavaScript系列之this是什么

本章将专门介绍与执行上下文创建阶段直接相关的最后一个细节——this是什么?以及它的指向到底是什么。

了解this

也许你在其他面向对象的编程语言曾经看过this,也知道它会指向某个构造器(constructor)所建立的对象。但事实上在JavaScript里面,this所代表的不仅仅是那个被建立的对象。

先来看看ECMAScript 标准规范对this 的定义:

「The this keyword evaluates to the value of the ThisBinding of the current execution context.」
「this 这个关键字代表的值为当前执行上下文的ThisBinding。」

然后再来看看MDN 对this 的定义:

「In most cases, the value of this is determined by how a function is called.」
「在大多数的情况下,this 其值取决于函数的调用方式。」

好,如果上面两行就看得懂的话那么就不用再往下看了,Congratulations!

...... 我想应该不会,至少我光看这两行还是不懂。

先来看个例子吧:

var getGender = function() {
    return people1.gender;
};

var people1 = {
    gender: 'female',
    getGender: getGender
};

var people2 = {
    gender: 'male',
    getGender: getGender
};

console.log(people1.getGender());    // female
console.log(people2.getGender());    // female

what?怎么people2变性了呢,这不是我想要的结果啊,为什么呢?

因为getGender()返回(return)写死了people1.gender的关系,结果自然是'female'。

那么,如果我们把getGender稍改一下:

var getGender = function() {
    return this.gender;
};

这个时候,你应该会分别得到femalemale两种结果。

所以回到前面讲的重点,从这个例子可以看出,即便people1people2getGender方法参照的都是同一个getGender function,但由于调用的对象不同,所以执行的结果也会不同

现在我们知道了第一个重点,this实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数的调用方式。如何的区分this呢?

this到底是谁

看完上面的例子,还是有点似懂非懂吧?那接下来我们来看看不同的调用方式对 this 值的影响。

情况一:全局对象&调用普通函数

在全局环境中,this 指向全局对象,在浏览器中,它就是 window 对象。下面的示例中,无论是否是在严格模式下,this 都是指向全局对象。

var x = 1

console.log(this.x)               // 1
console.log(this.x === x)         // true
console.log(this === window)      // true

如果普通函数是在全局环境中被调用,在非严格模式下,普通函数中 this 也指向全局对象;如果是在严格模式下,this 将会是 undefined。ES5 为了使 JavaScript 运行在更有限制性的环境而添加了严格模式,严格模式为了消除安全隐患,禁止了 this 关键字指向全局对象。

var x = 1

function fn() {
    console.log(this);   // Window 全局对象
    console.log(this.x);  // 1
}

fn();      

使用严格模式后:

"use strict"     // 使用严格模式
var x = 1

function fn() {
    console.log(this);   // undefined
    console.log(this.x);  // 报错 "Cannot read property 'x' of undefined",因为此时 this 是 undefined
}

fn();  

情况二:作为对象方法的调用

我们知道,在对象里的值如果是原生值(primitive type;例如,字符串、数值、布尔值),我们会把这个新建立的东西称为「属性(property)」;如果对象里面的值是函数(function)的话,我们则会把这个新建立的东西称为「方法(method)」。

如果函数作为对象的一个方法时,并且作为对象的一个方法被调用时,函数中的this指向这个上一级对象

var x = 1
var obj = {
    x: 2,
    fn: function() {
        console.log(this);    
        console.log(this.x);
    }
}

obj.fn()     

// obj.fn()结果打印出;
// Object {x: 2, fn: function}
// 2

var a = obj.fn
a()   

// a()结果打印出:   
// Window 全局对象
// 1

在上面的例子中,直接运行 obj.fn() ,调用该函数的上一级对象是 obj,所以 this 指向 obj,得到 this.x 的值是 2;之后我们将 fn 方法首先赋值给变量 a,a 运行在全局环境中,所以此时 this 指向全局对象Window,得到 this.x 为 1。

我们再来看一个例子,如果函数被多个对象嵌套调用,this 会指向什么。

var x = 1
var obj = {
  x: 2,
  y: {
    x: 3,
    fn: function() {
      console.log(this);   // Object {x: 3, fn: function}
      console.log(this.x);   // 3
    }
  }
}

obj.y.fn();      

为什么结果不是 2 呢,因为在这种情况下记住一句话:this 始终会指向直接调用函数的上一级对象,即 y,上面例子实际执行的是下面的代码。

var y = {
  x: 3,
  fn: function() {
    console.log(this);   // Object {x: 3, fn: function}
    console.log(this.x);   // 3
  }
}

var x = 1
var obj = {
  x: 2,
  y: y
}

obj.y.fn();    

对象可以嵌套,函数也可以,如果函数嵌套,this 会有变化吗?我们通过下面代码来探讨一下。

var obj = {
    y: function() {
        console.log(this === obj);   // true
        console.log(this);   // Object {y: function}
        fn();

        function fn() {
            console.log(this === obj);   // false
            console.log(this);   // Window 全局对象
        }
    }
}

obj.y();  

在函数 y 中,this 指向了调用它的上一级对象 obj,这是没有问题的。但是在嵌套函数 fn 中,this 并不指向 obj。嵌套的函数不会从调用它的函数中继承 this,当嵌套函数作为函数调用时,其 this 值在非严格模式下指向全局对象,在严格模式是 undefined,所以上面例子实际执行的是下面的代码。

function fn() {
    console.log(this === obj);   // false
    console.log(this);   // Window 全局对象
}

var obj = {
    y: function() {
        console.log(this === obj);   // true
        console.log(this);   // Object {y: function}
        fn();
    }
}

obj.y();  

情况三:作为构造函数调用

我们可以使用 new 关键字,通过构造函数生成一个实例对象。此时,this 便指向这个新对象

var x = 1;

function Fn() {
   this.x = 2;
    console.log(this);  // Fn {x: 2}
}

var obj = new Fn();   // obj和Fn(..)调用中的this进行绑定
console.log(obj.x)   // 2

使用new来调用Fn(..)时,会构造一个新对象并把它(obj)绑定到Fn(..)调用中的this。还有值得一提的是,如果构造函数返回了非引用类型(string,number,boolean,null,undefined),this 仍然指向实例化的新对象。

var x = 1

function Fn() {
  this.x = 2

  return {
    x: 3
  }
}

var a = new Fn()

console.log(a.x)      // 3

因为Fn()返回(return)的是一个对象(引用类型),this 会指向这个return的对象。如果return的是一个非引用类型的值呢?

var x = 1

function Fn() {
  this.x = 2

  return 3
}

var a = new Fn()

console.log(a.x)      // 2

情况四:call 和 apply 方法调用

如果你想改变 this 的指向,可以使用 call 或 apply 方法。它们的第一个参数都是指定函数运行时其中的this指向。如果第一个参数不传(参数为空)或者传 null 、undefined,默认 this 指向全局对象(非严格模式)或 undefined(严格模式)。

var x = 1;

var obj = {
  x: 2
}

function fn() {
    console.log(this);
    console.log(this.x);
}

fn.call(obj)
// Object {x: 2}
// 2

fn.apply(obj)     
// Object {x: 2}
// 2

fn.call()         
// Window 全局对象
// 1

fn.apply(null)    
// Window 全局对象
// 1

fn.call(undefined)    
// Window 全局对象
// 1

使用 call 和 apply 时,如果给 this 传的不是对象,JavaScript 会使用相关构造函数将其转化为对象,比如传 number 类型,会进行new Number()操作,如传 string 类型,会进行new String()操作,如传 boolean 类型,会进行new Boolean()操作。

function fn() {
  console.log(Object.prototype.toString.call(this))
}

fn.call('love')      // [object String]
fn.apply(1)          // [object Number]
fn.call(true)          // [object Boolean]

call 和 apply 的区别在于,call 的第二个及后续参数是一个参数列表,apply 的第二个参数是数组。参数列表和参数数组都将作为函数的参数进行执行。

var x = 1

var obj = {
  x: 2
}

function Sum(y, z) {
  console.log(this.x + y + z)
}

Sum.call(obj, 3, 4)       // 9
Sum.apply(obj, [3, 4])    // 9

情况五:bind 方法调用

调用 f.bind(someObject) 会创建一个与 f 具有相同函数体和作用域的函数,但是在这个新函数中,新函数的 this 会永久的指向 bind 传入的第一个参数,无论这个函数是如何被调用的。

var x = 1

var obj1 = {
    x: 2
};
var obj2 = {
    x: 3
};

function fn() {
    console.log(this);
    console.log(this.x);
};

var a = fn.bind(obj1);
var b = a.bind(obj2);

fn();
// Window 全局对象
// 1

a();
// Object {x: 2}
// 2

b();
// Object {x: 2}
// 2

a.call(obj2);
// Object {x: 2}
// 2

在上面的例子中,虽然我们尝试给函数 a 重新指定 this 的指向,但是它依旧指向第一次 bind 传入的对象,即使是使用 call 或 apply 方法也不能改变这一事实,即永久的指向 bind 传入的第一次参数。

情况六:箭头函数中this指向

值得一提的是,从ES6 开始新增了箭头函数,先来看看MDN 上对箭头函数的说明

An arrow function expression has a shorter syntax than a function expression and does notbind its ownthis,arguments,super, ornew.target. Arrow functions are always anonymous. These function expressions are best suited for non-method functions, and they cannot be used as constructors.

这里已经清楚了说明了,箭头函数没有自己的this绑定。箭头函数中使用的this,其实是直接包含它的那个函数或函数表达式中的this。在前面情况二中函数嵌套函数的例子中,被嵌套的函数不会继承上层函数的 this,如果使用箭头函数,会发生什么变化呢?

var obj = {
  y: function() {
        console.log(this === obj);   // true
        console.log(this);           // Object {y: function}

      var fn = () => {
          console.log(this === obj);   // true
          console.log(this);           // Object {y: function}
      }
      fn();
  }
}

obj.y() 

和普通函数不一样,箭头函数中的 this 指向了 obj,这是因为它从上一层的函数中继承了 this,你可以理解为箭头函数修正了 this 的指向。所以箭头函数的this不是调用的时候决定的,而是在定义的时候处在的对象就是它的this

换句话说,箭头函数的this看外层的是否有函数,如果有,外层函数的this就是内部箭头函数的this,如果没有,则this是window

var obj = {
  y: () => {
        console.log(this === obj);   // false
        console.log(this);           // Window 全局对象 

      var fn = () => {
          console.log(this === obj);   // false
          console.log(this);           // Window 全局对象 
      }
      fn();
  }
}

obj.y() 

上例中,虽然存在两个箭头函数,其实this取决于最外层的箭头函数,由于obj是个对象而非函数,所以this指向为Window全局对象。

同 bind 一样,箭头函数也很“顽固”,我们无法通过 call 和 apply 来改变 this 的指向,即传入的第一个参数被忽略

var x = 1
var obj = {
    x: 2
}

var a = () => {
    console.log(this.x)
    console.log(this)
}

a.call(obj)       
// 1
// Window 全局对象

a.apply(obj)      
// 1
// Window 全局对象

上面的文字描述过多可能有点干涩,那么就看以下的这张流程图吧,我觉得这个图总结的很好,图中的流程只针对于单个规则。

image.png

小结

本篇文章介绍了 this 指向的几种情况,不同的运行环境和调用方式都会对 this 产生影响。总的来说,函数 this 的指向取决于当前调用该函数的对象,也就是执行时的对象。在这一节中,你需要掌握:

  • this 指向全局对象的情况;
  • 严格模式和非严格模式下 this 的区别;
  • 函数作为对象的方法调用时 this 指向的几种情况;
  • 作为构造函数时 this 的指向,以及是否 return 的区别;
  • 使用 call 和 apply 改变调用函数的对象;
  • bind 创建的函数中 this 的指向;
  • 箭头函数中的 this 指向。
查看原文

赞 0 收藏 0 评论 0

李不要熬夜 发布了文章 · 4月8日

JS事件绑定的三种方法

前言

在这个框架盛行的时代,可能很多人已经忘记了JS一些基础的方法。虽然说现在在项目都(应该)很少用 原生JS写代码了,但我听说过这样一句话, "对底层知识了解得越深,能达到得水平也就越高。"所以JS原生API还是要熟悉的。 这一篇总结了基础的JS事件绑定,介绍了三种绑定事件的方法。

  • 在 HTML 中直接绑定
  • 在 Javascript 中绑定
  • 绑定事件监听函数 addEventListener()

方法

1. 在 HTML 中直接指定

例如鼠标单击事件 onclick 、双击事件 onmouseover 、鼠标移入移出事件 onmouseover 、onmouseout 。又可分为两种。 ① 直接写在 HTML 的属性中

<button onclick="alert('hello world')">Click</button>

② 在 HTML 中自定义函数

<button onclick="func()">Click</button>
<script type="text/javascript">
  var func = () => {
    alert('hello world')
  };
</script>

2、在 Javascript 中 绑定

第一种方法将JS事件和HTML标签写在一起,不利于项目的管理和开发。为了使代码结构清晰合理,按照不同功能将其分离将提高效率。

<button id="btn">Click</button>
<script type="text/javascript">
  document.getElementById('btn').onclick = func; // 这里要写上括号,否则会出现意想不到的结果,可以去试试
  function func() {
    alert('hello world');
  }
</script>

3. 绑定事件监听函数 addEventListenr()

第二种方法比第一种好,但也有不足之处。一般一个点击事件上有时候不止触发一个事件。一种设想是把 func() 函数再套一层函数,比如把定义的函数 a() 和 b() 放在 func() 中。但是这样未免太过烦琐了,于是使用 addEventListenr()

(1) 语法

target.addEventListener(type, listener, options);

① target 是DOM 操作获得的对象

② type 指事件类型的字符串。例如 clickmouseout

③ listener 在这里指当事件出发的时候执行的函数。可直接写函数名,也可直接定义函数然后用引号括起来。

④ options 可选。具体见参考链接。

(2) 实例

<button id="btn">Click</button>
<script type="text/javascript">
  const dom = document.getElementById('btn');
  dom.addEventListener('click', func1);
  dom.addEventListener('mouseout', func2);
  function func1() {
    alert('hello')
  };
  function func2() {
    alert('world')
  }; 
  // 鼠标点击事件和移开鼠标事件都被执行,分别输出 hello、world
</script>
查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 12 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 3月4日
个人主页被 896 人浏览