# javascript-demo **Repository Path**: CandyPop/javascript-demo ## Basic Information - **Project Name**: javascript-demo - **Description**: javascript的学习笔记 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-02-10 - **Last Updated**: 2024-12-01 ## Categories & Tags **Categories**: Uncategorized **Tags**: JavaScript ## README #### JavaScripts学习笔记 本笔记不会记录简单的知识,类似语法和控制语句等,主要记录一下javascript一些较为高级的知识和使用中的一些细节。 ---- ##### Null ```javascript var a = null; console.log(typeof a); // "object" // 与其他的的String,Number不同,被声明为null的变量,他的类型是对象,即便他是空的,没有任何属性和参数的变量。 ``` ##### Undefined ```javascript var b; console.log(b==undefined); // true console.log(typeof b=="undefined");// true //如果声明一个变量,但是没有被赋值,他就只有一个属性,就是undefined ``` ##### Number ```javascript var a = Number.MAX_VALUE + 1 ; console.log(a);// Infinity 正无穷 -Infinity 负无穷 一个字面量 console.log(typeof a);// number var a = "abc"*"vbb"; console.log(a); // NaN not a number 不是一个数 也是一个字面量 console.log(typeof a);// number var a = Number.MIN_VALUE; // 5e-324 0.000...5最小的数 // js的小数计算不能够保证绝对的精确,原因在于最后需要转化为2进制,而0.1表示10分之一是不好表示的,javascript在这方面并没有解决,所以设计精度比较重要的计算,请在服务器上计算完成后返回页面。 ``` ##### 强制类型转换 * 将其他数据类型转化为String * **方法1:**调用被转换数据类型的toString()方法,**但不会改变原本的值**,会返回转化为String类型的副本。 ```javascript var a = 123; var b = a.toString(); ``` * **注意**:如果a是null,或者undefined,调用toString会报错。 * **方法2:** 调用String()函数转换 ```javascript var a=123; var b=String(a); console.log(typeof b);// string ``` * 和上述的toString在于,Number类型和Boolean类型的对象调用String方法实际上也就是调用目标的toString方法,但是当目标对象是null或者undefined的时候。他就不会调用toString方法,同时也不会报错,而是返回对应的字符串结果。 ```javascript var c; var e=null; console.log(String(c));// "undefined" console.log(String(e));// "null" ``` * 将其他数据类型转化为Number * **方法1:**调用Number函数 ```javascript var a = "123"; a = Number(a); console.log(typeof a);//number console.log(a);// 123 ``` * 如果是纯数字的字符串,则直接转换为字符串 * 如果字符串中有非数组内容,则转换为NaN,但类型是number * 如果字符串是**空字符串**或者**全是空字符串**,则转换为0,类型是number * 如果对象是Boolean类型,true转换为1,false转换为0,类型是number * 如果对象是null,则会转换为0,类型是number。 * 如果对象是undefined,则转换为NaN,类型是number。 * 但是如果读取类型`123px`这种可能是从css读取而来的样式,直接使用Number会变成NaN,因为Number转化的字符串,不能含有任何非数字的字符,这也是一大缺点。 * **方法2:**调用parseInt()和parseFloat()方法,该方法会对字符串进行解析,对非数字内容进行处理 ```javascript var a = "123px"; a = parseInt(a); console.log(a);// 123 console.log(typeof a);// number // 该方法会一个一个字符解析,遇到非数字停下来 var a = "123px1231243"; // 这个结果还是123,因为到p的时候,就停止解析 var a = "a123"; //而这个一开头就停止解析,所以会返回NaN。 ``` * 如果对非String变量使用parseInt()或者parseFloat(),它会先将其转换为String,然后再操作 ```javascript var a = true; // 会先将 a 变成字符串 true 即 "true" // 但是"true"不是一个准确的数字,所以结果是一个NaN a = parseInt(a); ``` ##### 对象 ```javascript /** * * 创建对象 * * 使用new 关键字调用的函数,是构造函数constructor 专门来创建对象的函数 * * * */ var obj = new Object(); // 返回 object console.log(typeof obj); obj.name = 'pop'; //删除属性 delete obj.name; // in 运算符 判断对象里面是否有属性 console.log("name" in obj); /** * 基本数据类型 * String Number Boolean Null Undefined * * 引用数据类型 * Object * */ // 对于基础数据类型而言,即便被赋值,彼此的数据都是独立 var a = 123; var b = a; a++; console.log(a,b); // 对应引用类型,由于是内存地址指向,其它都会改变 var o = new Object(); o.name = '123'; var b = o; b.name = 3345; console.log(o.name,b.name); /** * 基本类型是保存在栈中, * 引用类型在堆内存中创建对象,持有引用 * */ /** * * **/ ``` ##### 函数 ```javascript /** * 函数也是一个对象 * * typeof 检查一个函数,会返回function * **/ var fun = new Function("console.log('hello 这是我的第一个函数')"); fun(); // 函数的参数可选 function fun3(){ function fun4(){ } return fun4; } var f = fun3; f();//执行了fun4 // 立即执行函数 // 匿名这样写会报错 (function(){ })(); //加上括号可以不报错,表示他是个整体,立即执行函数 ``` ##### 作用域 ```javascript // 在全局作用域中创建的变量,都会作为window的对象属性保存 var a = 10; var b = 22; console.log(window.a); function fun(){ } console.log(window.fun()); // 使用 var 关键字声明的变量,会在所有代码执行之前被声明 console.log(c); var c = 123; //如果不写var 直接写 c =123 会报错,说没有定义 // 函数的声明提前,声明形式 func(); // 这样的声明函数,在哪里写都是一样的,会被优先调用 function func(){ } // 但是这种,适用于上面的var变量,虽然会创建,但是在执行到这行之前都是undefined所以,你调用也会报错。 var fun2 = function(){ } ``` ##### This ```javascript /** * 解析器在调用函数每次都会向函数内部传入一个 * 隐含的参数,就是this对象,this指向一个对象 * 这个对象我们成为函数执行的上下文对象 * * 根据函数的调用方式不同,this会指向不同的对象 1.以函数的形式,this 永远都是window 2.以方式的形式调用时,this就是调用方法的那个对象 * **/ function fun(a,b){ // window console.log(this); } fun(123,456); var obj = { name:"孙悟空", sayName:fun } // 这里对象会变成obj obj.sayName(); ``` ##### 对象工厂 ```javascript var obj = { name:'孙悟空', age:18, sayName:function(){ alert(this.name); } } /** * * 使用工厂方法创建对象 * 通过该方法可以大批量的创建对象 * */ function createPerson(){ var obj = new Object(); obj.name="swk"; //.... return obj; } function createPerson0(name,age,gender){ var obj = new Object(); obj.name="swk"; //.... return obj; } /** * 使用工厂方法创建的对象,使用的构造函数都是object * 无法区别,用构造函数创建 * * * 创建一个构造函数,专门来创建Person对象 构造函数就是一个普通的函数,创建方式和普通函数没有区别 不同的是构造函数习惯上首字母大写 普通函数是直接调用,而构造函数需要使用new关键字来调用 1.立即创建一个新的对象 2.将新建的对象设置为函数中this 3.逐行执行函数中的代码 4.将新建的对象作为返回值返回 * **/ function Person(){ this.name = "6666"; } // var per = Person(); var per = new Person(); console.log(per); // true console.log(per instanceof Person); // true console.log(per instanceof Object); ``` ##### 原型方法 ![1649683156855](./img/1649683156855.png) ![1649682557610](./img/1649682557610.png) ```javascript // 写法1 var obj = { name:'孙悟空', age:18, sayName:fun } /** * 尽量不往全局作用域定义函数或者变量, * 容易污染作用域的命名空间 * 同时也很不安全 * */ function fun(){ alert(this.name); } function MyClass(){ } var mc = new MyClass(); var mc2 = new MyClass(); /** * 原型 prototype * * 我们所创建的每一个函数,解析器都会向 * 函数添加一个属性prototype * 也就是我们的原型对象 * * 如果函数作为普通函数prototype没有任何作用 * 当函数以构造函数形式调用时,他所创建的对象 * 都会有一个隐含的属性,指向构造函数的原型对象 * 我们可以通过__proto__来访问属性 * * * 原型对象都相当于一个公共的区域,所有同一个类的实例 * 都可以访问到这个原型对象,统一设置到原型对象中 * **/ // true 这两个是同一个对象 console.log(MyClass,'$',mc); console.log(MyClass.prototype == mc.__proto__); /** * 当我们访问对象的一个属性或方法时,它会 * 现在对象自身中寻找,如果有就直接使用 * 如果没有则会去原型对象中寻找吗,如何找到 * 则直接使用。 * * **/ ``` ![1649684125538](./img/1649684125538.png) ```javascript // 使用in检查对象中是否含有某个属性时,如果对象中没有,但是原型里有,也会返回true console.log("name" in mc); // 如果使用对象的hasOwnProperty来检查对象自身中是否含有该属性 // 使用该方法只有当对象自身中含有属性时,不包括原型,才会返回true console.log(mc.hasOwnProperty("age")); // hasOwnProperty 方法我们也没定义过,但是却可以使用,是否也是原型里的呢 console.log(mc.hasOwnProperty("hasOwnProperty")); // 还是false console.log(mc.__proto__.hasOwnProperty("hasOwnProperty")); /** * 原型的对象也是对象,所以他也有原型, * 当我们使用一个对象的属性或者方法时,会先在自身中寻找 * 1.自身中如果有,则直接使用 * 2.如果没有则去原型的原型中寻找,直找到Object对象的原型 * 3.Object对象的原型没有原型,如果在Object中已然没有找到,则返回undefined * * **/ console.log(mc.__proto__.__proto__.hasOwnProperty("hasOwnProperty")); ``` ##### 数组 * 内建对象 * 内置的 Array之类的,由浏览器定义 * 宿主对象 * es标准定义的对象 * 自定义对象 * 自己new出来的对象 ```javascript ``` ##### call和apply ```javascript /** call()和apply() -这两个方法都是函数对象的方法,需要通过函数对象来调用 -当对函数调用call()和apply()都会调用函数执行 -在调用call()和apply()可以将一个对象指定为第一个参数 此时这个对象将会成为函数执行的this **/ var obj = {name:'obj',say:function(a,b){ console.log(this); }}; var obj2 = {name:'obj2'}; function fun(){ console.log(this); } fun.call(obj2); //可以自定义this是谁,也意味着可以指定上下文是谁 fun.apply(obj); //你也可以通过这个方式强行切换this指向 obj.say.apply(obj2);//这个输出是obj2 //你通过这个方式调用,第一个永远是this的指向,后面跟着是形参的 obj.say.call(obj2,3,2); //唯一区别 obj.say.apply(obj2,[3,2]); ``` ##### this的阶段性总结 * 当函数调用的时候,this指向的一定是window对象 * 当通过某个对象调用时,this指向是那个对象 * 当使用构造函数创建对象的时候,this指向的是新建的实例对象 * 当使用apply和call来调用函数的时候,this指向的是传入的首个对象 ##### arguments ```javascript /** 在调用函数时,浏览器每次都会传递两个隐含的参数 1.函数的上下文对象this 2.封装实参的对象arguments -arguments是一个类数组对象,他可以通过索引来操作对象,也可以获取长度 -在调用函数时,我们所传递的实参都会在argumenets中保存 -arguments.length 可以获得的形参的数量 -arguments[n]可以通过索引获得对象 -arguments.callee 指向当前正在执行的对象 **/ function fun(a,b){ //false console.log(arguments instanceof Array); // false console.log(Array.isArray(arguments)) console.log(arguments.length); console.log(arguments[1]); console.log(arguments.callee); // true console.log(arguments.callee==fun); } ``` ##### 包装类 基本数据类型 -String Number Boolean Null Undefined ``` String("hello") 可以将基本类型转换为String Number(3) Boolean(true) typeof 和原本有所区别,会变成object ``` ##### 正则表达式 计算机可以根据正则表达式,来检查一个字符串是否符合规则,将字符串中创建规则的内容提取出来。 ```javascript ``` #### 高级篇 ###### 基础总结深入 - 基本类型 - String - 任意字符串 - Number - 任意的数字 - Boolean - true/false - undefined - undefined - null - null - 对象引用 - Object - 任意对象 - Function - 一种特别的对象(可以执行) - Array - 一种特别的对象,数值下标,内部数据是有序的 - 判断 - typeof - 类型判断,数据类型的字符串表达式 - instanceof - 实例判断,对象的具体类型 - === - 全等,不会类型转换 * undefined与null的区别 * undefined代表了定义了未赋值 * null 定义并赋值了,只是值为null * ```javascript var b = null // 初始赋值为null,表示将要赋值为对象 ``` * 什么时候给变量赋值为null呢? * 初始赋值,表示将该变量赋值为对象 * 结束时,让对象成为垃圾对象,被垃圾回收器回收 * 严格区别变量类型与数据类型 * 变量类型(变量内存值的类型),声明的变量 * 基本类型:保存的就是基本类型的数据 * 对象类型:保存的是地址值 * 数据类型 * 基本类型 * 对象类型 ##### 数据-变量-内存 ![1649944158343](./img/1649944158343.png) 内存:存储数据的临时空间 变量:存在变量名和变量值,变量名用于查找该变量在内存中指向的地址,变量值用于存储变量的数据/内容,可能是基本类型,也可能是对象类型。 ##### 函数 ![1650034845297](./img/1650034845297.png) ```javascript var obj = {}; function fun(){ this.name = "pop"; } //即使obj没有fun方法,也可以调用,这是js的强大之处 fun.call(obj); console.log(obj.name);// pop ``` ##### 回调函数 ###### 什么是回调函数 * 你定义的 * 你没有调用 * 但最终它执行了 ###### 常见的回调函数 * dom事件回调函数 * 定时器回调函数 * ajax请求回调函数 * 生命周期回调函数 ###### IIFE (Immediately-Invoked Function Expression) 一种特殊的执行函数,他的匿名的,我们需要用括号将它看做一个整体后调用。 ```javascript (function(){ // 内容 // 匿名函数 var a = 3; console.log(a+3); })();// 这样可以立即执行 ``` 作用,隐藏实现,不会污染外部命名空间(一般是全局) ```javascript var a = 4 console.log(a) //你可以通过前面加分号,来和后面区别开 ;(function(){ var a=1 function test(){ console.log(++a); } // 向外暴露了一个方法,但是具体细节在匿名函数被隐藏 window.$ = function(){ return { test:test } } })(); $().test(); ``` ###### This this每个函数都持有的隐藏变量,指向不确定。 任何函数本质上都是通过某个对象来调用,所有函数内容都有一个变量this; ###### 分号 需不需要加分号 是否加分号是编码风格问题,没有应该不应该,只有自己喜不喜欢 以下两种情况,不加分号会有问题 * 小括号开头的前一个句话 ```javascript var a = 3 ;(function(){ //... })(); ``` * 中方括号开头的前一条语句 ```javascript var b = 4 ;[1,2].forEach(function(){ ); ``` ##### 函数高级 ###### prototype ![1650462647904](./img/1650462647904.png) ```javascript function Fun(){} // 函数原型构造函数指向函数 console.log(Fun.prototype.contructor === Fun); ``` ![1650462962366](./img/1650462962366.png) 假设一个函数名叫做Type,Type的prototype属性指向Type的原型对象,而,Type的原型对象的构造函数指向函数Type。相互引用。 ![1650463201660](./img/1650463201660.png) ###### 原型链 ![1650465369849](./img/1650465369849.png) ![1650465531651](./img/1650465531651.png) 原型链其实是**隐式原型对象**,他通过不断通过多个实例对象的隐式原型对象构成一条链来查找属性,这条原型链的尽头是object的`__proto__`,他是null。 ![1650466605024](./img/1650466605024.png) ![1650466585510](./img/1650466585510.png) 首先明白一点,prototype属性只可能属于构造函数,而`__proto__`属性只可能属于实例对象,为了我们可以这样认为。当我们这样定义的时候 ```javascript var test = function(){} function test(){} if(test instanceof Function){ //true } // 是否意味着,test其实是Function构造函数的实例对象呢 var test = new Function(); // 又因为test是实例对象,所以他肯定是有__proto__隐式原型对象属性,那么他指向的一定是Function的显式原型对象,对此我们可以理解为,所有的函数的隐式原型都指向一个对象,就是Function的显式原型对象。 // 对此,由于Function也是一个构造函数,所以有显示原型对象属性是必然的,但是如果你的Function是一个构造函数的同时,是否也意味着你也是一个函数实例对象呢? Function = new Function(); //所以,Function其实是个很特殊的函数,他既是自己的实例,也是自己的构造器,他自己创建了自己,因此,他的同时拥有了prototype和__proto__属性,并且都指向同一个对象。 ``` ###### instanceof ``` 实例对象 instaceof 构造函数 请注意,当你使用instanceof这个关键字的时候,无论你的左右写了什么 左边固定视为 实际对象,右边固定视为构造函数 根据走原型链的标准,实例对象看隐式原型,构造函数看显示原型,当他们的引用相交的时候,返回true,否则返回false 判断一个对象是否是某个实例的时候,实际上是判断这个对象的隐式原型对象,是否在对应的构造函数原型链上,如果是,那么就会返回true ``` ![1651158186632](./img/1651158186632.png) ![1651158229448](./img/1651158229448.png) ``` Object instanceof Function true Object instanceof Object true Function instaceof Function true Function instanceof Object true Object instanceof Foo false ``` ![1651160244287](./img/1651160244287.png) 以`Object instanceof Function` 为例子 我们之前说过左边被视为实例对象,那么Object的看做实例对象的话,那么就要去找他的隐式原型,Object是Function构造方法的实例,根据实例对象的`__proto__`指向构造函数的`prototype`的特点,我们找到Function.prototype 接着来看右边,右边是Function看做构造函数,构造函数那么去找他的显示原型对象,也就会Function.prototype 这两条原型类相交,那么返回true 以`Object instanceof Foo` 为例子,Object被视为实例,根据自身隐式原型找到Function构造器的显示原型,又因为任何构造器的显示原型都是一个空的object对象,所以我们根据`Function.prototype.__proto__`找到Object.prototype。 来看到Foo,Foo被视为构造器,那么找到Foo.prototype,**构造器只走一步,不会往下走**。所以这是一条无法相交的链路,返回false ![1651160844747](./img/1651160844747.png) ###### 执行上下文 ```javascript var a = 3; function fn(){ console.log(a); var a = 4; } fn() // a 输出undefined 变量提升 // 定义成函数的对象,总是被优先加载到window对象 ``` * 变量声明提升 * 通过var定义的变量,在定义语句之前就可以访问到 * 值是undefined * 函数声明提升 * 通过function声明的函数,在之前就可以直接调用 * 值 函数定义的对象 * 问题:变量提升和函数提升如何产生的 ![1653315076783](./img/1653315076783.png) ![1653316676924](./img/1653316676924.png) ![1653317390974](./img/1653317390974.png) ![1653317640265](./img/1653317640265.png) 每次执行函数的时候,会创建栈来存放执行上下文,后进先出,全局上下文,也就是window由浏览器创建,函数执行的上下文,在执行函数的时候创建,执行完弹出,销毁 ###### 作用域与作用域链 ![(t)1653404603708](./img/1653404603708.png) 块作用域 ```javascript if(true){ var c = 3; } console.log(c); //这个c是可以访问的,{} 这个括号里面的作用域被称为块作用域,外面的可以访问 // 但是类似java,这样是访问不到的 ``` ![1653405144539](./img/1653405144539.png) 作用域链,作用域形成的一串链,在当前作用域没找到会向上查找,找不到就会undefined ![1653405451437](./img/1653405451437.png) ##### 闭包 一个平时使用很容易出问题的点 ```javascript // 循环的监听 var btns = document.getElementByTagName("button"); for(var i = 0,len = btn.length -1;i