JavaScript最底层的那些事
- js中的数据类型分为两种:
- 原始值(Number String undefined null)它是栈数据
- 原始值如果把一个a的值给另一个b,如果改变a的值,但是b的值不会变
因为在将a的值赋给b的时候,其实是将a的的数据复制给b,他们;两个的数据是一样的,但是,他们的地址是不一样的
- 原始值如果把一个a的值给另一个b,如果改变a的值,但是b的值不会变
- 引用值(array Object function...data RegExp) 它是堆数据
- 原始值如果把一个a的值给另一个b,如果改变a的值,但是b的值也会变
因为在将a的值给b的同时,其实是将a数据的地址直接赋值给b,所以a和b现在指向的地址都是相同的数据,当a或者b在改变的时候,a和b都在变
但是,如果将a的值赋值给b后,a又重新给自己赋值,那么现在a就不和b相同了,因为重新赋值就给自己开辟了新的地址(内存)
- 原始值如果把一个a的值给另一个b,如果改变a的值,但是b的值也会变
- 原始值(Number String undefined null)它是栈数据
- 任何数据类型加字符串都等于字符串
- var a = c && d 或者 var a = c || d
在这个表达式中如果c为真,那个这个a得到的就是d的值, 在这个表达式中最后返回的一定是c或者d的值,而不是true或者false
我们所说的返回为true或者false都是转换的 - toFixed(index)可以限制一个小数的数据的小数点后边的位数 比如:num.toFixed(3)这个就是将num小数点后保留三位 每个函数当中都有一个隐式的属性就是arguments,它就是实参列表,他会将实参以数组的方式放在里边
- 在每个函数定义的时候他的形参也有一个隐式的属性就是(函数名.length),这样的话就会得到形参的数量
function myName(a,b,c){ console.log(myName.length); //将形参的数量打印出来 console.log(arguments.length); //将实参的个数打印出来 } myName();
- return就是函数的终止属性,当一个函数的执行遇见return的时候,这个函数就终止执行,就是说这个函数里的,return后边的代码块都不回再执行、
return的作用有两个- 就是终止函数
- 就是返回数据
- JavaScript单线程,解释性语言(翻译一句执行一句)
- javaScript执行的三部曲:
- 语法分析
- 预编译
- 函数声明整体提升
- 变量 声明提升
- 解释执行
- 预编译:发生在函数执行的前一步
- 生成AO对象
- 找形参和变量声明,将变量声明和形参名作为AO属性名,值为undefined
- 将实参值和形参统一
- 在函数体里边找函数声明,值赋予函数体
- 内部的函数被保存在外部,必产生闭包
闭包会导致原有的作用域链不释放,造成内存泄漏 - 立即执行函数的语法:
//第一种方法,建议这样书写(W3C标准) (function(){ }()) //第二种方法 (function(){ })()
- 一个简单的产生闭包,并解决闭包的方法(用闭包解决闭包)
//产生闭包的问题 function test(){ var arr=[]; for(var i=0;i<10;i++){ arr[i]=function(){ document.write(i+" "); } } return arr; } var arry=test(); for(var j=0;j<10;j++){ arry[j](); }
//上边的函数在最后执行的时候,是十个10,要解决上边的闭包问题,就要用闭包解决 function test(){ var arr=[]; for(var i=0;i<10;i++){ (function(j){ arr[i]=function(){ document.write(j+" "); } }(i)) } return arr; } var arry=test(); for(var j=0;j<10;j++){ arry[j](); } - 解决setTimeout的问题:(可以将setTimeout看成是闭包问题,那么闭包就需要闭包解决)
function test(){ for(var i=0;i<10;i++){ setTimeout(function() { console.log(i); }, 10); } } test(); //解决setTimeout的问题(打印出0-9) function test(){ for(var i=0;i<10;i++){ (function(j){ setTimeout(function() { console.log(j); }, 10); }(i)) } } test();
- 函数有两种的定义方式:
- 一种是函数表达式(var a = function () { })
- 另一种是函数声明(function a () { })
- 构造函数的内部原理
- 在函数体最前边隐式加上this={}
- 执行this.xxx=xxx;
- 隐式的返回this
function Car(name,age){
//var this={
//name:"",
//age:"",
// }; this.name=name; this.age=age; this.sex="男";
//return this; } var car=new Car("yang",18); //在执行new的时候就发生前边的三步骤,就是隐式执行上边的注释
- 隐式的返回this
包装类:首先原始值是没有属性和方法的,而引用值有,但是进行如下代码的时候却可以——这是因为js引擎默认地帮我们调用了包装类,但是使用后就在本次执行后delete,后边就找不到了 -
console.log(str.length); //4 其实str是没有length属性的,这就是引擎默认加上的 str.name="fei"; //引擎默认会默认创建一个对象来给str加上name的属性,但是会立即delete,就是防止报错引擎才会这样做 console.log(str.name); //undefined 这是因为str不会拥有任何的属性
- 原型:原型是function对象的一个属性,它定义了构造函数制造出的对象的公共祖先。通过该构造函数产生的对象,可以继承该原型的属性和方法。切记:::原型也是对象
-
利用原型特点和概念,可以提取公有属性
Person.prototype.name="yang"; Person.prototype.age=18; //上边的这两个就是给原型设置属性 function Person(){ } var person = new Person();
console.log(person.name); //如果在这里我们console.log(person)现在这个值里边没有name和age的属性 //但是当我们person.name的时候,就会打印出yang,这是因为这个实例本身没有这个属性,但是你查找的时候,它先找自己的,如果没有就会找原型当中2.对象如何查看原型-->隐式属性-->__proto_ _
3.对象如何查看对象的构造方法----->constructor
Person.prototype.name="yang"; Person.prototype.age=18; function Person(){ } var person = new Person(); console.log(person.constructor); //上边就会打印出person的构造函数 //Person(){ }
-
- JavaScript的call和apply方法的作用是什么,两者有什么区别?
他们都是改变this的指向
区别:传参列表不同(call:一般的传参方法 apply:把参数放在一个数组里) - 判断一个数据的类型是否为数组的四种方法:
- 使用typeof :我们以前说过,数据分两种,一种是原始值(null,undefined,String,Number,Boolean),一种是引用值(array,object,function),如果我们typeof一个原始值的话会返回原始值的类型,而如果typeof引用值,则会返回object
即 typeof(array) ------>object - 使用instanceof方法:instanceof 用于判断一个变量是否某个对象的实例,左边操作数是一个对象,右边操作数是一个函数对象或者函数构造器。原理是通过判断左操作数的对象的原型链上是否具有右操作数的构造函数的prototype属性。
即 [] instanceof Array -------->true - 使用constructor方法:在W3C定义中的定义:constructor 属性返回对创建此对象的数组函数的引用,就是返回对象相对应的构造函数。从定义上来说跟instanceof不太一致,但效果都是一样的。
即 [].constructor==Array ------->true - 使用Object.prototype.toString.call(arr)==='[abject Array]'
- 使用typeof :我们以前说过,数据分两种,一种是原始值(null,undefined,String,Number,Boolean),一种是引用值(array,object,function),如果我们typeof一个原始值的话会返回原始值的类型,而如果typeof引用值,则会返回object
-
如果在给一个变量赋值时后边是多个值,并且用逗号隔开,那这个变量的最后的值为最后一个
比如 a=(1,2) 最后这个a就是将2赋值给了a - 浅层拷贝:
//浅拷贝(不用拷贝引用值,就是拷贝的对象当中没有数组、对象、函数等) var a = { name:"yang", age:18, sex:"'男", height:180 } var b = {}; function copy (object1,object2){ var object2=object2 || {}; for( var tar in object1){ //遍历对象的每个属性 object2[tar]=object1[tar] } return object2; } copy(a,b);
//其实浅拷贝最笨的方法就是直接b = a; - 深拷贝:
//深度拷贝,对对象中的数组,或者对象也拷贝过来 var a = { name:"yang", age:18, sex:"'男", height:180, chengji:{ gaoshu:60, zonghe:{ fen:90 }, } } var b={}; function deepClone(origin,target){ var target=target || {}; var str=Object.prototype.toString; var strArr="[object,Array]"; for(var prop in origin){ if(origin.hasOwnProperty(prop)){ //检查属性是否是自己的,不是原型链上边的 if(typeof(origin[prop])=='object'){ if(str.call(origin[prop]==strArr)){ target[prop]=[]; }else{ target[prop]={}; } deepClone(origin[prop],target[prop]); //采用递归回调 }else{ target[prop]=origin[prop]; } } } return target; } deepClone(a,b);
- 数组的相关的操作:
- 改变原数组
- push pop shift unshift sort reverse
- push:在数组的最后一位添加数据,一次可以添加多个数据,就是说传参可以传很多个,并返回数组的长度,下边是模拟push的源码:
var arr = []; Array.prototype.push=function(){ for(var i = 0;i < arguments.length;i++){ this[this.length]=arguments[i]; //前边我们已经说过,this就是谁调用这个函数谁就是这个this } return this[length]; }
- pop:把数组的最后一位去除,不能传参,传参也没用,一次只能去除一位
- unshift:在数组的前边添加数据,一次可以添加多个数据,就是说一次可以传很多的参数
- shift:恰好与上边的相反,是从最最前边去除数据的,一次只能去除一位,不能传参,传参也没用,一次只能去除一位
- reverse:将数组的顺序逆反,并返回原数组
- splice(切片):splice(从第几位开始,截取的长度,在切口处添加数据)
- sort:默认是将原数组升序排序,但是这并不能解决我们的所有问题,所以sort提供了一个接口,我们可以自定义自己的sort方法
(在这里我们对sort要说的多一点,因为面试/笔试题都是关于它的)- 我们先来通过代码来看看sort默认的升序的实现的原理:
// 首先我先说一下sort的累不得一些规则: // 1.必须写两个参数 // 2.看返回值: // 1)当返回值为正数时,那么前边的数放在后边(调换位置) // 2)当返回值为负数时,那么前边的数放在前边(不调换位置) // 3)当返回值为0时,不动 // 3.参数比较的时候是采用冒泡的方式 // 下边我们就实现以下sort默认的升序排序 var arr = [1,12,4,2,56,58,2,3]; arr.sort(function(a,b){ if(a>b){ return 1 }else{ return -1 } })
- 那么问题来了,如果我们要实现sort的降序排序怎么实现呢,其实很简单,我们只需要将 a>b 时 a,b 不调换位置,a<b时调换位置,那我们来实现一下:
var arr = [1,12,4,2,56,58,2,3]; arr.sort(function(a,b){ if(a>b){ return -1 //不调换位置 }else{ return 1 //调换位置 } })
- 问题来了,上边的代码我们可以简化一下代码吗???答案当然是可以:(这是一个数学问题,能记住最好,记不住也没关系,上边的搞懂就行)
var arr = [1,12,4,2,56,58,2,3]; arr.sort(function(a,b){ return a-b //升序 return b-a //降序 })
- 现在我们扩展一下:我们想要一个有序的数组乱序,我们该怎么办呢???下边我们就来实现一下:
(在这里补充一个知识点:Math.random();这个函数每次执行都会产生一个0-1的开区间的数)var arr = [1,2,3,4,5,6,7,8]; arr.sort(function(){ return Math.random()-0.5 })
- 我们先来通过代码来看看sort默认的升序的实现的原理:
- push:在数组的最后一位添加数据,一次可以添加多个数据,就是说传参可以传很多个,并返回数组的长度,下边是模拟push的源码:
- push pop shift unshift sort reverse
- 不改变原数组
- concat join ------->split toString slice
- concat:arr.concat(arr1) 就是将arr1拼接在arr的后边,并返回子女的数组(不改变原来的数组)
- slice :slice(从该位开始截取,截取到该位)1.不写参数的时候就是将整个数组截取 2. 一个参数就是从该位截取到最后
- join:后边的参数必须是一个字符串,作用就是将数组的数据用参数(传的字符串)连接起来,重点在这里::::它返回的是一个字符串 如果不传参数就是默认逗号
- split:和上边的join是互逆的,就是按照参数的字符串拆分
- concat join ------->split toString slice
- 改变原数组
- 在这里我还想给大家提醒一下,而且我觉得有必要提醒一下,有的问题的当中会提到类,在js当中类就是构造函数,这个和Java当中的类有很大的区别
- 相信了解过Java的同学都知道try catch 在这里我再给大家说一说这个try catch
- try catch的作用就是标记要尝试的语句块,并指定一个异常时抛出的相应(说这些官方的话语我自己有时也理解不了,那么我就用代码给大家解释)
try{ var a =123; var c=145; console.log(a); //正常执行 console.log(b); //报错,try后边的程序不再执行 console.log(c); //不执行 }catch(e){ console.log(e); //执行,因为上边有错就执行这个 } console.log("Hello"); //执行,try catch外边的代码块不受影响
- 其实说白了就是,你爸感觉会出错的代码放在try当中,如果有错就会抛出错误信息,而且不会影响其他代码块的正常执行
- 写在这里JavaScript的最让人头疼的基础就算是差不多了,后边的学习的就是基于基础的一下知识点比如DOM BOM...
希望大家学习顺利
早日找到自己喜欢的工作和岗位!!!!
- try catch的作用就是标记要尝试的语句块,并指定一个异常时抛出的相应(说这些官方的话语我自己有时也理解不了,那么我就用代码给大家解释)
做最好的自己,不努力永远看不到自己的光环。别在该努力的年纪享乐,就不会在该享乐的年纪吃苦!