创建对象
对象可以完成某件事.
必须先有类,再通过类创建对象
在JavaScript中,只要给一个对象添加了属性和方法之后,就可以通过这个对象访问对应的属性和方法
- 方法一:通过默认的Object这个类(构造函数)来创建
var per = new Object();
//动态给对象添加属性和方法
per.name = "yzf";
per.age = 18;
per.say = function () {
console.log("hello");
};
console.log(per.name);
console.log(per.age);
per.say();
- 方法二:通过字面量来创建对象(语法糖)
var per = {};// 相当于 var obj = new Object();
per.name = "yzf";
per.age = 18;
per.say = function () {
console.log("hello");
};
console.log(per.name);
console.log(per.age);
per.say();
//方法二:
var per = {
name : "yzf",
age : 18,
say : function () {
console.log("hello");
}
};
console.log(per.name);
console.log(per.age);
per.say();
- 方法三:先自定义构造函数,再通过构造函数来创建对象
this关键字
- 每一个函数中都有一个this关键字
- this关键字的值是当前调用函数的那个对象(==谁调用了当前的函数, this就是谁==)
- 注意点:默认情况下所有的函数都是通过window调用的
function say() {
console.log("hello");
console.log(this); //window
}
say();
- 将一个函数和一个对象绑定在一起之后, 函数就变成了方法
var per = {
name: "yzf",
age: 18,
say: function () {
console.log("hello");
console.log(this);
}
}
console.log(per.name);
console.log(per.age);
// say();//报错,方法只能由所属对象调用
per.say();// this是per这个对象
- 注意点:
- 函数可以直接调用,在JavaScript中函数属于window对象
- 方法不可以直接调用,方法属于其他对象
工厂函数创建对象
function creatPerson(name, age) {
var obj = new Object();
obj.name = name;
obj.age = age;
obj.say = function () {
console.log("hello");
};
return obj;
}
var per1 = creatPerson("yzf", 18);
var per2 = creatPerson("zx", 18);
构造函数创建对象
- 构造函数:一个专门用于创建对象的函数
- 构造函数的名称首字母必须大写
- 构造函数必须通过new来调用
- 本质上是工厂函数的简化
- 通过Object对象,或者通过字面量, 或者通过工厂函数创建的对象, 我们无法判断这个对象是谁创建出来的但是通过构造函数创建的对象, 我们可以判断这个对象是谁创建出来的
- 默认情况下每一个对象都有一个隐藏的属性, 叫做constructor, 这个属性指向了创建当前对象的构造函数
function Person(name, age) {
this.name = name;
this.age = age;
this.say = function () {
console.log("hello");
};
}
var per1 = new Person("yzf", 18);
var per2 = new Person("zx", 17);
console.log(per1);
console.log(per2);
// console.log(per2.constructor); //指向Person构造函数
console.log(per1 instanceof Person); //判断per1是否由Person创建
性能问题:
默认情况下,只要创建一个对象就会在对象中开辟一块存储空间,该存储空间中会存储对象的所有数据
function Person(name, age) {
this.name = name;
this.age = age;
this.say = function () {
console.log("hello");
};
}
var per1 = new Person("yzf", 18);
var per2 = new Person("zx", 17);
console.log(per1.say === per2.say); //false 两个函数的地址不同
say相同,但每个say都保存在不同的地址里,消耗内存
解决构造函数性能问题
方法一:
- 注意点:==在JavaScript中,函数也是一个对象==
function say() {
console.log(this.name, this.age);
}
function Person(name, age) {
this.name = name;
this.age = age;
this.say = say;
}
var per1 = new Person("yzf", 18);
var per2 = new Person("zx", 17);
console.log(per1.say === per2.say); //true 两个say函数的地址相同
- 将方法定义在外面,然后将函数的地址赋值给属性,每次创建对象,say的地址都相同
- 弊端:==将函数定义在了全局作用域中, 所以如果定义了多个函数, 会导致全局作用域的名称匮乏==
方法二:
- 将所有函数都封装到另一个函数(对象)中,函数名称就不在全局作用域中,就不会导致全局作用域命名匮乏问题
- 然后将对象中方法的地址复制给使用者即可
var fns = {
say: function () {
console.log(this.name, this.age);
}
};
function Person(name, age) {
this.name = name;
this.age = age;
this.say = fns.say;
}
var per1 = new Person("yzf", 18);
var per2 = new Person("zx", 17);
console.log(per1.say === per2.say);// true
方法三(重要):
- 每一个构造函数都有一个默认的属性prototype
- ==prototype属性指向构造函数的原型对象==
- 为了解决全局作用域命名匮乏的问题,将所有的方法都放到了一个对象中,所以既然构造函数的prototype就对应一个对象,所以我们就可以将方法都放到这个对象中
- 注意点:
- 所有通过同一个构造函数创建出来的对象,都可以放到该构造函数的原型对象
- 所有通过同一个构造函数创建出来的对象,访问的都是同一个原型对象
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function () {
console.log(this.name, this.age);
};
var per1 = new Person("yzf", 18);
var per2 = new Person("zx", 17);
per1.say();
per2.say();
//===是在判断两个函数的地址是否相同
console.log(per1.say === per2.say); //true
- 注意点:
- 私有成员(一般就是非函数成员)放到构造函数中
- 共享成员(一般就是函数)放到原型对象中
- 如果重置了prototype记得修正 constructor 的指向
构造函数-对象-原型对象的关系
- 每个构造函数都有一个默认的属性prototype,这个属性指向一个对象(原型对象)
- 每个原型对象都有一个默认的属性constructor,这个属性指向原型对象对应的构造函数
- 每个对象都有一个默认的属性, proto,这个属性指向创建它的构造函数的原型对象
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function () {
console.log(this.name, this.age);
};
Person.prototype.type = "猪";
var per1 = new Person("zx", 17);
console.log(per1.type); //"猪"
console.log(Person.prototype.constructor === Person); //true
console.log(Person.prototype === per1.__proto__); //true
console.log(per1.__proto__.constructor === Person); //true
原型链
console.log(per1.__proto__.__proto__.__proto__);//null
属性和方法的查找顺序
- 属性
- 先在当前对象中查找有没有type属性, 如果有, 使用当前的
- 如果当前对象没有type属性,会到原型对象中查找,如果有,就使用原型对象中的type属性
- 如果原型对象中没有,会继续根据proto链条查找,如果找到null都没有, 就输出undefined
- 方法
- 会先在当前对象中查找有没有say方法, 如果有, 使用当前的
- 如果当前对象没有say方法,会到原型对象中查找,如果有,就使用原型对象中的say方法
- 如果原型对象中没有,会继续根据proto链条查找,如果找到null都没有, 就报错
function Person(name, age) {
this.name = name;
this.age = age;
// this.say = function () {
// console.log("我是say");
// }
// this.type = "我不是猪";
}
Person.prototype.type = "猪";
Person.prototype.say = function () {
console.log(this.name, this.age);
};
var per1 = new Person("zx", 17);
// per1.say(); //我是say
per1.say();//zx 17
// console.log(per1.type);//我不是猪
per1.type = "猪人";
console.log(per1.type);//猪人
//在直接通过对象访问属性的时, 不会修改原型中的属性
//会在当前对象中新增一个属性
console.log(per1.__proto__.type);//猪
解决构造函数性能的解决方案(最终)
// 定义了一个构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
// 给构造函数的原型对象添加属性和方法
Person.prototype.say = function () {
console.log(this.name, this.age);
};
Person.prototype.type = "人";
// 利用构造函数创建实例对象
var obj1 = new Person("lnj", 13);
var obj2 = new Person("zq", 18);
/*
私有成员(一般就是非函数成员)放到构造函数中
共享成员(一般就是函数)放到原型对象中
如果重置了 prototype 记得修正 constructor 的指向
*/
console.log(obj1.say === obj2.say); //true
自定义原型对象
- 原型对象也是一个对象,所以我们可以自定义构造函数的原型对象
- 注意点:自定义原型对象一定要保持三角恋的关系,一定要设置constructor: 所属的构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype = {
constructor:Person,
say:function () {
console.log(this.name, this.age);
}
};
var p1 = new Person("yzf", 18);
console.log(p1.__proto__);
p1.say();
console.log(p1.__proto__.constructor);
对象的公私有属性及方法
- 全局变量和函数:写在一对script标签中或者写在一个单独的JS文件中的变量和函数
- 局部变量和函数:在其它函数中定义的变量或者函数
- 默认情况下对象的属性和方法都是公有的
- 构造函数也是一个函数,所以在构造函数中直接定义的变量就是局部变量, 外界就不能访问
function Person() {
this.name = "yzf";
this.age = 18;
this.say = function () {
console.log(this.age, this.name);
}
}
var p1 = new Person();
p1.say();
// say();//报错,无法直接访问
实例属性方法和静态属性方法
- 实例:==通过构造函数创建出来的对象==
- 实例属性和实例方法:通过对象才能访问的属性和方法
- 静态属性和静态方法:不需要通过对象就能访问的属性和方法;通过构造函数能直接访问的属性和方法就称之为静态属性和方法
function Person(name, age) {
this.name = name;
this.age = age;
this.say = function () {
console.log(this.name, this.age);
}
}
var p1 = new Person("yzf", 18); //实例
p1.say(); //实例方法
console.log(p1.name); //实例属性
Person.type = "人";
Person.eat = function () {
console.log("吃饭");
};
console.log(Person.type); //静态属性
Person.eat(); //静态方法
继承
方式一:借用原型链
- 由于直接创建了父类对象作为子类的原型对象,所以在指定原型对象的时候就必须指定父类的参数
- 企业开发中每个子类对象的参数都可能不一样, 这种方案不行
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function () {
console.log(this.name, this.age);
};
function Student(score) {
this.score = score;
}
//修改Student的原型对象
Student.prototype = new Person("yzf", 18);//指定原型对象的时候就必须指定父类参数
//修改原型对象constructor指向Student
Student.prototype.constructor = Student;
var stu1 = new Student(99);
方式二:利用构造函数
三种方法:
- bind方法:
- 修改函数内部的this,但是不会调用这个函数,会返回一个新的函数
var obj = {
name:"yzf"
};
function test(a, b) {
console.log(a + b);
console.log(this);
}
// test(10,20);//this是window
var fn = test.bind(obj,10,20);
fn();//this是obj
- call方法:
- 修改函数内部的this的, 但是会调用这个函数(参数依次用逗号隔开)
var obj = {
name:"yzf"
};
function test(a, b) {
console.log(a + b);
console.log(this);
}
test(10,20);//this是window
test.call(obj,10,20); //this是obj
- apply方法:
- 修改函数内部的this的,但是会调用这个函数(参数需要放在一个数组中)
var obj = {
name:"yzf"
};
function test(a, b) {
console.log(a + b);
console.log(this);
}
test(10,20);//this是window
test.apply(obj,[10,20]); //this是obj
- 构造函数实现继承
function Person(name, age) {
this.name = name;
this.age = age;
// this.say = function () {
// console.log(this.age, this.name);
// }
}
Person.prototype.say = function () {
console.log(this.age, this.name);
};
function Student(score, name, age) {
Person.call(this, name, age);
//仅仅是借用父类构造函数来给子类动态添加属性或方法,没有其它关系
this.score = score;
}
var stu1 = new Student(99,"yzf",18);
stu1.say(); //报错,无法访问父类构造函数原型对象的方法
方式三:组合方式一
- 借用构造函数动态添加
- 将原型修改为父类的原型
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function () {
console.log(this.age, this.name);
};
function Student(score, name, age) {
Person.call(this, name, age);
this.score = score;
}
//修改子类的原型对象为父类的原型
Student.prototype = Person.prototype;
Student.prototype.constructor = Student;
Student.prototype.eat = function () {
console.log("吃东西");
};
var stu1 = new Student(59.9, "yzf", 18);
stu1.say();
stu1.eat();
var p1 = new Person("zx", 13);
console.log(p1.__proto__.constructor);//指向Student构造函数
- 缺点:会破坏父类的三角关系.父类和子类为同一个原型对象,子类原型添加属性和方法后父类也会收到影响.不推荐使用
方式四:组合方式(最终版)
function Person(name, age) {
this.name = name;
this.age = age;
}
//为父类原型添加方法
Person.prototype.say = function () {
console.log(this.name, this.age);
};
//子类
function Student(score, name, age) {
Person.call(this, name, age);
this.score = score;
}
//修改子类的原型对象为父类的对象
Student.prototype = new Person();
Student.prototype.constructor = Student;
//创建子类对象
var stu1 = new Student(66, "yzf", 18);
//创建父类对象
var p1 = new Person("zx", 99);
//为子类原型添加方法
Student.prototype.eat = function () {
console.log("吃东西");
};
stu1.say(); //子类对象可以访问父类原型中的方法
stu1.eat();//子类对象可正常访问子类原型的方法
p1.say();//父类对象可以访问父类原型中的方法
p1.eat();//报错,父类对象无法访问子类原型中的方法
多态
function Dog(name) {
this.name = name;
this.say = function () {
console.log("汪汪");
}
}
function Cat(name) {
this.name = name;
this.say = function () {
console.log("喵喵");
}
}
var dog = new Dog("旺财");
var cat = new Cat("猫咪");
function bark(animal) {
console.log(animal.name);
animal.say();
}
bark(dog);
bark(cat);
对象的增删改查
- 查询增加对象属性
- 访问对象的属性(==查询==)
- 对象.属性名称方式, 操作属性
- 对象["属性名称"]方式, 操作属性
console.log(p.name);
console.log(p["age"]);
p.name = "zx";
p["age"] = 18;
- 对象如果没有就会新增
- ==删除==对象中的属性
delete p.name;
- 判断对象中==是否存在==某个属性
- 方式一:"属性名称" in 对象 存在返回true,没有返回false
//会在当前对象中查找,如果没有就在原型对象上查找,直到null
console.log("age" in p);
console.log("name" in p);
- 方式二:通过对象 .hasOwnProperty("属性名称")方式来判断
//只会在当前对象上查找
console.log(p.hasOwnProperty("age"));
- 判断某个属性是否只存在于原型对象中
function res(obj, name) {
if(!obj.hasOwnProperty(name)){
if(name in obj){
return true
}
}
return false
}
遍历对象
- JavaScript中对象是可以直接遍历的
- 利用高级for循环:for(var key in obj){}
- 会将对象中的每一个属性依次取出来赋值给key,有多少个属性,循环就会执行多少次
function Person() {
this.age = 18;
this.name = "yzf";
this.say = function () {
console.log(this.name, this.age);
}
}
var p1 = new Person();
for(var key in p1){
// console.log(p1[key]);
if(p1[key] instanceof Function){
continue
}
console.log(p1[key]);//去对象中查找当前遍历到名称的属性
console.log(p1.key);//代表去对象中查找名称叫做key的属性
}
对象的拷贝
- 拷贝:将一个对象赋值给另外一个对象
浅拷贝
将A对象赋值给B对象:修改B对象的属性和方法会影响到A对象的属性和方法
function Person(dog) {
this.age = 18;
this.name = "yzf";
this.say = function () {
console.log(this.name, this.age);
};
this.dog = dog;
}
var p1 = new Person({
name:"mm",
age:3
});
var p2 = new Person();
//直接拷贝
// p2 = p1;
// p2.name = "zx";
// console.log(p1.name);//zx
//逐一拷贝
function copy(o1, o2) {
for(var key in o1){
o2[key] = o1[key];
}
}
copy(p1, p2);
p2.name = "zx";
p2.dog.name = "wc";
p2.say(); //zx 18
console.log(p2.dog);//ww
p1.say(); //yzf 18
console.log(p1.dog);//ww 同样指向dog对象
深拷贝
将A对象赋值给B对象:修改B对象的属性和方法不会影响到A对象的属性和方法, 我们称之为深拷贝
function Person(dog) {
this.age = 18;
this.name = "yzf";
this.say = function () {
console.log(this.name, this.age);
};
this.dog = dog;
}
var p1 = new Person({
name:"mm",
age:3
});
var p2 = new Person();
function deepCopy(o1, o2) {
//遍历对象
for(var key in o1){
var item = o1[key];
//判断属性是否为引用数据类型
if(o1[key] instanceof Object){
//新建一个空对象
var temp = new Object();
deepCopy(item, temp);
o2[key] = temp;
}else {
//基本数据类型
o2[key] = o1[key];
}
}
}
deepCopy(p1, p2);
// console.log(p2);
p2.dog.name = "wc";
console.log(p1.dog.name); //mm
console.log(p2.dog.name); //wc
注意点:
- 默认情况下对象之间的直接赋值都是浅拷贝
- 默认情况下一个对象的属性如果是基本数据类型,那么都是深拷贝
- 如果对象的属性包含了引用数据类型, 才真正的区分深拷贝和浅拷贝
时间对象
- 创建时间对象
var date = new Date();
- 注意点:如果通过逐个传入参数的方式指定月份, 会出现误差1
- 月份在JS中的Date对象中是从0开始的
var date1 = new Date(2019, 5, 4, 18, 52, 55);
console.log(date1); //Tue Jun 04 2019 18:52:55 GMT+0800 (中国标准时间)
var date2 = new Date("2019-5-4 18:52:55");//
console.log(date2); //Sat May 04 2019 18:52:55 GMT+0800 (中国标准时间)
- 获取当前时间距离1970年1月1日相差的毫秒
var date3 = Date.now();
- 不同方法在不同浏览器输出的格式不同
console.log(date.toTimeString());
console.log(date.toLocaleTimeString());
console.log(date.toDateString());
console.log(date.toLocaleDateString());
- 时间格式化
var date = new Date();
function formatDate(date) {
var arr = [];
arr.push(date.getFullYear());
arr.push("-");
arr.push(date.getMonth() + 1);
arr.push("-");
arr.push(date.getDay());
arr.push(" ");
arr.push(date.getHours());
arr.push(":");
arr.push(date.getMinutes());
arr.push(":");
arr.push(date.getSeconds());
return arr.join("");
}
var res = formatDate(date);
console.log(res);
Math对象
- Math是一个对象
- Array/String/Date是内置构造函数
- 常用方法:
Math.PI; //圆周率
Math.floor() //向下取整
Math.ceil() // 向上取整
Math.round() //四舍五入
Math.abs() //绝对器
Math.random() //生成随机数
var num = Math.PI;
console.log(num);//3.141592653589793
var num1 = Math.floor(num);
console.log(num1);//3
var num2 = Math.ceil(num);
console.log(num2);//4
var num3 = Math.round(num);
console.log(num3);//3
var num4 = -1;
var num5 = Math.abs(num4);
console.log(num5);//1
var num6 = Math.random();
console.log(num6);
- 练习:生成1-10的随机数(==MDN文档里有==)(并不安全)
function getRandomIntInclusive(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min; //含最大值,含最小值
}
var res = getRandomIntInclusive(1, 10);
console.log(res);
- 补充:安全的随机数
var array = new Uint8Array(1);
window.crypto.getRandomValues(array);
console.log("Your lucky numbers:");
for (var i = 0; i < array.length; i++) {
console.log(array[i]);
}