一、JavaScript特点
- 解释性语言——不需要编译代码,可以跨平台,像php、js、jsp都是解释性语言。
- 单线程——js是单线程的语言,同时只能执行一件事情
- ECMA标准——为了统一js的规则,推出了ECMA标准,因此js也称为ECMAScript。
二、JavaScript三大部分:(ECMAScript、DOM、BOM)
- ECMAScript是符合ECMA标准的基本javascript。
- DOM是Document Object Model文档对象模型,可以操作页面的代码,可以操作html和css部分。DOM是非常非常重要的部分。
- BOM是Browser Object Model浏览器对象模型,操作浏览器shell的。因为每一个浏览器厂家的不同,导致我们在每一个浏览器操作BOM都不一样。
三、JavaScript的基本语法
变量声明
- Js是一种弱数据类型的语言,任何类型的变量都用关键字Var来声明。
1 var arr = [1,2,3]; 2 var num = 123; 3 var steing = "abc";
- 赋值可以在声明的的同时赋值,也可以在后面赋值。
1 var num = 123; 2 var num; 3 num = 123;
- 这两种方法是一样的。
- 同时有一种单一Var模式。
1 var num1 = 123, 2 num2 = 234, 3 num3 = 456;
- 变量名上下对齐,这样结构更清晰,也能节省很多代码。
2.变量命名规则
- 以英文字母开头或者_和$符开头。
- 变量名可以包含数字。
- 不可以使用系统自带的关键字,保留字。
3.数据类型(值类型)
- 不可改变的原始值。
- 主要有 Number String Boolean undefined null 堆数据。
- 引用值 数组Array 对象Object 函数function 栈数据。
四、JavaScript语句的基本规则
- 语句后面要用英文“ ;”结束。
- js语法错误会引发后续代码的终止,但不会影响其他的js代码块,这里仅限于逻辑错误和低级语法错误会导致代码全部执行不了。
- 书写规范(不同公司有不同要求)。
五、JavaScript运算符
- “ + ”数学上相加的功能和字符串拼接“- * / % ”数学运算。
- 相同的还有“++ == -- += -= > < ...”等等。
- 逻辑运算符 && || !
- && 的作用是结果是true的时候才会继续执行,第一个就错了第二个不会执行,如果都是true的话返回最后一个。
- || 的作用是结果只要有一个表达式是true的,后面的就不走了,并且返回的结果是这个正确的表达式的结果,全是false表达式返回的结果就是false。
- && 可以当做一种短路语言使用;|| 可以当做赋初值的使用。
4.默认为false的值
- unedfined; null; " "; NaN; 0; false 。
六、类型转换
(一)显示类型转换
- 用 typeof 可以检测数据的类型。
1 console.log(typeof(123)); // Number
- typeof 返回的结果有六种:number string boolear undefined object function 。
- 数组和 null 都属于object。
- NaN 属于number ,虽然是非数,但也属于数字。
- typeof 返回结果是字符串。
2.Number(mix)[混合]
- 这个方法是可以把其他类型的数据转换成数字类型的数据。
3.parseInt(string,radix)[基数]
- 这个方法是将字符串转换成整型类型数字的。其中第二个参数radix基底是可以选择的参数。
- 当radix为空的时候,这个函数的作用仅仅是将字符串转换成number类型。
- 当参数string里面既包括数字字符串又包括其他字符串的时候,它会将看到其他字符串就停止了,不会继续转换后面的数字型字符串了。
1 parseInt('123abc123') // 123; 2 parseInt('abc123') // NaN; 3 parseInt('123') // 123; 4 parseInt('true') // NaN;
- 当radix不为空的时候这个函数可以用作进制转换,把第一个参数的数字当成几进制的数字转换成十进制。
- radix参考范围是2--36,
1 var demo = 10; 2 parseInt(demo,16) //16
4.parseFloat(radix)
- 这个方法和parseInt类似,是将字符串转换成浮点类型的数字,碰到第一个非数字类型停止。
- 只能识别第一个小数点及后面的数字,第二个小数点不能识别。
1 parseFloat('123.2.3') // 123.2 2 parseFloat('132.2abc') // 123.2 3 parseFloat('123.abc1') // 123
5.toString(radix)
- 这个方法和前面的不同,它是对象上的方法,任何数据类型都可以使用,转换成字符串类型,涉及到包装类。
- 同样是radix基地可选参数,为空仅仅将数据转换成字符串。
1 var demo = 123; 2 typeof demo.toString(); // string123; 3 typeof true.toString(); // stringtrue;
- 当写了radix时代表要将这个数字转换成几进制的数字型字符串。
1 var dome = 10; 2 demo.toString(16) // A
- undefined和null没有toString方法
6.String(mix)
- 和Number类似把任何类型转换成字符串
7.Boolean(mix)
- 和Number类似把任何类型转换为Boolean
(二)隐式类型转换
1.isNaN()
- 这个方法可以检测是不是非数类型,调用的Number方法。
2.算数运算符
- ++就是将现有数据调用Number之后,自身加一。
- + - * / 执行之前都会先进行类型转换,换成数字在运算。
3.逻辑运算符
- && || ! 都会调用Boolean转换成布尔值看看结果是ture还是false,返回结果还是本身表达式的结果。
- // !abc; // false
4.不发生类型转换的比较运算符
- ===严格等于 !==严格不等于
七、预编译【precompile】
函数声明提升:函数声明提升是一种整体提升,他、它会把函数声明和函数体一起提到前面。
变量声明提升:变量声明提升是一种局部提升,它仅仅将变量的声明提前了,但是并没有将赋值一起提升。
1.js运行三部曲
- 语法分析
- 预编译
- 解析执行
2.预编译前奏
- imply global
- 暗示全局变量,如果任何变量未经声明就赋值使用,此变量归window所有,并且成为window对象的一个属性。
- 一切声明的全局变量,都是window属性。
- 未经声明的全局变量可以用delete操作来删除。
- 函数在执行的前一刻一个执行上下文,Activeaction Object对象。
- 这个对象是空的,但是里面有着一些看不见的隐式属性:this:window属性和arguments[];属性。
3.预编译四步
- 创建AO对象
- 寻找形参和变量声明并当做属性名添加到AO对象中,值为undefined。//函数声明不叫变量。
- 将实参形参相统一
- 在函数体里寻找函数声明,将函数名当做属性名,值为这个函数的函数体。
1 function test (a,b) { 2 console.log(a) 3 function a () {} 4 a = 222; 5 console.log(a) 6 function b () {}; 7 console.log(b) 8 var b = 111; 9 var a; 10 } 11 test(1);
- var b = function () {} 这种不叫函数声明,这个函数是给b赋值的,b变量是声明。
- 在第四步寻找函数声明并不会把赋值成function(){},执行到这一行的时候才会赋值成这个函数。
十、函数与作用域与闭包
(一)函数部分
1.函数声明有3种方式
1 var demo = function () {}; 函数表达式 2 function demo () {}; 函数声明 3 var demo = function xxx () {};命名函数表达式 //没用
- 每一个函数里面都有一个类数组属性arguments,这个属性里面存的就是实参。
- 每一个函数有一个length属性,这个属性存的是形参的数量。
- 每一个函数都会有一个return,如果不写的话函数会自动加一个return。
- return的功能有两个:返回这个函数的执行结果、终止函数的执行。
1 function test(a,b) { 2 console.log(a + b); 3 return; 4 console.log('hello'); 5 } 6 test(1,2); // 打印结果3 不会打印hello
(二)作用域
- 定义:变量(变量作用域又称为上下文)和函数生效(能被访问)的区域。
- JavaScript的函数是可以产生作用域的。
- es5中的作用域只有全局作用域函数作用域两种,es6新添加的块级作用域。
1 var demo = 123; // 全局变量 2 function test () { 3 var demo = 234; // 局部变量 4 console.log(demo); 5 var demo1 = 'hello'; 6 } 7 test(demo); // 打印234 就近打印局部变量,没有局部变量打印全局变量 8 console.log(demo1); // 报错 我们的全局作用域无法访问局部作用域
- 函数作用域就好像一个屋子,里面的可以拿外面的东西,外面的不能拿里面的东西。
- 在函数作用域里声明变量没有用var的话,那么就生成了一个全局变量。
- 两个不同的作用域(除了全局作用域)是不能互访问的。
(三)作用域链(scope chain)
- 既然函数存在作用域,函数有可以嵌套,那么作用域就会产生嵌套关系,这个时候就产生作用域链。
- 当代码在一个环境中执行时,会创建变量的作用域链来保证对执行环境有权访问的变量和函数的有序访问。
- 作用域链第一个对象始终是当前执行代码所在环境的变量对象。
1 function demo () { 2 var dome_a = 1; 3 function test () { 4 var demo_a = 2; 5 console.log(demo_a); 6 } 7 test(); 8 } 9 demo();
- 本着对执行环境的有权和有序访问,每个函数的自身作用域总在作用域链的最顶层,下一层就是这个函数的父级函数作用域,直到全局作用域。
- 因此test执行的时候打印的demo_a是本身作用域中的是‘2’而不是‘1’,如果自身作用域没有demo_a的话系统就会沿着作用域链向下找demo_a。
(四)闭包【closure】
1.什么是闭包
- 闭包就是能够读取其它函数内部变量的函数。
- 不同的作用有之间不能互相访问,但是如果在一个函数内部再定义一个函数与外部的变量有所关联,那么就可以返回这个函数来访问外部函数里面的变量,所以在本质上闭包就是将函数内部与函数外部连接起来的桥梁。
1 function a (){ 2 var dome1 = 123; 3 add = function(){ 4 demo1 ++; 5 } 6 return function(){ 7 console.log(demo1); 8 }; 9 } 10 var demo = a (); 11 demo(); // 123 12 add(); 13 demo(); // 124
- 当函数执行完函数的执行上下文就会被销毁,自然就无法访问里面的变量了,但是我们这个函数返回了一个依赖于这个函数的新函数,也就是说这个没有被销毁的新函数的作用域链中存在着原本函数作用域的引用,就导致原本函数的上下文不会被销毁,返回的新函数是原本函数的闭包函数。
2.使用闭包的注意点
- 闭包会使函数的变量都保存在内存中,内存消耗很大,不能滥用闭包,否则会造成网页的性能问题,IE会造成内存泄漏。解决的方法就是退出函数时,将不使用的局部变量全部删除。
- 闭包会在父函数外部改变父函数内部的值,如果把闭包当对象使用,那么就把闭包当做它的公用方法,把内部变量当做它的稀有属性。(不要随便改变函数内部变量的值)
1 var name = 'global'; 2 var obj = { 3 name:'obj', 4 getName:function() { 5 return function () { 6 console.log(this.name); 8 } 10 } 11 } 12 obj.getName() ();
- 累加器的例子:
1 function a () { 2 var num = 1; 3 function addNum () { 4 num ++; 5 console.log(num); 6 } 7 return addNum; 8 } 9 var demo = a (); 10 demo(); 11 demo(); 12 var demo1 = a(); 13 demo1(); 14 demo1();
(五)立即执行函数
- 立即执行函数是解闭包的一个重要方法,但是注意闭包是没有办法解除的,只能通过一个新的闭包来消除上一个闭包的影响。
- 立即执行函数不需要被定义,直接执行,执行完释放,经常作用作初始化。
- 函数声明不能被执行,但是函数表达式可以
1 (function (){}()) 2 function returnB() { 3 var arr = []; 4 for(i = 0; i < 10; i ++){ 5 arr[i] = (function(){ 6 console.log(i); 7 }()) 8 } 9 return arr; 10 } 11 var save = returnB(); 12 console.log(save); 13 for(j = 0; j < 10; j ++){ 14 save[j]; 15 }
十一、对象、构造函数与包装类
1.对象的创建方式有三点
- 对象字面量。
1 var obj ={};
- 这样的方式是最简单最常用的方法。
- 对象里面有属性,属性之间用逗号相隔,每条属性都有属性名和值,属性名和属性值用分号相隔。
2.构造函数【constructor】
- 构造函数也分为两种,系统自带的构造函数和自定义的构造函数。
- 创建对象的构造函数Object()
1 var object = new object();
- 通过这条语句就创建了一个空对象,它的作用和 var obj = {}; 的作用一样。
- 系统自带的构造函数还有Number();String();Boolean();Array() 。
3.自定义构造函数
- 自定义的构造函数是最常见的一种构造函数。
1 var function Person () {}; 2 var operson = new Person (); 3 typeof operson // object
- 用new操作符创造出来的对象,尽管使用的是一个构造函数,但是之间没有联系。
1 function Person (name,age) { 2 this.name = name; 3 this.age = age; 4 } 5 var person = new Person('zhangsan',18); 6 console.log(person.name);
- 创建对象的时候只有new才会有this。
- 重点:为什么可以用new操作符创建出相互独立的对象呢?
- 用new操作符的时候,这个new在构造函数里面隐式的创建了一个this对象,并且最后返回了这个this对象。
1 function Person (name) { 2 var this = {}; 3 this.name = name; 4 return this; 5 }
- 如果在构造函数首行手动创建一个对象,比如that对象,然后返回that,那么里面的this就没有了,属性值就用that了。
1 function Person (name) { 2 var that = { 3 name: 'lisi' 4 }; 5 that.name = name; 6 return that; 7 } 8 var person = new Person ('demo'); 9 console.log(person.name)
- 如果最后返回的是对象,那么this就失效,但是如果显示返回的是原始值那么this还是有效的。
4.属性的增删改查
- 增:可以通过对象名+点属性名的方法来给对象添加新的属性并且赋值。
1 var obj = {};
2 obj.name = 'xiaoming'
- 改:修改的操作增加是一样的,只要调用相同的属性名然后赋一个新值就可以了。
1 var obj = { 2 name:'demo'; 3 } 4 obj.name = 'tan';
- 查:查看属性的功能console.log(xxx)。
- 删:删除属性需要借助delete操作符。
1 var obj = { 2 name = 'scerlett' 3 } 4 obj.name; // scerlett 5 delete obj.name; 6 obj.name; // undefined
- 包装类
十二、原型与原型链
(一)原型:prototype
1.原型的定义:原型是function对象的一个属性,它定义了构造函数制造出来的对象的公有祖先,通过该构造函数产生的对象,可以继承原型的属性和方法,原型也是对象。
1 function Person () {}
- 定义一个构造函数,Person.prototype这个属性就是这个构造函数的原理,这个属性天生就有的,并且这个属性的值也是一个对象。
- 可以在person.prototype上面添加属性和方法,每一构造出来的对象都可以继承这些属性和方法。
- 虽然每一个对象都是独立的,但是它们都有共同的祖先,当访问这个对象属性的时候,如果它没有这个属性,就会向上查找,找到它原型,然后在原型上访问这个属性。
2.利用原型特点概念,可以提取公有属性
- 可以把每一个对象都有的公有属性不写在构造函数里面,而是提取到原型上,这样当构造函数构造大量的对象的时候就不需要走多次构造里面的赋值语句了,而只需走一遍,每个对象调用属性的时候直接上原型上查找就可以了。
3.对象如何查看原型
- 用构造函数构造对象的时候,就会隐式创建一个this对象,这个this对象里面有一个默认的属性叫做proto属性,这个属性的值就是指向对象的原型。
- 当查找的属性是自身没有的属性的时候,就会查找proto这个属性,然后这个属性指向原型,所以就到原型上查找属性了。
- 注意:prototype是函数的属性,proto是对象的属性。
4.如何查看构造自身的构造函数
- 在prototype里面,有一个隐式的属性叫做constructor,这个属性记录的就是对象的构造器,里面存的就是构造函数。
1 console.log(person.constructor); //person();
(二)原型链
1.有了原型,原型还是一个对象,那么这个名为原型的对象自然还有自己的原型,这样的原型上还有原型的结构就成了原型链。
1 Gra.prototype.firsName = 'scarlett' 2 function Gra () { 3 this.name = 'grandfather'; 4 this.sex = 'male'; 5 } 6 var grandfoo = new Gra(); 7 garndfoo.word = 'hello' 8 Foo.prototype = grandfoo; 9 function Foo () { 10 this.age = '18'; 11 this.money = '100'; 12 } 13 var father = new Foo(); 14 function Son () { 15 this.name = 'son'; 16 } 17 Son.prototype = father; 18 var son = new Son();
- Foo创造出来的每一个对象都继承来自grandfoo对象,son的每一对象都继承来自father这个由Foo创造出来的对象,这样的son就可以继承上面Foo和Gra的所有属性。
- 这种链式的查询结构就叫做原型,最终的尽头是Object.prototype这个对象。
- 如果没有规定原型的对象,它的原型就是Object.prototype。
2.但是并不是所有的对象都有原型,比如使用Object.create方法。
- Object.create()方法需要写一个参数,这个参数就是对象的原型,如果要构造一个var obj = {};一样的对象,就需要写:
1 var obj = Object.create(Object.prototype);
- 也可以写一个自定义的属性,让它成为原型。
3.原型链上的增删改查
1 Person.prototype.arr[1,2,3]; 2 var person1 = new Person(); 3 var person2 = new Person(); 4 person1.arr.push(4); 5 console.log(person2);//1 2 3 4
- 删:删除属性需要借助delete操作符,对象不能删除原型上的属性。
十三、继承、this
1.this的一些问题:
函数内部的this默认指向window,可以使用call / apply来改变this的指向,区别:后面传参形式不同。
1 function person () { 2 this.name = 'scarlett'; 3 console.log(this); 4 } 5 person();
现在this指向window,name属性自然就是window上的全局属性
var obj = {}; person。call(object)//Object.{name:'scarlett'}
如果这个函数还有参数的话,只要把实参的值写在call后面并且用逗号隔开
function person(name,age){ this.name = name; this.age = age; } var obj = {}; person.call(obj,'scarlett',18); console.log(obj.name);
apply和call基本没什么区别,唯一的区别就是call后面的参数是一个一个传的,而apply后面的参数是放在数组里
person.apply(obj['scarlett',18]);
2.继承【inherit】
圣杯模式
function inherit(Target, Origin) { function F() {}; F.prototype = Origin.prototype; Target.prototype = new F(); Target.prototype.constuctor = Target;//让constuctor指向自己的 Target.prototype.uber = Origin.prototype; //超类,知道自己继承的是谁
yahu封装方法:
1 // var inherit = (function (){ 2 // var F = function () {}; 3 // return function (Target, Origin){ 4 // F.prototype = Origin.prototype; 5 // Target.prototype = new F(); 6 // Target.prototype.constuctor = Target; 7 // Target.prototype.uber = Origin.prototype; 8 // } 9 // }()); 10 // 11 // for (var i = 0; i < 5; i ++) { 12 // var btn = document.createElement('button'); 13 // btn.appendChild(document.createTextNode('Button' + i)); 14 // btn.addEventListener('click', function(){console.log(i); }); 15 // document.body.appendChild(btn); 16 // }
对象的枚举与this
1.对象的枚举
查看对象的属性可以用obj.name查看,也可以用obj['name']类数组方式查看,但事实上是数组模仿了对象的查看方式
2.for-in操作符
要枚举一个数组的所有属性只需用一个for循环从头到尾遍历一遍就可以了。
但是对象并不能用for循环来遍历属性,所有就要用到for-in操作了
1 // var obj = { 2 // name: 'scarlett', 3 // age: 18, 4 // sex: 'female' 5 // } 6 // for(var prop in obj){ 7 // console.log(prop + ':' + obj[prop]); 8 // }
- for-in循环会按照属性的顺序取出属性名然后赋给prop,所有打印prop都是属性名,obj【prop】则是性对应的属性的值
- 注意:这里不能写成obj.prop方式,因为在系统底层会转化成obj【‘prop’】的形式,但是并没有prop属性,它只是一个变量,所以会打印 undefined,这里必须使用obj['prop']。
- 在非严格模式中,for-in循环都会把原型里面的一些属性一起打印出来,但es5的严格模式不会。
2.三种操作符
hasOwnProperty这个操作符的作用是查看当前这个属性是不是对象自身的属性,在原型链上的属性会被过滤掉,自身的ture
// function Person() { // this.name = 'scarlett' // } // Person.prototype = { // age:18 // } // var oPerson = new Person(); // for (var prop in oPerson) { // if (oPerson.hasOwnProperty(prop)){ // console.log(oPerson[prop]); // } // }
这样for-in循环就只会打印自身的属性,而不是打印原型上的属性
in操作符:这个操作符的作用是查看一个属性是不是在这个对象或者它原型里面。
1 // 'name' in oPerson; //ture 2 // 'sex' in oPerson; //False
instanceof操作符:作用是查看前面对象是不是后面的构造函数构造出来的,和constructor很像
1 // oPerson intanceof object; // ture 2 // {} instanceof oPerson; // false
3.this
- 预编译过程中this执行window
- 全局作用域里的this指向window
- call / apply可以改变this指向
- obj.func()func()里的this指向Obj
1 // var obj = { 2 // height:190, 3 // eat:function () { 4 // this.height ++; // eat在没有执行之前,谁也不知道this指向谁 5 // } 6 // } 7 // obj.eat(); // 谁调用this,this指向谁 8 // eat.call(obj); // eat里面的this指向obj
如果能理解下面的这段代码的this指向问题,那么就掌握的this的所有知识点了
1 // var name = '222' 2 // var a = { 3 // name:'111', 4 // say:function () { 5 // console.log(this.name); 6 // } 7 // } 8 // var fun = a.say; 9 // fun(); // 此处其实就是把a.say这个函数体赋给fun这个函数,相当于在全局空间写下了一个fun函数,此时this指向window,打印'222' 10 // a.say(); // 按照谁调用指向谁的说法,这里打印'111' 11 // var b = { 12 // name:'333', 13 // say:function (fun) { 14 // fun(); 15 // } 16 // } 17 // b.say(a.say); // 其实和在全局调用a.say的函数体方法差不多,打印'222' 18 // b.say = a.say; 19 // b.say(); // this指向B 所以打印'333'
十五、克隆与数组
1.argument.callee
这个方法是指代函数本身,当在一些匿名函数或者立即执行函数里面进行递归调用函数本身的时候,由于没有名字,就用这种方式调用;
一般当需要通过计算机进行初始化的时候,写一个立即执行函数,当这个立即执行函数还需要递归调用自身的时候,就用这种方式调用。
2.function.caller
// function test () { // console.log(test.caller); // } // function demo () { // test() // } // demo();
这是函数本身自带的一个属性,可以指出当前函数的ude运行环境的函数引用,就是这个函数在哪个函数里面执行的……
3.克隆【clone】
克隆和继承有一些区别,克隆是复制出来一模一样的目标对象又分为浅度克隆和深度克隆
// function clone (src,tar) { // var tar = tar || {}; // for (var prop in src) { // if (src.hasOwnProperty(prop)){ // tar[prop] = src[prop]; // } // } // return tar; // }
当有一个属性是引用值(数组、对象)时按照这种克隆模式,只是把这个引用值的指向赋给了新的目标对象,一旦改变了原对象或者目标对象的引用属性另一个也跟着变,这一点就是浅度克隆的缺点;
深度克隆的原理很简单,只要不克隆引用值的引用而是把引用值也当做一个原对象,把里面的值一个个克隆岛目标对象里面,就解决了二者相同指向的问题;
// function deepCopy (src,tar) { // var tar = tar || {}; // for (var prop in src) { // if(typeof(src[prop] == 'object')){ // tar[prop] = (src[prop].constructor === Array) ? [] : {}; // }else{ // tar[prop] = src[prop]; // } // } // return tar; // }
这个时候目标对象和原对象的引用值就没有关系了,都是独立值可以进行修改。
4.数组【array】