高级前端软件工程师知识整理之基础篇(二)

雨点打透心脏的1/2处 2021-09-18 17:56 474阅读 0赞

6. 怎么实现对象的浅拷贝和深拷贝?

对象属于引用类型,保存在堆内存中。

浅拷贝是指简单的赋值,实际上是复制该对象指向的指针地址,因此一个对象改变时,另一个的对象也会跟着改变。示例:

  1. var obj = {
  2. name: 'John',
  3. age: 30
  4. }
  5. var obj2 = obj;
  6. obj2.sex = 'man';
  7. console.log(obj); //Object {name: "John", age: 30, sex: 'man'}
  8. console.log(obj2); //Object {name: "John", age: 30, sex: 'man'}

深拷贝是指创建一个新的内存块存储新对象,并且新对象的值与被拷贝对象一样,两个对象是相互独立没有关联的。

数组对象的深拷贝可以使用slice()函数:

  1. var arr = ['a', 'b', 'c'];
  2. var arrCopy = arr.slice();
  3. arrCopy[0] = 'test'
  4. console.log(arr); // ["a", "b", "c"]
  5. console.log(arrCopy); // ["test", "b", "c"]

带属性对象的深拷贝可以使用遍历对象的属性(for key in object)并赋给新对象,示例:

  1. var obj = {
  2. name: 'John',
  3. age: 30
  4. }
  5. var deepCopy = function (source) {
  6. var result = {};
  7. for(var key in source) {
  8. if(typeof source[key] === 'object') {
  9. result[key] = deepCopy(source[key])
  10. } else {
  11. result[key] = source[key]
  12. }
  13. }
  14. return result;
  15. }
  16. var objCopy = deepCopy(obj)
  17. obj.name = 'Lee';
  18. console.log(obj);//Object {name: "John", age: 30}
  19. console.log(objCopy);//Object {name: "Lee", age: 30}

7. 文件上传如何做断点续传?

由于篇幅问题,这里只简单介绍原理:断点续传实现的基本思路就是在客户端将要传输的文件分割为大小相当的多块,将这些块同时向目标服务器端发送;在服务器端的监听数据传输请求,每完成一次请求就告诉客户端可以继续发送直至全部发送完成。最终,发送给到服务端的是n个Blob格式的二进制文件。如果要还原为一个文件只需把这些二进制文件合并即可。

流程:

首先通过input标签可以获取到一个File格式的上传文件,如

  1. <input type="file" multiple="multiple" />

断点核心:将File格式文件切分成多块,如每块切分为1000字节,可以使用:

  1. // 首次上传
  2. range = file.slice(0,1000); // range为Blob格式
  3. upload(range)
  4. ...
  5. // 首次上传完成后再次上传
  6. range = file.slice(1000,2000);
  7. upload(range)
  8. ...
  9. // 直至全部完成

然后把这个些range加上特定标识上传即可。

8. 防抖和节流的区别是什么?

防抖是指高频触发事件时,程序会等待,直至停止触发并经过一定时间后(如果等待期间触发将会重新计算时间)才会执行代码一次。

如以下示例,无论如何频繁点击,都不会执行todo函数,直至停止点击2000毫秒内不再点击,才会触发一次todo函数。

js写法:

  1. <body>
  2. <div onclick="clickHandle()">防抖</div>
  3. </body>
  4. <script>
  5. function clickHandle() {
  6. debounce(); // 防抖
  7. }
  8. function todo() {
  9. console.log('todo!');
  10. }
  11. var debounce = (function(fn) {
  12. var timeout = null;
  13. return function() {
  14. clearTimeout(timeout);
  15. timeout = setTimeout(() => {
  16. fn.apply(this, arguments); // 等效于 fn()
  17. }, 2000);
  18. };
  19. })(todo);
  20. </script>

react写法:

  1. import React from 'react';
  2. class Test extends React.PureComponent {
  3. constructor(props) {
  4. super(props);
  5. this.timeoutID = null;
  6. }
  7. _todo = () => {
  8. console.log('防抖');
  9. }
  10. _debounce = () => {
  11. const _this = this;
  12. clearTimeout(this.timeoutID);
  13. this.timeoutID = setTimeout(() => {
  14. _this._todo();
  15. clearTimeout(this.timeoutID);
  16. }, 2000);
  17. }
  18. render() {
  19. return (
  20. <>
  21. <div onClick={this._debounce}>防抖</div>
  22. </>
  23. );
  24. }
  25. }
  26. export default Test;

节流是指高频事件触发时,都会在 n 秒内执行一次,节流会稀释函数的执行频率。

如以下示例,无论如何频繁点击,都会在2000毫秒后执行一次todo函数。

js写法:

  1. <body>
  2. <div onclick="clickHandle()">节流</div>
  3. </body>
  4. <script>
  5. function clickHandle() {
  6. throttle(); // 节流
  7. }
  8. function todo() {
  9. console.log('todo!');
  10. }
  11. var throttle = (function(fn) {
  12. let canRun = true;
  13. return function() {
  14. if (!canRun) return;
  15. canRun = false;
  16. timeout = setTimeout(() => {
  17. fn.apply(this, arguments); // 等效于 fn()
  18. canRun = true;
  19. }, 2000);
  20. };
  21. })(todo);
  22. </script>

react写法:

  1. import React from 'react';
  2. class Test extends React.PureComponent {
  3. constructor(props) {
  4. super(props);
  5. this.timeoutID = null;
  6. this.time = 2000;
  7. this.canRun = true;
  8. }
  9. _todo = () => {
  10. console.log('节流');
  11. }
  12. _throttle = () => {
  13. if (this.canRun) {
  14. this.canRun = false;
  15. this.timeoutID = setTimeout(() => {
  16. clearTimeout(this.timeoutID);
  17. this._todo();
  18. this.canRun = true;
  19. }, this.time);
  20. }
  21. }
  22. render() {
  23. return (
  24. <>
  25. <div onClick={this._throttle}>节流</div>
  26. </>
  27. );
  28. }
  29. }
  30. export default Test;

解决问题的思路都是使用闭包自执行函数写法,使局部变量只初始化一次,如示例中的timeout和canRun。

9. 介绍浏览器事件流?

浏览器事件流包括三个阶段:冒泡阶段、处于目标阶段和捕获阶段。其流向如下图所示:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3plcGluZzg5MTEwMw_size_16_color_FFFFFF_t_70

可以通过addEventListener和removeEventListener来添加和删除事件监听,这两个函数接收三个参数:1)事件类型。2)事件处理函数。 3)boolean值,为true时表示捕获事件,默认为false冒泡事件。如果想要中途停止冒泡或捕获,可以使用e.stopPropagation();

  1. <body>
  2. <div id="container">
  3. <div id="child"></div>
  4. </div>
  5. </body>
  6. <script>
  7. /************停止捕获写法******************/
  8. document.getElementById('container').addEventListener('click', function(e) {
  9. e.stopPropagation();
  10. console.log('container');
  11. }, true)
  12. document.getElementById('child').addEventListener('click', function(e) {
  13. console.log('child');
  14. })
  15. /************停止冒泡写法******************/
  16. document.getElementById('container').addEventListener('click', function() {
  17. console.log('container');
  18. })
  19. document.getElementById('child').addEventListener('click', function(e) {
  20. e.stopPropagation();
  21. console.log('child');
  22. })
  23. </script>

10. 介绍this各种指向情况?

this在开发中很常见,this并不代表函数自身,它的指向是由调用它的对象而定,即它是在执行过程中绑定的。

this的指向有四种情况:

(1)全局函数调用:this指向window全局变量,但如果函数内有定义this值,则函数内定义的值优先级更高。示例:

  1. // 函数调用
  2. function test() {      
  3. console.log('test:',this.a);  
  4. }  
  5. var a = 0;
  6. test(); // 0
  7. // 函数内有定义this值,则优先级更高
  8. function test() {
  9. this.a = 1;      
  10. console.log('test:',this.a);  
  11. }  
  12. var a = 0;
  13. test(); // 1

(2)作为对象属性调用:this指向调用它的对象,如果找不到指向的属性,则为undefined。

  1. function test() {  
  2. console.log(this.a);
  3. }
  4. var a = 0;
  5. var obj = {};
  6. obj.a = 1;
  7. obj.m = test;
  8. obj.m(); // 1,如果obj对象没有a属性,则为undefined
  9. // 相当于obj2.m指针还是指向obj的堆内存,obj对象占有自己的内存块,即最终由obj调用test函数
  10. var obj2 = {};
  11. obj2.a = 2;
  12. obj2.m = obj;
  13. obj2.m.m(); // 1
  14. // 与第一种情形相同
  15. var obj3 = {};
  16. obj3.a = 3;
  17. obj3.m = obj.m;
  18. obj3.m(); // 3

(3)构造函数调用:this指向new出来的对象,如果找不到指向的属性,则为undefined。

  1. var a = 2; 
  2. function test() {   
  3. this.a = 1;
  4. this.m = function(){
  5. console.log(this.a);
  6. } 
  7. } 
  8. var obj = new test();
  9. console.log(obj.a); // 1
  10. obj.m(); // 1
  11. // 如果构造函数没有属性a,则为undefined
  12. var a = 2; 
  13. function test() {   
  14. this.m = function(){
  15. console.log(this.a);
  16. } 
  17. } 
  18. var obj = new test();
  19. console.log(obj.a); // undefined
  20. obj.m(); // undefined
  21. obj.a = '1'
  22. console.log(obj.a); // 1
  23. obj.m(); // 1

(4)apply、call、bind调用 ,this指向第一个参数,当不设第参数时,this指向window全局变量。

  1. var x = 0;  
  2. function test() {    
  3. console.log(this.x);  
  4. }  
  5. var o = {};  
  6. o.x = 1;   
  7. test.apply(); //0
  8. //apply()的参数为空时,默认调用全局对象。因此,这时的运行结果为0,证明this指的是全局对象。如果把最后一行代码修改为
  9. test.apply(o); //1

箭头函数的this是在定义函数时绑定,而不是在执行过程中绑定的。箭头函数的this指向上下文,简单说,就是this指向取决于调用该箭头函数往上一层或N层的父级作用域,如果有父级作用域,父级作用域的this就是箭头函数的this; 如果没有,就是window。

  1. // 箭头函数外层没有其他函数,this指向window
  2. var foo = () => {
  3. console.log(this.a);
  4. }
  5. var a = 2;
  6. var obj = {
  7. a: 3,
  8. foo: foo
  9. };
  10. obj.foo(); //2
  11. foo.apply(obj); //2 apply在箭头函数不会生效
  12. // 箭头函数外层有其他函数,this指向最近一层带this属性的作用域,直至冒泡到最用引用的对象
  13. function foo() {
  14. this.a = 0;
  15. return () => {
  16. this.a = 1;
  17. return() => {
  18. console.log(this.a);
  19. }
  20. }
  21. }
  22. var a = 2;
  23. var obj = {
  24. a: 3,
  25. foo: foo
  26. };
  27. obj.foo()()(); // 1。
  28. // 如果把this.a = 1删除,则结果为0;
  29. // 如果再把this.a = 0删除,则结果为3。this会往上冒泡取值直至调用它的对象。
  30. // 如果再把a:3也删掉,则为undefined,因为这里引用它的是obj而不是window

发表评论

表情:
评论列表 (有 0 条评论,474人围观)

还没有评论,来说两句吧...

相关阅读