JS自我复习指南

1.闭包和作用域:

https://www.cnblogs.com/chengzp/p/scopechain.html

一般来说,只有函数作用域和块作用域,es6有块作用域

闭包就是能够读取其他函数内部变量的函数

function F1(){
    var a = 100

    //返回一个函数 (函数作为返回值)
    return function (){
        console.log(a)
    }
}

//f1得到一个函数
var f1 = F1()
var a = 200
f1()

例题:创建10个<a>标签,点击的时候输出对应的序号

看到这道题,最直接的想法或许就i是这样的

//错误代码演示var i,a
for (i = 0; i < 10; i++) {
    a = document.createElement('a')
    a.innerHTML = i + '<br />'
    a.addEventListener('click',function(e){
        e.preventDefault()
        console.log(i)
    })
    document.body.appendChild(a)
}

但这是不对的。

可以看到,我对着左边的标签一顿乱点,结果打印的都是10.为什么呢

因为上例代码都是同步执行的,在页面加载的一瞬间,for循环已经执行完毕,我再去点标签的时候,i的值已经是10了,所以不管我点第几个标签,打印的都会是10。

那怎么解呢?

这么解

for (var i = 0; i < 10; i++) {
    (function(i){
        a = document.createElement('a')
        a.innerHTML = i + '<br />'
        a.addEventListener('click',function(e){
            e.preventDefault()
            console.log(i)
        })
        document.body.appendChild(a)
    })(i)
}

2.this的指向问题:

es6箭头函数解决了this造成的一些问题

1.全局环境下,this 始终指向全局对象(window), 无论是否严格模式;

2.函数直接调用this

(1)非严格模式下,this 默认指向全局对象window

(2)严格模式下,this 默认为undefined

3.es6箭头函数里的this指的是定义这个函数时外层代码的this,这句话可以从两个方面理解:(

  1. es6箭头函数没有自己的this
  2. es6箭头函数里的this是外层代码(定义时,非执行时)this的引用

var Animal = function() {
    this.name = "Animal"; // 下面那一行的this指向的是这外面的this
    this.speak = (name,words) => {
        console.log(this.name + ' is saying ' + words + '.');
    }
}
var cat = new Animal();
cat.speak(22,"miao ~"); // Animal is saying miao ~.
var speak = cat.speak;
speak(33,"miao ~"); // Animal is saying miao ~.

4.this的取值依赖于函数被谁调用

1.如果是使用new关键字来调用函数,那么函数内部的this就是一个全新的对象。

2.如果使用apply、call或者bind来调用一个函数,函数内部的this就将指向传入的第一个参数。(注:使用这几个方法可以改变this的指向)

3.如果函数被作为一个方法进行调用,比如:obj.method() --- 那么this就该函数的调用者。(注:样例中的obj)

3.箭头函数和普通函数的区别

(1)箭头函数的this在定义时确定(父级作用域),而非运行时(普通函数在运行时确定)

(2)箭头函数不能当作构造函数通过new来新建实例(追问:为什么不能new? 当时回答应该和new的过程有关,但是具体没答上来,后来百度了一下,大概是因为箭头函数没有自己的prototype)

(3)没有argumnets对象

(4)没有prototype

(5)不能用yield命令,因此箭头函数不能用做Generator函数

4."use strict"严格模式:

1.变量使用前要先声明

2.不能对变量执行delete

3.禁止this关键字指向全局对象

4.禁用eval()

5.对象的属性名不能重复

6.不能修改arguments

7.不能在函数内定义arguments变量

8.不能使用arugment.caller和argument.callee。因此如果你要引用匿名函数,需要对匿名函数命名。

 

5.setTimeout(foo,0)可以写成foo()吗?

那如果是setTimeout( foo, 0 )呢?0毫秒哎,foo是不是会马上执行?

不会!不会!不会! setTimeout( foo, 0 )的意思是,把foo在0ms内推入任务队列,而浏览器会继续执行执行栈中的事件,当执行栈空了,才会把任务队列中的foo事件推入执行栈执行。如果当前执行栈的事件很多,foo还是得继续等着哦,等到执行栈中的事件全部执行完了,foo才能进入到执行栈中执行。所以不是在0毫秒后就立即执行foo的意思。

那么setTimeout( foo, 0 )有什么作用呢?

同步变异步,降低foo事件优先级,把它放到后面执行。

6.宏任务和微任务

首先是event loop,同步任务和异步任务在不同的线程执行,异步任务的回调函数放进事件队列中。

当主线程的任务执行完了(执行栈空了),JS会去询问事件队列

事件队列中分为宏任务和微任务,(实际上宏任务和微任务处于两条不同队列)先执行宏任务,执行完后若发现有微任务则执行微任务(每执行完一个宏任务都要把现存的微任务执行完)

宏任务

微任务

7.浅拷贝和深拷贝有什么区别?分别有什么方式?

https://juejin.im/post/6844903807705415687

https://juejin.im/post/6844903833223561229

当对象的属性值为基本数据类型,那么拷贝的就是基本数据类型的值,如果对象的属性值为引用数据类型,那么拷贝的就是内存地址,上面的代码可以用下图更形象的解释:

浅拷贝

1.Object.assign()

Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。

var obj = { a: {a: "kobe", b: 39} };
var initalObj = Object.assign({}, obj);
initalObj.a.a = "wade";
console.log(obj.a.a); // wade

注意:当object只有一层的时候,是深拷贝

let obj = {
   username: 'kobe'
};
let obj2 = Object.assign({},obj);
obj2.username = 'wade';
console.log(obj);//{username: "kobe"}

2. Array.prototype.concat()

let arr = [1, 3, {
   username: 'kobe'
}];
let arr2=arr.concat();    
arr2[2].username = 'wade';
console.log(arr);

3. Array.prototype.slice()

let arr = [1, 3, {
   username: ' kobe'
}];
let arr3 = arr.slice();
arr3[2].username = 'wade'
console.log(arr);

深拷贝(指向不同的地址)

 

8.addEventListener()

第三个参数是true时,在事件捕获阶段执行,是false时,在时间冒泡阶段执行

事件冒泡的过程

9.JS的垃圾回收机制

垃圾回收算法:“标记-清除算法”

  • 垃圾回收器获取根并“标记”(记住)它们
  • 然后它访问并“标记”所有来自它们的引用。
  • 然后它访问标记的对象并标记它们的引用。所有被访问的对象都被记住,以便以后不再访问同一个对象两次。
  • 以此类推,直到没有未访问的引用(可以从根访问)为止。
  • 除标记的对象外,所有对象都被删除。

什么是垃圾

一般来说没有被引用的对象就是垃圾,就是要被清除, 有个例外如果几个对象引用形成一个环,互相引用,但根访问不到它们,这几个对象也是垃圾,也要被清除。

如何检垃圾

使用“标记-清除算法”

 

10.var和let、const的区别

1.var声明变量存在变量提升,let和const不存在变量提升

2.let、const都是块级局部变量 顾名思义,就是只在当前代码块起作用

3.同一作用域下let和const不能声明同名变量,而var可以

 

11.AJAX

JS原生AJAX

    function request() {
        var request;
        if(window.XMLHttpRequest){
            request=new XMLHttpRequest();
        }else{
            request=new ActiveXObject();
        }
        request.onreadystatechange=function(){
            if (xmlhttp.readyState===4 && xmlhttp.status===200)
            {
                document.getElementById("myDiv").innerHTML=xmlhttp.responseText;
            }
        }
        request.open('GET',"/try/ajax/demo_get.php?t=" + Math.random(),true)
        //使用post的情况
        // xmlhttp.open("POST","/try/ajax/demo_post2.php",true);
        // xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
        // xmlhttp.send("fname=Henry&lname=Ford");
        request.send()

    }

JQ的AJAX

$("button").click(function(){
    $.ajax({url:"demo_test.txt",success:function(result){
        $("#div1").html(result);
    }});
});

//使用$.post来提交post请求
$("input").keyup(function(){
  txt=$("input").val();
  $.post("demo_ajax_gethint.asp",{suggest:txt},function(result){
    $("span").html(result);
  });
});

//使用$.get来提交get请求
$("button").click(function(){
    $.get("demo_test.html",{ name:"Donald", town:"Ducktown" },function(data,status){
        alert("Data: " + data + "nStatus: " + status);
    });
});

12.原型和原型链

面试官:谈谈你对 JS 原型和原型链的理解?

候选人:JS 原型是指为其它对象提供共享属性访问的对象。在创建对象时,每个对象都包含一个隐式引用指向它的原型对象或者 null。

原型也是对象,因此它也有自己的原型。这样构成一个原型链。

面试官:原型链有什么作用?

候选人:在访问一个对象的属性时,实际上是在查询原型链。这个对象是原型链的第一个元素,先检查它是否包含属性名,如果包含则返回属性值,否则检查原型链上的第二个元素,以此类推

面试官:那如何实现原型继承呢?

候选人:有两种方式。一种是通过 Object.create 或者 Object.setPrototypeOf 显式继承另一个对象,将它设置为原型。

另一种是通过 constructor 构造函数,在使用 new 关键字实例化时,会自动继承 constructor 的 prototype 对象,作为实例的原型。

在 ES2015 中提供了 class 的风格,背后跟 constructor 工作方式一样,写起来更内聚一些。

13.Arguments

是函数的参数,不建议在es6中使用

function func1(a, b, c) {
  console.log(arguments[0]);
  // expected output: 1

  console.log(arguments[1]);
  // expected output: 2

  console.log(arguments[2]);
  // expected output: 3
}

func1(1, 2, 3);

 

14.call()、apply()、bind()

都是用来定义对象函数中this指向的对象的

比如

obj.myFun.call(db);    // 德玛年龄 99
obj.myFun.apply(db);    // 德玛年龄 99
obj.myFun.bind(db)();   // 德玛年龄 99

以上出了 bind 方法后面多了个 () 外 ,结果返回都一致!

由此得出结论,bind 返回的是一个新的函数,你必须调用它才会被执行。(因为call和apply都是函数调用时使用,而bind是函数调用时使用)

对比三个函数传参情况

obj.myFun.call(db,'成都','上海');     // 德玛 年龄 99  来自 成都去往上海
obj.myFun.apply(db,['成都','上海']);      // 德玛 年龄 99  来自 成都去往上海  
obj.myFun.bind(db,'成都','上海')();       // 德玛 年龄 99  来自 成都去往上海
obj.myFun.bind(db,['成都','上海'])();   // 德玛 年龄 99  来自 成都, 上海去往 undefined

从上面四个结果不难看出:

call 、bind 、 apply 这三个函数的第一个参数都是 this 的指向对象,第二个参数差别就来了:

call 的参数是直接放进去的,第二第三第 n 个参数全都用逗号分隔,直接放到后面 obj.myFun.call(db,'成都', ... ,'string' )。

apply 的所有参数都必须放在一个数组里面传进去 obj.myFun.apply(db,['成都', ..., 'string' ])。

bind 除了返回是函数以外,它 的参数和 call 一样。

当然,三者的参数不限定是 string 类型,允许是各种类型,包括函数 、 object 等等!

 

15.扩展/剩余运算符

扩展运算符

剩余运算符

剩余运算符可以和数组的解构赋值一起使用,但是必须放在最后一个,因为剩余运算符的原理其实是利用了数组的迭代器,它会消耗3个点后面的数组的所有迭代器,读取所有迭代器生成对象的value属性,剩运算符后不能在有解构赋值,因为剩余运算符已经消耗了所有迭代器,而数组的解构赋值也是消耗迭代器,但是这个时候已经没有迭代器了,所以会报错

 

这里first会消耗右边数组的一个迭代器,...arr会消耗剩余所有的迭代器,而第二个例子...arr直接消耗了所有迭代器,导致last没有迭代器可供消耗了,所以会报错,因为这是毫无意义的操作

剩余运算符和扩展运算符的区别就是,剩余运算符会收集这些集合,放到右边的数组中,扩展运算符是将右边的数组拆分成元素的集合,它们是相反的

16.for in 和for of

使用 for in 循环遍历对象的属性时,原型链上的所有属性都将被访问:

Object.prototype.bar = 10;// 修改Object.prototype  
var obj={"name":"wjy","age":26,"sex":"female"};//定义一个object对象    
var keys=[];//定义一个数组用来接受key    
var values=[];//定义一个数组用来接受value    
  for(var key in obj){    
    keys.push(key);    
    values.push(obj[key]);//取得value      
    }    
alert("keys is :"+keys+" and values is :"+values);   
//keys is : name,age,sex,bar and values is : wjy,26,female,10

推荐总是使用 hasOwnProperty 方法, 这将会避免原型对象扩展带来的干扰:

function allpro(obj){  
    var keys=[];   
    var values=[];    
    for(var key in obj){   
        //只遍历对象自身的属性,而不包含继承于原型链上的属性。  
        if (obj.hasOwnProperty(key) === true){  
            keys.push(key);    
            values.push(obj[key]);   
            }                 
        }  
    alert("keys is :"+keys+" and values is :"+values);    
}  
Object.prototype.bar = 1;// 修改Object.prototype  
var o={"name":"wjy","age":26,"sex":"female"};//定义一个object对象   
allpro(o);  
//keys is : name,age,sex and values is: wjy,26,female

for of只会迭代当前对象的迭代器

 

17.RegExp

https://www.runoob.com/jsref/jsref-obj-regexp.html

18.跨域

http://www.ruanyifeng.com/blog/2016/04/cors.html

实现方式参考https://blog.csdn.net/qq_38128179/article/details/84956552

什么是跨域

当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域

【1】无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB

【2】无法接触非同源网页的 DOM

【3】无法向非同源地址发送 AJAX 请求

解决方案

【1】设置document.domain解决无法读取非同源网页的 Cookie问题

【2】跨文档通信 API:window.postMessage()

【3】JSONP

jsonp的介绍:https://blog.csdn.net/u014660247/article/details/80912553

【4】CORS

CORS 是跨域资源分享(Cross-Origin Resource Sharing)的缩写。它是 W3C 标准,属于跨源 AJAX 请求的根本解决方法。

1、普通跨域请求:只需服务器端设置Access-Control-Allow-Origin

2、带cookie跨域请求:前后端都需要进行设置

实现:https://blog.csdn.net/qq_38128179/article/details/84956552

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值