JS基础小知识

395 阅读13分钟

JS基本概念

1.js的核心是ECMAscript,它规定了语法的构成,语法、类型、语句、关键字、保留字、操作符、对象。

2.DOM(文档对象模型)文档对象模型: HTML文档各个节点被视为各种类型的Node对象。每个Node对象都有自己的属性和方法,利用这些属性和方法可以遍历整个文档树。 然后可以对这些node节点对象进行各种操作,如增删改查等等

3.BOM(浏览器对象模型) 使js在浏览器里面有能力跟浏览器进行交互

变量声明与函数提升

1.预编译

通常js引擎在执行的时候,分为3个阶段:

  • 语法解析
  • 预编译
  • 执行代码

js代码进行语法解析后会先进行一次预编译,在这个过程中将变量声明及函数声明提升到当前作用域的顶端,然后再进行处理。

//例子:1
var a = 5;
console.log(a); //5
console.log(b); //undefind
var b = 10;

//例子:2
function fun(a){
  console.log(b); // undefind
  var a = b = 2;
  var c = 123;
  console.log(a); // 2
  console.log(b); // 2
  console.log(c); // 123
}
var d = 4;
fun(1)

解析:针对预编译,js在编译之前会创建一个新的全新的对象,可以称为window对象,或者是也可以理解成为GO(Global Object),然后将所有生命的全局的变量,和未使用var,let声明的变量放到GO中去,并且赋值为undefind,然后将函数声明也放到GO对象里面,并且赋值为自身的函数体。

GO{
    a: undefind,
    b: undefind,
    d: undefind,
    fun: function fun(){
        var a = b = 2;
        var c = 123;
    }
    
},
GO{
    a: 1,
    b: 10,
    d: 4,
    fun: function fun(){
        var a = b = 2;
        var c = 123;
    }
}

另一方面进行的函数预编译过程:

创建OA对象;找形参和变量声明,将形参和变量声明作为AO的属性;将实参与形参相统一,就是将实参赋值给形参; 在函数体中找到函数声明,并将其作为AO的属性; 执行函数;

OA{
    a: 2,
    c: 123
}

同名的变量,后面的会覆盖前面的

console.log(c); //f c(){console.log(5)}
var c = 2;
console.log(c); //2
function c(){
   console.log(3)
}
var c = 4;
console.log(c); // 4
function c(){
   console.log(5)
}
console.log(c); //TypeError: c is not a function
c();

解析:首先在预解析阶段,var c = 2;但是接下来是函数声明,函数声明里面的函数名字跟变量名字相同,故函数声明代替变量名,接下来var c = 4;也是这个道理,但是下面是一个相同名字的函数,相同名字的函数会覆盖上一个函数,取最后一个函数,故得到答案。

var a = "s";
function foo(a){
    console.log(a); //undefind
    a = 8
}
foo();
console.log(a) // s

解析:函数foo中传递的参数相当于foo(var a)所以,a=8的作用域不是全局的作用域,则第一个结果为undefined,第二个结果为s

var a = "s";
function foo(a){
    console.log(a); //s
    a = 8
}
foo(a);
console.log(a) // s

解析:如果foo调用时传递一个参数,则a = 8为作用域不是全局,而是局部的作用域。

注意:内部a=8 当没有变量a的声明时,那么a=8生成的是全局作用域,但是当函数内部有局部作用域下的a时,a=8仅仅就是赋值的作用

2.变量声明及变量提升

  • 变量声明为显示声明跟隐示声明
var name = "liming"//显示声明
name = "李明" //隐示声明
  • 变量提升 就是把变量提升提到作用域的top的地方,变量提升 只是提升变量的声明,并不会把赋值也提升上来。 此外,针对于作用域,if条件语句不会创建新的作用域,而函数会创建新的作用域。
var v='Hello World'; 
(function(){ 
  alert(v); //undefind
  var v='I love you'; 
})() 

3.函数提升

函数声明会提升,函数的表达式不会提升,如果既有变量声明,又有函数声明的情况下,函数声明会被提前声明,如果是2个相同名字的函数,后面会覆盖前面的函数。箭头函数没有函数提升。

var a = true;
foo();

function foo(){
 if(a){
     var a = 100
 }  
 console.log(a) // undefind
}

以上会转化成

function foo(){
    var a;
    if(a){
        a = 100
    }
    console.log(a) //undefind
}

var a;
a = true;
foo();

4.作用域

  • 全局作用域
  • 局部作用域
  • 块级作用域
  • 作用域链 <1>全局作用域,最外层定义的变量拥有全局作用域,对于任何的函数来说都可以访问到,全局作用域在浏览器关闭的时候才销毁,比较耗费内存
var name = 'liming';
function foo(){
    console.log(name)
}
foo();

<2>局部作用域,一般只在固定函数代码片段可以访问的到,而对于函数外部是无法访问的常见于在函数内部声明,当程序执行完毕以后会销毁,节省内存

function foo(){
    var name = 'liming';
}
console.log(name); //SyntaxError: Invalid or unexpected token
foo();

注明:在函数内部声明变量的时候 要用var,或者let,或者const,如果不用且函数内部没有声明其他变量的情况下会变成全局

function foo(){
    name = 'liming';
}
console.log(name);//liming
foo();

函数内定义了一个局部变量,在函数内解析的时候都会将这个变量提前声明

var name = "liming"
function foo(){
    console.log(name) // undefind
    var name = "ceshi"
    console.log(name) // ceshi
}
foo()

<3>块级作用域

块级作用域由{}包括,if和for里面也属于块级作用域

5.let const var的区别

let 块级作用域,只能在块级作用域里面访问到,不能夸函数访问,也不能夸块级作用域访问

const 定义常量,使用时必须赋值,只能在块作用域里访问,而且不能修改。

var 定义变量没有块级作用域的概念,可以垮块访问。 函数作用域里面访问不到

this

1.this在构造函数中表示的是实例对象 this不管用在什么场合总是会返回来一个对象

2.函数被赋值给另外一个变量,this的指向会变

var a = {
      name: "张三",
      des: function(){
        return "姓名:" + this.name
      }
    }

    var name = "李四";
    var f = a.des;
    console.log(f()) //姓名:李四

3.this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确认this指的是谁,this指向的是调用他的那个对象。

4.如果一个函数中有this,但是没有对上一级的对象所调用,那this的指向就是window,如果是严格模式下的话不是window,

var a = 10;
function fun(){
 console.log(this.a) // 10 this指向的是window
}
fun();

5.如果一个函数中有this,这个函数有被上一级对象所调用,那么this指的就是上一级的对象

var a = {
    c: 4,
    b: function(){
       console.log(this.c) //4
    }
}

a.b();

6.如果一个对象中有this,且这个对象中包含多个对象,尽管函数是被最外层的对象调用,this的指向也只是他上一级的对象,

var a = {
    c: 4,
    b: {
        c: 8
        d: function(){
           console.log(this.c) //8
        }
    }
    
}

a.b.d();

7.构造函数中的this

function Fn(){
    this.user = "测试";
}
var a = new Fn();
console.log(a.user); //测试

构造函数中的this,指向的是对象a,因为new关键字创建了一个对象的实例,用对象a创建了fn的实例,此时仅仅是创建,相当于fn复制了一份放到了a里面,所以调用a.user也可以

8.call,bind,apply方法

  • call 改变this的指向,修改环境,也可以传递参数。
var d = {
    name: '测试一下',
    ss: function(){
        console.log(this.name)
    }
}

var m = d.ss;
m() //undefind
var d = {
    name: '测试一下',
    ss: function(){
        console.log(this.name) //测试一下
    }
}

var m = d.ss;
m.call(d) 
var d = {
    name: '测试一下',
    ss: function(a,b){
        console.log(this.name); //测试一下
        console.log(a+b) //3
    }
}

var m = d.ss;
m.call(d,1,2) 
  • apply

用于改变this的指向,修改环境,传递参数的时候必须传递数组。

var d = {
    name: '测试一下',
    ss: function(){
        console.log(this.name) //测试一下
    }
}

var m = d.ss;
m.apply(d) 
var d = {
    name: '测试一下',
    ss: function(a,b){
        console.log(this.name) //测试一下
        console.log(a + b) //3
    }
}

var m = d.ss;
m.apply(d, [1,2]) 

如果call方法,跟apply方法中第一个参数为null,则this的指向为window。

  • bind 修改this的指向,
var d = {
    name: '测试一下',
    ss: function(a,b){
        console.log(this.name) 
        console.log(a + b)
    }
}

var m = d.ss;
m.bind(d);

//ƒ (a,b){
     //console.log(this.name)
    // console.log(a + b)
//}

会返回一个修改过后的函数

var d = {
    name: '测试一下',
    ss: function(a,b){
        console.log(this.name) //测试一下
        console.log(a + b) //3
    }
}

var m = d.ss;
var c = m.bind(d);
c()

传递参数是按照形参的顺序进行的,

var d = {
    name: '测试一下',
    ss: function(a,b,c){
        console.log(this.name) //测试一下
        console.log(a + b + c) // 16
    }
}

var m = d.ss;
var c = m.bind(d,10);
c(2,4)
  • 总结:

apply和call都是改变上下文的this,而且能立即执行函数,但是bind方法会让调用的函数想什么时候执行,就什么时候执行。

9.箭头函数的this指向

箭头函数的指向问题,是根据环境来的,指向距离非箭头函数最近的一个函数,如果父级还是箭头函数,则依次往上找。箭头函数内部是没有this的。针对,call(),apply(),bind()方法不能改变this的指向,可以传递参数。

var x=11;
var obj={
  a:22,
  say:function(){
    console.log(this.a) //22
  }
}
obj.say();

var x=33;
var obj={
  a:44,
  b: this,
  say:()=>{
    console.log(this.a) //11
  }
}
obj.say();

箭头函数

1.将原函数的“function” 关键字和函数名字都去掉,用=> 链接列表和函数体,箭头函数相当于匿名函数。

function foo(a,b){
 return a + b
}

(a,b) => {return a + b}

当参数只有一个的时候,可以省略括号,没有参数的时候,不可以省略

//没有参数的时候
var fn1 = function(){}
var fn1 = ()=>{}

//有一个参数的时候
var fn1 = funcion(a){}
var fn1 = a => {}

//有2个参数的时候
var fn1 = funcion(a,b){}
var fn1 = (a,b) => {} 

//有多个参数的时候
var fn1 = funcion(a,b,...arg){}
var fn1 = (a,b,...arg) => {} 

箭头函数内部没有arguments,不能使用new 关键字来实例化对象

js中的原型与原型链

--- 参考于:www.jianshu.com/p/dee9f8b14…

1.在js的世界里面全是对象,对象之间也是存在区别的,普通对象跟函数对象也是有区分的,如下代码:

function f1(){}
var f2 = function(){}
var f3 = new Function()

var o1 = {}
var o2 = new Object()
var o3 = new f1()

console.log("f1", typeof function f1(){}); //function
console.log("f2", typeof f2); //function
console.log("f3", typeof f3); //function
console.log("o1", typeof o1); //object
console.log("o2", typeof o2); //object
console.log("o3", typeof o3); //object

console.log("f2", f2.prototype);  
console.log("f3", f3.prototype);
console.log("o1", o1.prototype);
console.log("o2", o2.prototype);
console.log("o3", o3.prototype);

image.png

2.原型

在JS中一切皆对象,

深浅拷贝

1.js的数据类型整体的分为2种,一种是基本类型一种是引用类型

  • 基本数据类型

    String,Number,Boolean,Null,Undefind,Symbol

  • 引用数据类型

    对象,数组,函数,Date

    基本数据类型是存放到栈里面的,引用数据类型是存放到堆里面的,保存在栈内存的数据必须有固定大小的数据,引用类型的大小不固定,只能保存在堆中,但是可以把地址放到栈内存中用来引用,操作的是保存在变量中的引用类型的地址

var b="测试"; //保存在栈里面
var c = {
    hh: '名字',
}
  • 基本类型的复制:
 var d = 1;
 var c = d;
 console.log(c); //1
 var d = 5;
 console.log(d); //5
 console.log(c); //1

在赋值的过程中栈内存会另开辟一个内存,重新放置c,在栈内存中同时放着c,d,相互没有影响。

  • 引用类型的赋值
var person = {
  name: "我是阿乐啊!!",
  age: 18
}
var personTemp = person;
personTemp.age = 20;
personTemp.name = "我是测试啊";
console.log("personTemp",personTemp);
console.log("person",person);
    

image.png

personTemp及person指向的是堆内存里面的同一个地址,是同一个对象。 引用类型的地址,名是存在栈中的,值是存在堆里面的但是栈内存会提供一个引用的地址指向堆内存。

2.深浅拷贝解释

深浅拷贝是针对数组,对象,这样的引用数据类型的

浅拷贝只是复制的某一个对象的地址,不复制对象的本身,不管是新复制出来的,还是旧的对象,都是指向同一个对象,而深拷贝指的是会创造出另外一个内存,来存储对象,这2个相互不干扰,不影响。

  • 浅拷贝
var arr = [1,2,3,4,5];
var arrTemp = arr;
console.log(arrTemp);
arrTemp[0] = 3;
console.log(arrTemp);
console.log(arr);

image.png

由此可以看出arr与arrTemp指的同一个数组,arrTemp指向的数组的一个引用地址

  • 深拷贝
function deepClone(obj){
  let objClone = Array.isArray(obj)?[]:{};
  if(obj && typeof obj === "object"){
    for(key in obj){
      if(obj.hasOwnProperty(key)){

        if(obj[key] && typeof obj[key] === "object"){
          objClone[key] = deepClone(obj[key]);
        }else{
          objClone[key] = obj[key];
        }

      }
    }

  }

  return objClone;
}

let a = [1,2,3,4];
b = deepClone(a);
a[0] = 2;
console.log(a,b); 

image.png

通过递归可以操作深拷贝

function deepClone(obj) {
  let _obj = JSON.stringify(obj);
  _objClone = JSON.parse(_obj);

  return _objClone
}

let a = [0,1,2,3];
b = deepClone(a);

a[0] = 2;
console.log(a,b)

image.png

通过JSON.stringify,JSON.parse也可以实现深拷贝

操作对象的方法

1.Object.keys();

 var data = {
  name: '',
  value: ""
}
var arr = Object.keys(data);
console.log(arr); //["name","value"]
console.log(arr.length) //2

用来检测对象里面是不是空对象,返回值是对象的所有可枚举的属性的字符串数组

  • 处理数组
let arr = [1,2,3,4];
Object.keys(arr);
console.log(Object.keys(arr)) // ["0","1","2","3"];

返回的是数组的字符串下标

  • 处理字符串
let arr = "字符串";
Object.keys(arr);
console.log(Object.keys(arr)) // ["0","1","2"];

返回的是数组的字符串下标

let person = {name: '测试', age: 10};
console.log(
  Object.keys(person).map((key)=>{
    return person[key] // ["测试",10]
  })
)

2.Object.values()

  • 操作对象
var obj = { 0: '测试', 1: '一下', 2: '呀' };
console.log(Object.values(obj)); // ['测试', '一下', '呀']

返回来的是属性里面对应的值的数组

  • 操作数组
var arr = ["测试测试","哈哈","哈哈哈哈"];
console.log(Object.values(obj)) //["测试测试","哈哈","哈哈哈哈"]

返回的还是数组

  • 字符串
var str = "测试测试测试"console.log(Object.values(str)) //["测", "试", "测", "试", "测", "试"]

字符串会切割成数组。

简单请求跟非简单请求

参考(developer.mozilla.org/zh-CN/docs/…

跨域资源共享(cors),是一个系统,由http头构成的,这些请求会决定浏览器是否允许跨域,而浏览器会有同源策略,理论上是不允许请求跨域的,而cors设置给了浏览器这个权限,让服务器可以允许跨域。

浏览器对那些服务器产生副作用的影响的请求方法,需要有一个预检请求,先通过options发送一个预检请求,先看浏览器是否允许跨域,如果允许的话,再次发送一个正常发送的请求,有这样操作的请求叫做非简单请求。

简单请求:不会触发cors预检请求

post,get,head

请求头:

Accept Accept-Language Content-Language Last-Event-ID Content-Type,但仅能是下列之一 application/x-www-form-urlencoded multipart/form-data text/plain

满足以上均为简单请求,有其中的一项不满足的话为复杂请求。

http响应字段

1.Access-Control-Request-Method 服务器允许客户端使用的方法

2.Access-Control-Allow-Headers 服务器允许请求头中所带的字段

3.Access-Control-Max-Age 相应的有效时间

4.Access-Control-Allow-Credentials: true 是否可以允许携带cook信息 如果请求头中携带了信息,则不可以设置为*

5.Access-Control-Allow-Origin 允许外域访问

6.Access-Control-Expose-Headers 在跨源访问时,XMLHttpRequest对象的getResponseHeader()方法只能拿到一些最基本的响应头,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果要访问其他头,则需要服务器设置本响应头。

http请求字段

1.Origin 发起请求或者是预检请求的地址

2.Access-Control-Request-Method 发起请求的方法

3.Access-Control-Request-Headers 请求头

4.Accept 发送的数据类型

5.Accept-Encoding 告诉服务器可以压缩的数据格式 content-encoding 网页使用了那些压缩数据传送

6.Connection 请求完成后是否关闭网页链接

7.Content-Type 网页的编码格式

8.Host 请求头指明了请求将要发送到的服务器主机名和端口号。

9.Referer 告诉服务器网页的链接是从哪里过来的