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);
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);
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);
由此可以看出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);
通过递归可以操作深拷贝
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)
通过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 告诉服务器网页的链接是从哪里过来的