js基础

210 阅读10分钟

数据类型和变量

声明变量

  • let:声明的变量可以多次赋值。
  • const:常量,只能赋值一次,引用内容可以修改。
  • var:在同一个作用域多次声明同一个变量不会报错,从第二个开始当作赋值操作。 var和let的区别

数据类型

基本数据类型

  • undefined:

    • 访问数组不存在的元素,访问对象不存在的属性
    • 使用没有初始化值的变量
  • null:null由程序员主动提供

  • String:

    • 三种写法(单双反):" ' `

    • 模板字符串拼接:拼接字符(即url)必须要用反引号

       let name = ; // zhang li ...
       let age = ; // 18 20 ...
       ​
       let uri = `/test?name=${name}&age=${age}`;
      
  • number:浮点数

    • 可以除零,正数除零Infinity(正无穷大),负数除零-Infinity(负无穷大)
    • 浮点数都有运算精度问题
    • parseInt(“xxx”)字符串转number
  • bigint:数字结尾加n

  • boolean:

    • Truthy:除Falsy外的值
    • Falsy:false,Nulish(null,undefined),0,0n,NaN,空字符串。

对象类型

Function

语法

定义函数:

 function 函数名(参数) {
     // 函数体
     return 结果;//可有可无
 }
  • 对实参的类型个数都没有限制,没有用到的形参是undefined。
  • 在函数中对形参赋值相当于是默认值

匿名函数:

语法:

 (function (参数) {
     // 函数体
     return 结果;
 })

使用场景:

  • 定义完毕后立刻调用

     (function(a,b){
         return a + b;
     })(1,2)
    
  • 作为对象的方法(类似Java的匿名内部类)

    例如:

     document.getElementById("p1").onclick = (function(){
         console.log("鼠标单击了...");
     });
    

箭头函数:

语法:

 (参数) => {
     // 函数体
     return 结果;
 }

规则和Java的Lambda表达式一致


对象

js中函数的本质是对象,这点和java完全不一样。

  • 函数可以参与赋值

  • 有属性,有方法

  • 可以作为方法参数(高阶函数)

  • 可以作为方法返回值(高阶函数)

     function c() {
         console.log("c");
         function d() {
             console.log("d");
         }
         return d;
     }
     c() //打印c。此时c()是d函数的对象
     c()() //c d 
    

作用域

函数可以嵌套。

image.png

  • 最外面一层是全局作用域。
  • 查找变量时,由内向外查找
  • 作用域本质上是函数对象的属性,可以通过 console.dir 来查看调试,查看时不会显示本身(即红色的)。
闭包
  • 函数定义时,它的作用域已经确定好了,因此无论函数将来去了哪,都能从它的作用域中找到当时那些变量。
  • 闭包:函数能够访问自己的作用域中变量

var与let的区别

如果函数外层引用的是 let 变量,那么外层普通的 {} 也会作为作用域边界,最外层的 let 也占一个 script 作用域。

普通的 {} :除了定义函数之外的 {}。

image.png

最外层(橙色)是全局作用域。


使用 var 变量,外层普通的 {} 不会视为边界,不便于区分同名变量。

var e = 10;
if(true) {
    var e = 20;
    console.log(e);    //20
}
console.log(e);    //20

Array

  • join

     let arr = ['a','b','c'];
     ​
     arr.join();         // 默认使用【,】作为连接符,结果 'a,b,c'
     arr.join('');       // 结果 'abc'
     arr.join('-');      // 结果 'a-b-c'
    
  • map

     let arr = [1,2,3,6];
     //要求变成[10,20,30,60]
     ​
     function a(i) {   // 代表的新旧元素之间的变换规则
         return i * 10
     }
     arr.map(a); //把函数作为map的参数
     arr.map( i => i * 10) //箭头函数
    
  • filter

     let arr = [1,2,3,6];
     //要求只剩下奇数,即过滤偶数
     arr.filter( (i) => i % 2 == 1 ); // 结果 [1,3]
    
  • forEach

     let arr = [1,2,3,6];
     //遍历
     arr.forEach( (i) => console.log(i) );
    

map,filter,forEach都不会改变原数组

回调函数:作为参数传入的函数。

Object

语法
 let obj = {
     属性名: 值,
     方法名: 函数,
     get 属性名() {},
     set 属性名(新值) {}
 }

例1

 let stu1 = {
     name: "小明",
     age: 18,
     study: function(){
         console.log(this.name + "爱学习");
     }    
 }

例2(用的最多)

 let stu3 = {
     name: "小白",
     age: 18,
     study(){ //对象函数这么写,仅限于对象内部
         console.log(this.name + "爱学习");
     }    
 }

get,set(和java差别很大,js是调用get和set方法的属性名

 let stu4 = {
     _name: null, //约定私有属性,js没有私有属性的概念
     get name() {
         console.log("进入了get");
         return this._name;
     },
     set name(name) {
         console.log("进入了set");
         this._name = name;
     }
 }
 ​
 //调用
 stu4.name = "小白"
 console.log(stu4.name)
成员增删

js的对象属性和方法可以随时加减。

 let stu = {name:'张三'};
 stu.age = 18;                   // 添加属性
 delete stu.age;                 // 删除属性
 ​
 stu.study = function() {        // 添加方法
     console.log(this.name + "在学习");
 }
this

在java中调用对象的非静态方法时,会传入一个隐性参数

image.png


js中this是动态改变的,与函数运行时上下文相关。

通过call函数动态改变this,把 call 的第一个参数 stu 作为 this,因此可以把任何对象当成this。

 function study(subject) {
     console.log(this.name + "在学习 " + subject)
 }
 ​
 let stu = {name:"小黑"};
 study.call(stu, "js");  // 输出 小黑在学习 js

在箭头函数内出现的 this,以外层 this 理解。

 let stu = {
     name: "小花",
     friends: ["小白","小黑","小明"],
     play() {
         this.friends.forEach(e => {
             console.log(this.name + "与" + e + "在玩耍");
         })
     }    
 }
 stu.play()

this.name 所在的函数是箭头函数,因此 this 要看它外层的 play 函数,play 又是属于 stu 的方法,因此 this 代表 stu 对象


如果是匿名函数的话,this.name 所在的函数是 "落单" 函数,此时this代表window。

 let stu = {
     name: "小花",
     friends: ["小白","小黑","小明"],
     play() {
         this.friends.forEach(function(e){
             console.log(this.name + "与" + e + "在玩耍"); //this.name为""
         });
     }
 }
 stu.play()
原型继承

Java中继承是发生在子类和父类之间,对象之间没有继承。


js中继承是发生在对象之间。

image.png

对象的_proto_代表它的父对象,js术语:原型对象

 let son = Object.create(father); //创建father的子对象
基于函数的原型继承

通过Object.create(father)创建的对象只有父对象的成员,处于方便,js提供了基于函数的原型继承。

函数作用:相当于构造器,负责创建子对象,给子对象提供属性和方法。

函数有个特殊的属性prototype(区别上图的_proto_),他就是函数创建的对象的父对象

 function cons(f2) {
     // 准备好this, 给子对象提供属性和方法
     this.f2 = f2;
     this.m2 = function () {
         console.log("子方法");
     }
 }
 // cons.prototype 就是父对象
 cons.prototype.f1 = "父属性";
 cons.prototype.m1 = function() {
     console.log("父方法");
 }
 ​
 //使用 new 关键字,创建子对象
 let son = new cons("子属性")

子类的_proto_就是函数的prototype属性。

JSON
  • json 中只能有 null、true|false、数字、字符串(双引号)、json对象、数组。
  • json 中的属性必须用双引号引起来。

动态类型

静态类型语言,如 Java,值有类型,变量也有类型、赋值给变量时,类型要相符

而 js 属于动态类型语言,值有类型,但变量没有类型,赋值给变量时,没要求

运算符

===

严格相等运算符,用作逻辑判等

||

在一些老旧代码中用作函数默认值。

 function test(n) {
     n = n || '男';
     console.log(n);
 }

值1 || 值2:如果 值1 是 Truthy,返回 值1,如果 值1 是 Falsy 则返回 值2。

??与?

值1 ?? 值2:值1不是nullish,返回值1,否则返回值2。

?:当某个属性是nulish时,直接返回undefined。

 console.log(stu.address?.city) //若stu.address为nulish直接返回undefined,避免报错

...展开运算符

作用1:

打散数组,把元素传递给多个参数。

 test(arr[0],arr[1],arr[2]); //传统写法
 test(...arr);   

打散理解为去除了数组外侧的 []


作用2:

复制数组或对象。

 let arr1 = [1,2,3];
 let arr2 = [...arr1];       // 复制数组
 ​
 let obj1 = {name:'张三', age: 18};
 let obj2 = {...obj1};       // 复制对象

展开运算符复制属于浅拷贝,只会复制一层,更深层次的是引用


作用3:

合并数组或对象。

 let a1 = [1,2];
 let a2 = [3,4];
 let b1 = [...a1,...a2];     // 结果 [1,2,3,4]
 ​
 let o1 = {name:'张三'};
 let o2 = {age:18};
 let o3 = {name:'李四'};
 let n2 = {...o3, ...o2, ...o1}; // 结果{name:'李四',age:18}

合并对象时,后面的属性会覆盖前面的。

[],{} 解构赋值

[] 数组:

 //用在声明变量时
 let arr = [1,2,3];
 let [a, b, c] = arr;    // 结果 a=1, b=2, c=3
 ​
 //用在声明参数时
 let arr = [1,2,3];
 function test([a,b,c]) {
     console.log(a,b,c)  // 结果 a=1, b=2, c=3
 }
 test(arr);  

{} 对象:

 //用在声明变量时
 let obj = {name:"张三", age:18};
 let {name,age} = obj;   // 结果 name=张三, age=18
 ​
 //用在声明参数时
 let obj = {name:"张三", age:18};
 function test({name, age}) {
     console.log(name, age); // 结果 name=张三, age=18
 }
 test(obj)

和...的区别:...是在实参使用,[] {} 是在形参使用。

控制语句

for in

主要用来遍历对象

 let father = {name:'张三', age:18, study:function(){}};
 ​
 for(const n in father) {
     console.log(n, father[n]);
 }
  • const n 代表遍历出来的成员(属性和方法)名。
  • 遍历子对象时,父对象的属性会跟着遍历出来。
  • 在 for in 内获取属性值,要使用 [] 语法,而不能用 . 语法。

for of

主要用来遍历数组,也可以是其它可迭代对象,如 Map,Set 等。

 let a1 = [1,2,3];
 for(const i of a1) {
     console.log(i);
 }
 ​
 let a2 = [
     {name:'张三', age:18},
     {name:'李四', age:20},
     {name:'王五', age:22}
 ];
 ​
 for(const obj of a2) {
     console.log(obj.name, obj.age);
 }
 ​
 for(const {name,age} of a2) { //配合解构运算符使用
     console.log(name, age);
 }

常用API

查找元素

  • document.getElementById(): 根据 id 值查找一个元素。
  • document.querySelector():根据选择器(.是class,#是id)查找第一个匹配元素。
  • document.querySelectorAll():根据选择器查找所有匹配元素。

修改元素

innerHTML:会解析内容中的标签。

textContent:不会解析内容中的标签。

利用模板修改

     <div class="tbody">
         //把数据动态填入此处
     </div>
     
     <template id="tp"> //模板
         <div class="row ">
             <div class="col">xxx</div>
             <div class="col">xxx</div>
             <div class="col">xxx</div>
             <div class="col">xxx</div>
         </div>
     </template>
 ​
     <script>
         let array = [
             {id: 1, name: "张三", sex: '男', age: 18},
             {id: 2, name: "李四", sex: '女', age: 22}
         ]
 ​
         const tp = document.getElementById("tp"); //拿到tp模板
         const row = tp.content; //拿到模板中所有div元素
         const [c1,c2,c3,c4] = row.querySelectorAll(".col"); //从模板所有的div元素中找.col的元素
         const tbody = document.querySelector(".tbody") //拿到一个父元素
         for (const {id, name, sex, age} of array) {
             c1.textContent = id;
             c2.textContent = name;
             c3.textContent = sex;
             c4.textContent = age;
             const newNode = document.importNode(row, true);//复制元素包括子元素
             tbody.appendChild(newNode)//添加子元素
         }
 ​
     </script>

Fetch

Fetch API 可以用来获取远程数据,它有两种方式接收结果,同步方式与异步方式。

格式:

 fetch(url, options) // 返回 Promise,并不是最终结果

同步方式:

 const 结果 = await Promise
 // 后续代码
  • await 关键字必须在一个标记了 async 的 function 内来使用。
  • 后续代码会等待结果返回再执行。

异步方式:

 Promise
     .then(结果 => { ... })
 // 后续代码                 
  • 后续代码不必等待结果返回就可以执行。
  • then可以链式调用,需要结果是一个Promise对象。

跨域问题

image.png

  • 只要协议、主机、端口号有一个不同,就不同源。
  • 同源检查是浏览器的行为,其它客户端,例如postman,它们是不做同源检查的。而且只针对 fetch、xhr 请求。

解决1(后端服务器解决):

  • fetch 请求跨域,会携带一个 Origin 头,代表发请求的资源源自何处,目标通过它就能辨别是否发生跨域。

    • 上图中,student.html 发送 fetch 请求给 tomcat,告诉tomcat我来自localhost:7070。
  • 目标资源通过返回 Access-Control-Allow-Origin 头,告诉浏览器允许哪些源使用此响应。

    • 上图中,tomcat 返回 fetch 响应,告诉浏览器,这个响应允许源自 localhost:7070 的资源使用。

解决2(前端服务器解决):

在前端服务器使用代理对象,浏览器访问的是前端服务器,前端服务器再访问后端服务器,就不会产生跨域问题。

image.png

 //1.安装代理插件
 npm install http-proxy-middleware --save-dev 
 ​
 //2.在express 服务器启动代码中加入
 import {createProxyMiddleware} from 'http-proxy-middleware'
 ​
 //3.创建代理对象,意思是访问7070/api的请求都通过代理对象。
 app.use('/api', createProxyMiddleware({ target: 'http://localhost:8080', changeOrigin: true }));

导入导出

导出:

 export const a = 10; //导出单个
 export {a,b,c} //导出多个
 export default b; //默认导出,一个js文件中只能有一个export default

导入:

 <script type="module">
     import 语句
 </script>
 ​
 import {a,c} from '相对路径' //导入单个或多个
 import * as hello from '相对路径'  //导入全部
 import hello from '相对路径' //导入默认,名字随便起,因为默认就一个。