ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Web狗的js修养-基础篇

Web狗的js修养-基础篇 js变量变量的声明方式:有var和let.举一个简单的例子var a,b,c,d100; console.log(a,d); //输出undifined和100 //因为a没有赋值具体的数值 //------------------------------------ let c 10; console.log(c); //输出10 //------------------------------------var声明和let声明有什么区别呢?var声明的作用域在局部,也就是说比如function abc(){ var a 100; b 100; } console.log(a) //报错 console.log(b) //输出100然后let声明的变量是不能重复的,就比如你var a 100; var a 10; console.log(a) //输出10 let a 100; let a 10; console.log(a) //报错const关键字用于声明常量,也就是不会被改写的一个值,比如说const a 100,你就不能再var a 10;了.当然 const a 100; a 10也是不行的.初步我们这样理解就行了.变量类型:使用typeof 可以判断一个变量的类型,比如说var a 10; console.log(typeof a);//输出 number var a 10; console.log(typeof a);//输出 string var a 10; console.log(typeof a);//输出 string var a 10; console.log(typeof a);//输出 string var a true; console.log(typeof a);//输出 boolean var a null; console.log(typeof a);//输出 object var a []; console.log(typeof a);//输出 object var a {}; console.log(typeof a);//输出 object //其他的大家见闻思意即可. //还有NaN类型-Not a Number var a undefined; console.log(typeof a);//输出undefined var a 0 / 0; console.log(a); // NaN console.log(typeof a); // number var a NaN; console.log(typeof a);//number console.log(a NaN);//false再来说一下类型转换还是来个例子吧.对结果有疑问可以自己打开f12在控制台运行代码,我认为一个比较好的方法就是自己去跑一遍,用事实说话.var a [1,2,3]; console.log(typeof a);//输出 object console.log(typeof(a123));//输出string //为什么是这个结果?因为前面的a变量是object后面加字符串的123就会转换类型为string. console.log(a123)//输出1,2,3123 //特别的,强制类型转化 console.log((Number(hello));//NaN //而 console.log(typeof(Number(hello)));//number再来说一下模板字符串.var a console.log(1) console.log(${a})//最终会输出1 //在包裹的内容中出现了${a}这是一种模板的写法 //,想想SSTI,flask的{{7*7}}-最后是49.这里同理, //a会被当做js代码执行, //从而导致SSTI.也就是说${}会将里面的内容当作代码解析.//一些内置函数 //substr(x,y) //x代表开始截取的位置,y代表截取长度 var a a,b,32esad3ed console.log(a.substr(2,4))//输出b,32 console.log(a.substring(2,4))//从第二位截取到第4位[2,4)是这样一个区间. //所以第二个输出是b, console.log(a.indexOf(a))//输出0 //indexOf只输出找到的第一个位置 //如果找不到就会输出-1 //indexOf是从左向右查询 //lastindexOf是从右向左查询 console.log(a.lastindexOf(a))//输出8 console.log(a.charAt(2))//取第2个字符也就是b //因为第一位是从0开始而不是1 console.log(a.charcodeAt(0))//取第0位的字符的ascii码值 console.log(String.fromcharcodeAt(72))//将ascii码为72的值输出数组操作var a [1,2,3,4,5,78]; a.push(hello);//在尾部插入hello console.log(a); a.pop();//取出尾部数据 console.log(a); a.shift();//取出头部数据索引整体迁移 console.log(a); a.unshift(bey);//头部插入一个bey console.log(a); a.reverse();//数组逆序 console.log(a); a.sort();//数组顺序 //也就是reverse的逆运算 aa.join()//拼接每一个字符然后用来代替中间的连接符号不然默认是, console.log(a); var b [4,5,6,7] var c [8,8,9,0] console.log(a.concat(b).concat(c));//合并a,b,c三个数组 aa.slice(2,5);//取[2,5) aa.splice(1,3)//从一开始删除3位值 aa.splice(1,0,hello)//在第一位删除0个输并插入hellojs函数js函数定义的三种形式function abs(x) { if (x 0) { return x; } else { return -x; } }若函数执行到return会立马结束并且返回return后面的值若没有return也会结束代码由正常逻辑执行并且最后会返回一个undefined。var abs function (x) { if (x 0) { return x; } else { return -x; } };//这种属于匿名函数,也就是没有函数名的函数.需要赋值给变量或者放在数组中使用a (a,b){ console.log(a,b) } a(1,2);这种方式很常见三种方式完全等价既然三种方式完全等价那么我们从第二种方式来看他是不是定义了一个匿名函数function(x){...}然后将这个匿名函数赋给了变量abs所以abs可以调用这个函数。因此第一个定义方法我们可以看作abs()函数是一个函数对象而abs是变量名最后这个函数赋值给了abs变量。在js种函数调用参数可以有多个即使你的形参变量只有一个他只会接收第一个参数变量比如说这个地方的abs(10,abcc)会返回10abs(10,-3)还是返回10。传入参数比形参少也可以他会返回undefined计算结果是NaN和我们的RCE里面提到的无字母形很像构造出NaN取N然后进行加减运算得到其他的字母。说一个特殊的比较.function test(){ var a1,b2; return ab,b--,ba; } console.log(test()); //这样我们看到返回值有多个的时候他会返回最后一个表达式,但是 //前边的表达式依然会执行. //比如这个地方返回的就是ba最终的答案也就是2。如果一个函数不接受参数比如test()但是你在调用的时候仍然可以加参数运行。这是因为有一个arguments的变量在接收参数,而且是以键值对的方式存储的。比如function pollu(){ console.log(arguments); } //调用 pollu(1,23,1234,6)//自执行函数,认住它 (function (){ console.log(111) })(); //第一种写法把整个括起来最后加(). (function (){ console.log(222) }()) //第二种写法,先加()再把整个括起来 !function (){ console.log(333) }() //第三种写法写好函数加()再在头部加!函数提升,顺便补充一下之前没说到的变量提升//变量提升 console.log(a); var a 10; //最终结果是输出了10. //因为在var定义变量的时候会有一部预加载. //这一步相当于会在整段代码的前面默认执行一次 var a 10. //这种现象称为变量提升 //所以实际执行顺序是 var a 10; console.log(a) //函数提升 function ppp(){ console.log(111) } ppp(); function ppp(){ console.log(222) } ppp(); //最后实际执行的输出结果如下 // 222 222. //本质原因在于函数的定义会提升到使用之前所以顺需也就变成了 function ppp(){ console.log(111) } function ppp(){ console.log(222) } ppp(); ppp();js循环//先看最简单的for循环 for(i0;i10;i){ console.log(111) }//这种形式和c语言一模一样包括哪些参数的省略也是 //再来看复杂一点的 for(i0,j0;i5,j10;i,j){ console.log(i,j) } //最后的停止条件是j10 //因为,表达式是以最后一个条件主导 ----------------------------------------------- //for-in循环,主要用来遍历数组 a [1,a,hello,123143] for (i in a){ console.log(a[i]) }//遍历输出a的所有值 ----------------------------------------------- //while循环 i 0; while(i10){ console.log(i); i; } //也和c语言一样,do-while也是一样的这里不举例了其他的像if-else等条件语句就不多说了给个简单的例子,因为和c语言大差不大if(i10){ console.log(当前小于十); } else if(i10 i20){ console.log(当前处于10和20之间); } else { console.log(已经比20大了) } //switch也是一样的 t 1; switc(t){ case 1: console.log(1); break; case 2: console.log(2); break; case 3: console.log(3); break; default: console.log(没有); } //------------------------------------ t 6; switc(t){ case 1: case 2: case 3: console.log(3); break; default: console.log(不是3); }//当然这个例子有点鸡肋,也就是多个case可以公用一个结果时间戳的创建:t Data.now() t new Dta().getTime()说一下forEachforEach 是 JavaScript 数组Array原型上的一个方法用来遍历数组中的每一个元素并对每个元素执行一次回调函数js对象//对象 数据变量 方法(函数) var t { name:coinsheep, age:19, gender: male, S [1,3,5], code: function(){ console.log(coding ing....) }, hobby: { singing: 唱 dancing: 跳 rap:咳咳,不串了 } }//访问方式是访问键然后取值,其实和c语言的结构体很类似啊 console.log(t.hobby.rap)//使用.访问 console.log(t[hobby][dancing])//使用[]访问 console.log(t.hobby[dancing])//使用[]和.混合访问 //可以用hasOwnProperty(需要验证的键)来看有没有这个属性 //用tostring()方法来转化为字符串简单的混淆。这也是web面的挖掘最恶心的一点,js文件巨多,还有人做混淆。//简单的混淆 //你就比如说我这里有一串字符串 s coinsheep s1 s.substring(2,6) //这里的参数2,6我是不是可以用indexOf来取对应的字母下标来替换比如 c s.indexOf(i) d s.indexOf(e) //那我原本是不是就可以写成下面这种形式 s1 s.substring(s.indexOf(i),s.indexOf(e)) //就很唬人,那我们继续复杂 t s.substring(c,d) s2 substring.split()//把substring当字符串处理 s3 [] s2.forEach(function (a,b){ s3.push(a.charCodeAt(0)); })//转化substring为ascii码. console.log(s3) //结果如下 //[115, 117, 98, 115, 116, 114, 105, 110, 103] // s u b s t r i n g //有可能我们实际看到的只是如下代码 s4 [115, 117, 98, 115, 116, 114, 105, 110, 103]; s5 []; for(i 0;is4.length;i){ s5.push(String.fromCharCode(s4[i]));//substring还原为字符串 } console.log(s[s5.join()](c,d));//实际等价于s.substring(2,6)基础类型和引用类型基础类型: number string null undefined boolean symbol(独一无二的值)引用类型: object其实基础类型我们在之前的变量中已经提到过了这里主要的区别呢在于值传递和地址传递基础类型值传递,引用类型地址传递.//值传递的例子 var a 10; var b a; a 20; console.log(a,b); //结果为a是20,b是10. //地址传递 var a { value: 10 } var b a; b.value 20; console.log(a.value,b.value); //两个值都会变成20. //因为对象这个类型,object在赋值传递时传递的是它的地址,也就是说我们以键值对来类比一下。 //key:value这是一个正常的键值对. //值传递就是改变的value.只改变值. //地址传递相当于是我把键给你了,然后你再变了这个键下面的值. ------------------------------------------------------------------------------------- //举一个统一的例子 var a { value: 10 } var b 20; var test function(a,b){ a 100; b 90; } test(); console.log(a.value,b); //输出就是 100 20; //因为这里b是基础类型,是值传递而a是引用类型是地址传递作用域全局作用域全局作用域可以让用户在任何位置进行调用需要注意的是最外层函数和在最外层函数外面定义的变量拥有全局作用域所有未定义直接赋值的变量会自动声明为拥有全局作用域所有window对象的属性也拥有全局作用域函数作用域函数作用域也就是说只有在函数内部可以被访问当然函数内部是可以访问全局作用域的。块级作用域块级作用域则是在if和switch的条件语句或for和while的循环语句中块级作用域可通过新增命令let和const声明所声明的变量在指定的作用域外无法被访问。我们通过一段代码来理解var a 3; function test(//函数作用域){ var a 1; console.log(内部,a) //块级作用域 } test(); console.log(外部,a); 这种情况呢,我们可以看到函数内部有一个var a 1.所以最后内部是1. 内部 1 外部 3 内部访问的是函数内部局部变量,而外面的console.log访问的是全局变量 //第二种情况 var a 3; function test(a){ //var a 1; console.log(内部,a) //块级作用域 } test(2); console.log(外部,a); 这种情况呢内部是2外部是1,其实就是内部的这个有争议为什么呢.内部的console.log是 先找有没有块级作用域声明如果没有那么就去找参数部分,也就是函数作用域 //第三种情况 var a 3; function test(){ //var a 1; console.log(内部,a) //块级作用域 } test(2); console.log(外部,a); 内外部都是3,没有形参接收所以即使传了参数也只会交给arguments. //第四种情况 var a 3; function test(){ a 1; console.log(内部,a) //块级作用域 } test(2); console.log(外部,a); 内外部都是1.我们记住找变量的时候是从块级作用域找到函数作用域找到全局作用域 函数内部执行 a 1 时没有用 var/let/const 声明。 JavaScript 会沿着作用域链往外找有没有叫 a 的变量。 找到了外层的全局变量 a于是直接修改了全局的 a把它从 3 改成了 1。 所以函数内外打印的都是被修改后的值 1。 其中我们注意未规范赋值(没有let/var/const声明直接赋值的变量)和window.xxx都是全局作用域常见的window属性WindowWindow对象表示浏览器当前打开的窗口。Document载入浏览器的HTML文档。NavigatorNavigator对象包含的属性描述了当前使用的浏览器可以使用这些属性进行平台专用的配置。LocationLocation对象包含有关当前URL的信息Screen每个window对象的screen属性都引用一个Screen对象。Screen对象中存放着有关显示浏览器屏幕的信息。HistoryHistory对象包含用户在浏览器窗口中访问过的URL。其实我们这么说有点抽象window全局对象换句话说就是你的js里面所有的内置api函数可以用window来调用比如我们写代码的时候有的alert()可以直接在控制台厘米那window.alert(a)来调用它。注意一个点。JS 基础语法规则if、for、function、变量定义、运算符是语言本身不属于window对象。只有全局 API、浏览器功能、全局内置对象才挂在window上。闭包闭包即函数内部可访问外部无法访问.闭包 函数 它能访问到的外部变量来个例子(function jiami(){ function encrypt(){ console.log(加密逻辑如下) } encry encrypt })() encry(); 可以看到这里是一个自执行函数然后他的内部还有一个函数encrypt()然后那是第二层嘛.正常我们 直接调用jiami()是不会到第二层的函数的,也就是说他进不了内部.其实理解为function定义了一个 新的作用区域然后这个工作区域只干了一件事就是定义了一个函数encrypt()然后并没有调用这个函数. 当然我们加上了一个encry encrypt后就可以了. //当然也可以这样写 encry (function jiami(){ function encrypt(){ console.log(加密逻辑如下) } return encrypt })() encry();闭包的形成条件有一个函数嵌套在另一个函数里面内部函数引用了外部函数的变量内部函数被返回或通过其他方式传到外部去使用满足这三点闭包就形成了。再来个例子function createCounter() { let count 0; // 外部无法直接访问 return { increment() { count; }, getCount() { return count; } }; } const counter createCounter(); counter.increment(); console.log(counter.getCount()); // 1 // console.log(counter.count); // undefined访问不到 function makeMultiplier(x) { return function(y) { return x * y; }; } const double makeMultiplier(2); const triple makeMultiplier(3); console.log(double(5)); // 10 console.log(triple(5)); // 15 for (var i 1; i 3; i) { setTimeout(function() { console.log(i); // 全部输出 4经典问题 }, 1000); } // 用闭包解决 for (var i 1; i 3; i) { (function(j) { setTimeout(function() { console.log(j); // 1 2 3 }, 1000); })(i); }面向对象//构造器创建实例对象 function person(name,age,sex,job){ this.name name; this.age age; this.sex sex; this.job job; this.work function (){ console.log(${this.name}的工作是${this.job}); } } coinsheep new person(coinsheep,19,male,cs2) coinsheep.work();//输出coinsheep的工作是cs2 //原型继承,类比java的继承 person.prototype.sport function (hobby){ this.hobby hobby; console.log(${this.name}的爱好是${this.hobby}) } zhh new person(zhh,20,male,cs2) zhh.sport(coding); //输出zhh的爱好是coding;这里就说一下这个吧.person.prototypezhh.__proto__.这样看object(所有对象类型的祖宗类)-person(自定义的一个对象,继承自object)-sport(继承自person)好比你的爷爷生了你父亲,你的父亲生了你而刚好你们三代人右手掌心都有一颗痣。你可以看作这颗痣是你从你父亲那儿继承来的属性,你父亲从你爷爷那儿继承来的属性。(当然这样说有点问题但是大致是这个意思)顺带说一下我们之前说的原型链污染我们来缕一缕原型链污染,原型链污染的通用思路就是一直向上找父类一直找到object这个所有对象类型的父类然后污染object的属性使所有的对象类型都带上所污染属性,那么这个地方的话就相当于污染了person的原型,所有person的子类和它的实例,所有实例都会带上sort属性.function person() {}//函数对象 console.log(typeof person); // function它是函数 console.log(typeof person.prototype); // object它是普通对象 console.log(person.prototype instanceof Object); // true const p new person(); console.log(p instanceof person); // truep 才是实例 console.log(p.__proto__ person.prototype); // truethis:浏览器中依旧是谁调用它就指向谁,对象中就指向对象,否则指向window在Node.js种则指向{}或global.以下的代码以nodejs环境为主function test(){ console.log(this); } test();//输出的时global的信息 name coinseheep; function test(){ console.log(this.name); } test();//输出coinsheep. 因为global和winow一样时一个全局属性,而我们声明name的时候name已经在global之下了 然后this我们说是指向global嘛所以就是this.name相当于global.name var name coinseheep; function test(){ console.log(this); } test();//输出undefiend因为现在加了var 他就不在global之下了 console.log(this)//输出{}js常用apicall在调用的函数后面可以跟上来改变this的值,后面正常拿参数.apply在调用的函数后面可以跟上来改变this的值,后面数组拿参数.箭头函数补充://我们之前说的箭头函数形式是 a (a){ console.log(a); } a(); //这个形式我们继续简化 //只接收一个参数的时候可以省略括号 a a{console.log(a)} a(); //还可以继续就这样呗 a (a,b){return (ab)} //再进一步简化 a (a,b) a b; // //关于this的指向问题 //箭头函数种this的指向不再是调用它的函数本身而是调用它的函数的父级对象 //例如 coinsheep { num: 22, f1 : function(){ console.log(this.num) }, f2: (){ console.log(this.num) } } coinsheep.f1()//输出22 coinsheep.f2()//输出undefined因为他会指向它的调用者的调用者setTimeout() setInterval()//定时器//定时器 x setTimeout(function name(params){ console.log(1111) },3000)//第一个参数这里放了一个函数,后面是3000毫秒后才开始执行函数的内容。 y setInterval(function(){ console.log(2222) },3000)//第一个参数这里放了一个函数,后面是每3000毫秒后就执行一次。计时器启动后会返回一个数字来作为计数器的编号.eval:老演员了可以执行指令,RCE的审计点.可以加密代码//加密前 a 1; b 2; sum ab; console.log(sum); //加密后 eval(function(p,a,c,k,e,d){efunction(c){return(ca?:e(parseInt(c/a)))((cc%a)35?String.fromCharCode(c29):c.toString(36))};if(!.replace(/^/,String)){while(c--)d[e(c)]k[c]||e(c);k[function(e){return d[e]}];efunction(){return\\w};c1};while(c--)if(k[c])pp.replace(new RegExp(\\be(c)\\b,g),k[c]);return p}(2 0;3 1;6 23;4.5(6);,62,7,1|2|a|b|console|log|sum.split(|),0,{})) //还原eval加密也很简单. 我们直接去掉eval然后运行之后的代码就能得到eval执行的具体指令异步编程这里我决定先说一下异步编程,同步编程(syn)代码一行一行执行,很规律,顺序。异步编程(asyn).允许在执行等待其他结果的同时执行别的任务,不阻碍程序运行.在服务端也就是Node.js中有三种主要方式分别是回调函数,promise,async/await。先来看回调函数:你比如说你有一个input.txt的文件,文件里面内容是:这是你的文件以下是一个阻塞的例子:var fs require(fs); var data fs.readFileSync(input.txt); console.log(data.toString()); console.log(程序执行结束!);结果如下$ node main.js 这是你的文件 程序执行结束!以下是一个非阻塞的例子:var fs require(fs); fs.readFile(input.txt, function (err, data) { if (err) return console.error(err); console.log(data.toString()); }); console.log(程序执行结束!);结果如下:$ node main.js程序执行结束!这是你的文件可以看到阻塞的情况在进行读取文件内容操作的时候需要等待他读取完成才最后console.log。而非阻塞的情况则会独立运行console.log而不是等待文件读取完成。回调主要是一种思想,我们可以清楚地看到两次代码的差距,除开err处理,我们可以看到第二次在参数列表中出现了一个函数。我们之前说过这种嵌套的形式外部函数不会直接调用到内部函数,我们要用一个变量来接收它的返回值然后再调用一次。此时虽然不在函数体里面。而是在参数中,所以他只是被放进了Arguments中然后等当前函数体执行完后再去做参数中的函数。也就是说回调其实是把函数放进另一个函数参数等待调用函数时,执行函数自己然后再去把作为参数的函数执行。那么这个然后是多久呢?比如const fs require(fs); console.log(开始); // ① fs.readFile(test.txt, utf8, function(err, data) { console.log(异步回调执行了); // ④稍后执行 }); console.log(结束); // ② // 这里如果再写很多代码... // ③执行顺序:开始结束 这里如果有其他同步代码也会全部先跑完异步回调执行了也就是说异步会比同步慢执行。同步代码马上执行挡在路上的都会先跑完。异步回调被放到旁边排队等当前所有同步代码执行完毕后并且任务真正完成了才会被拿出来执行。回调地狱:纯回调的多重嵌套。promise:从思想层面上来说其实promise优化了纯回调的错误处理,纯回调的错误处理我们使用if(err)的方式,容易嵌套多层。promise更注重结果而不是像纯回调一样把结果和过程混杂在一起.promise把每次过程的结果分为三种状态:pending进行中fulfilled成功rejected失败)也就是说我们最终一定会直到每一个步骤执行的结果他只可能是三者中的某一种。成功就返回内容,失败就获取错误信号。Promise 把异步操作封装成一个「会在未来产生结果的对象」。它通过三种状态清晰地表达进度通过 .then / .catch 让我们以更线性、更可控的方式处理结果和错误从而同时解决了「回调地狱」和「错误处理分散」的问题。async/awaitasync/await 让你用写同步代码的方式来写异步代码。它让异步逻辑看起来几乎和普通的从上到下执行的代码一样。// 1. 在函数前面加 async这个函数就会自动返回 Promise async function main() { // 2. 在 Promise 前面加 await等待它完成 const data await somePromise(); console.log(data); }三种写法对比.// 1. 纯回调最难维护 fs.readFile(a.txt, (err, data) { fs.readFile(b.txt, (err, data2) { // 回调地狱... }); }); // 2. Promise 链比回调好 fs.promises.readFile(a.txt) .then(data fs.promises.readFile(b.txt)) .then(data2 { /* ... */ }) .catch(err {}); // 3. async/await最推荐 async function run() { try { const data await fs.promises.readFile(a.txt); const data2 await fs.promises.readFile(b.txt); // ... } catch (err) {} }
返回列表