构造函数:构造函数主要用来创建对象,并为对象的成员赋初始值。
示例:
function Person(name, age) {//定义一个函数,传入姓名和年龄参数 this.name = name;//初始化对象 this.age = age; this.sing = function () { console.log('我会唱歌'); }; } var p1 = new Person('张三', 18);//创建对象 var p2 = new Person('李四', 19); console.log(p1.name); // 输出结果:张三 console.log(p2.age); // 输出结果:19 p2.sing(); // 输出结果:我会唱歌
结果为:
张三
19
我会唱歌
实例成员是指实例对象的成员,而静态成员是指通过类或构造函数访问的成员。
实例:静态属性
function Person(uname) { this.uname = uname; } Person.school = 'X大学'; // 添加静态属性school Person.sayHello = function () {// 添加静态方法sayHello console.log('Hello'); }; console.log(Person.school); // 访问静态属性,输出结果:X大学 Person.sayHello(); // 访问静态方法,输出结果:Hello
结果为:
X大学
Hello
区别:类中的成员方法是定义在类中的,使用类创建对象后,这些对象的方法都是引用了同一个方法,这样可以节省内存空间。
实例:方法共享
class Person {//定义一个Person类 sing() {//定义唱歌方法 console.log('hello'); } } var p1 = new Person();//创建对象 var p2 = new Person();//创建对象 console.log(p1.sing === p2.sing);
输出结果:true
原型对象:每个构造函数(类)都有一个原型对象存在,这个原型对象通过构造函数的prototype属性来访问。
注意:
1、构造方法的原型对象是object类型
2、prototype属性是默认存在的
3、原型对象的作用:共享方法
示例:
function Person() { } // 定义函数 console.log(Person.prototype);//输出原型对象 console.log(typeof Person.prototype);//输出类型
结果是:
Person {}
object
通过原型对象增加方法:
构造方法名.prototype.添加的方法名 = function([参数]) { //方法体语句 }
【案例】利用原型对象共享方法。
function Person(uname) {//构造函数Person this.uname = uname;//初始化对象 } Person.prototype.sayHello = function () {//给Person增加sayHello()方法 console.log('你好,我叫' + this.uname); }; var p1 = new Person('张三');//创建对象 var p2 = new Person('李四'); console.log(p1.sayHello === p2.sayHello); // 输出结果:true p1.sayHello();// 输出结果:你好,我叫张三 p2.sayHello();// 输出结果:你好,我叫李四
对象的原型对象:每个对象都有一个默认的属性__proto__属性,该属性指向对象的原型对象
示例:
function Person() { } var p1 = new Person(); console.log(p1.__proto__ === Person.prototype);
结果是:true
实例对象和原型对象:
1、实现对象:使用new运算符创建的对象
2、原型对象:①通过构造方法名(类名).prototype得到的对象;②通过对象的__proto__属性得到的对象
对象的构造函数:在原型对象里面有一个constructor属性,该属性指向了构造函数。
通过原型对象来访问构造方法,语法:
构造方法名(类名).prototype.constructor
通过实例对象访问构造方法
对象名.constructor
构造方法的作用:初始化对象
ES5通过原型对象访问构造方法:
//ES5 function Student(name, gender) { this.name = name;//this指向自己 this.gender = gender; } var s1 = new Student("张三", "男"); var s2 = new Student("李四", "女"); console.log("原型对象访问构造方法:", Student.prototype.constructor); console.log("原型对象访问构造方法:", Student.prototype.constructor === Student);
ES6通过原型对象访问构造方法:
//ES6 不能显式调用构造方法,只能通过类名调用方法 class Student { constructor(name, gender) { this.name = name; this.gender = gender; } } var s1 = new Student("张三", "男"); var s2 = new Student("李四", "女"); console.log("原型对象访问构造方法:", Student.prototype.constructor);
示例:
function Person() { }//定义一个构造函数 // 通过原型对象访问构造函数 console.log(Person.prototype.constructor === Person);// 输出结果:true // 通过实例对象访问构造函数 var p1 = new Person();//不能显示调用构造方法,而是通过类名 console.log(p1.constructor === Person); // 输出结果:true
【案例】用赋值方式修改原型对象为新的对象,就无法访问构造函数。
function Person() { } var p1 = new Person(); console.log("原来的原型对象:", Person.prototype); console.log(p1.constructor) // 修改原型对象为一个新的对象 Person.prototype = { sayHello: function () { console.log('hello'); } }; var p1 = new Person(); // 使用实例对象p1可以访问新的原型对象中的属性 p1.sayHello(); // 输出结果:hello // 使用constructor属性无法访问原来的构造函数 console.log("修改后的原型对象:", p1.constructor);
构造函数、原型对象和实例对象之间的关系:
原型对象的原型对象:原型对象也是对象,那么这个对象应该也会有一个原型对象存在。
查看原型对象的原型对象的方法:
构造方法名/类名.prototype.__proto__
示例:
function Person() { } // 查看原型对象的原型对象 console.log(Person.prototype.__proto__); // 查看原型对象的原型对象的构造函数 console.log(Person.prototype.__proto__.constructor);
结果为:
{}
[Function: Object]
【案例】Person.prototype.__proto__这个对象其实就是Object.prototype对象。
function Person() { } console.log(Person.prototype.__proto__ === Object.prototype); // true var obj = {}; console.log(obj.__proto__ === Object.prototype); // true console.log(Object.prototype.__proto__); // 输出结果:null
结果为:
true
true
null
原型链结构的特点:
1、每个构造函数都有一个原型对象:通过prototype属性访问
2、原型对象通过constructor属性指回构造函数
3、实例对象通过__proto__属性指向原型对象
4、Object的原型对象的__proto__是null
原型链结构图:
1、先在对象中查找,JavaScript首先会判断实例对象有没有这个成员
2、若对象中没有,继续查找原型对象的原型对象
3、若仍没有找到,则返回undefined
示例:
function Person() {//构造Person函数 this.name = "张三";//初始化 } Person.prototype.name = "李四";//给构造函数的原型对象的name属性赋值为李四 var p = new Person();//创建对象 console.log("姓名:", p.name);//先在对象中查找,输出张三 delete p.name;//删除对象p的成员name console.log("姓名:", p.name);//再从函数的原型对象中查找,输出李四 delete Person.prototype.name;//删除函数的原型对象中的name console.log("姓名:", p.name);//再找原型对象的原型对象,输出undefined
【案例】利用原型对象扩展数组的方法——求数组元素之和
//Array:内置对象,下面扩展Array数组的方法 Array.prototype.sum = function () {//为Array的原型对象定义sum函数,求数组的和 var sum = 0;//局部变量 for (var i = 0; i < this.length; i++) {//this代表当前的对象 sum += this[i];//将当前对象的每一项相加 } return sum;//返回sum } var arr = [12, 32, 42, 50]; console.log("数组元素之和:", arr.sum());
数组元素之和: 136
函数中this指向,情况如下:
1、构造函数内部的this指向新创建的对象。
2、直接通过函数名调用函数时,this指向的是全局对象window。
3、如果将函数作为对象的方法调用,this将会指向该对象。
示例:
function foo() { //普通方法 return this; //this指向全局的对象window } var o = { name: "橘猫吃不胖", func: foo } console.log(foo()) //foo函数的this指向全局对象window console.log(foo() === window) //对应第2点,true console.log(o.func())//对应第三点,将foo函数作为对象o的函数进行调用,此时,foo函数中的this指向o
更改this指向方法有:apply()方法和call()方法。
apply()示例:
function method() { console.log(this.name); //this指向全局对象window } //更改了method方法中this的指向:此时this指向对象{name: '张三'} method.apply({ name: '张三' });
结果为:张三
call()示例:
function method() { console.log(this.name); //this指向全局对象window } //更改了method方法中this的指向:此时this指向对象{name: '李四'} method.call({ name: '李四' })
结果为:李四
apply()方法和call()方法的区别:
call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。
apply()用法示例:
function method(a, b) { console.log(a + b); } method.apply({}, [1, 2]);//数组中是数字类型 method.apply({}, ["1", "2"]);//数组中是字符串类型
输出结果:
2
12
call()用法示例:
function method(a, b) { console.log(a + b); } method.call({}, 3, 4);//传入数字参数 method.call({}, "3", "4");//传入字符串参数
输出结果为:
7
34
bind()方法:实现提前绑定的效果。在绑定时,还可以提前传入调用函数时的参数。
示例:
function method(a, b) { console.log(this.name + a + b); } var name = "橘猫吃不胖"; //全局变量,属于window //将{name:'小猫咪'}对象绑定到method上,此时,method方法中的this指向'小猫咪' var test = method.bind({ name: "小猫咪" }, '3', '4'); method('1', '2'); //全局调用,函数中的this指向window test();
使用try()…catch(),语法为:
try{ //可能出现错误的代码 } catch(e) //错误处理代码 }
catch:用来捕获错误,参数e用来捕获错误对象
错误案例演示:
var o = {}; o.func();// 这行代码会出错,因为调用了不存在的方法 console.log("test");// 前面的代码出错时,这行代码不会执行
当我们知道这行代码可能会出现问题时,就可以使用**try()…catch()**来进行错误处理,案例如下:
var o = {}; try {// 在try中编写可能出现错误的代码 o.func(); console.log("a");// 如果前面的代码出错,这行代码不会执行 } catch (e) {// 在catch中捕获错误,e表示错误对象 console.log(e); } console.log('b'); // 如果错误已经被处理,这行代码会执行
书写错误代码:
function foo1() { foo2();//调用foo2函数 console.log("foo1"); } function foo2() { var o = {}; o.func(); // 未定义这个函数,发生错误 } foo1();
将上面的代码使用try()……catch()进行错误处理后:
function foo1() { foo2();//调用foo2函数 console.log("foo1"); } function foo2() { var o = {}; o.func();//调用func()函数 } try { foo1();//调用foo1函数 } catch (e) { console.log("test");//如果错误输出test }
结果是:test
throw语句用来抛出一个用户自定义的异常。当前函数的执行将被停止(throw之后的语句将不会执行),并且控制将被传递到调用堆栈中的第一个catch块。如果调用者函数中没有catch块,程序将会终止。
try { var e1 = new Error("错误信息:橘猫吃胖了"); throw e1; //将错误对象e1抛出 } catch (error) { console.log(error.message);//输出错误信息 console.log(error === e1);//判断错误对象是否等于错误信息 }
类型 | 说明 |
---|---|
Error | 表示普通错误,其余6种类型的错误对象都继承自该对象 |
EvalError | 调用eval()函数错误,已经弃用,为了向后兼容,低版本还可以使用 |
RangeError | 数值超出有效范围,如“new Array(-1)” |
ReferenceError | 引用了一个不存在的变量,如“var a = 1; a + b;”(变量b未定义) |
SyntaxError | 解析过程语法错误,如“{ ; }”“if()”“var a = new;” |
TypeError | 变量或参数不是预期类型,如调用了不存在的函数或方法 |
URIError | 解析URI编码出错,调用encodeURI()、escape()等URI处理函数时出现 |