一、对象常用属性(掌握)
1、prototype:用来设置或获取(__proto__)对象的原型属性。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sex = 'man'
Person.prototype.work = function (type) {
console.log(this.name + " is work for " + type);
}
var p1 = new Person('tom', 30)
console.log(p1);
p1.work('web')
console.log(p1.sex);
console.log(p1.__proto__); // __proto__是一个指向prototype的地址指针
</script>
</html>
2、constructor:用于获取实例对象的构造函数类型,经常性用于判断变量的数据类型是对象还是数组。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
var arr = []
console.log(arr.constructor === Array);//true
var isTrue = new Boolean()
console.log(isTrue.constructor === Boolean);//true
console.log(isTrue.constructor === Array);//false
console.log(typeof arr, typeof isTrue);// 用typeof测试引用类型的数据时,返回的都是object
</script>
</html>
二、对象常用方法(了解)
1、isPrototypeOf:用于指示对象是否存在于另一个对象的原型链中。如果存在,返回 true ,否则返回 false 。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sex = 'man'
Person.prototype.work = function (type) {
console.log(this.name + ' is work for ' + type);
}
var p1 = new Person('tom', 44)
var obj = new Object()
var p2 = p1
console.log(Person.prototype.isPrototypeOf(p1));//true
console.log(Person.prototype.isPrototypeOf(obj));//false
console.log(Person.prototype.isPrototypeOf(p2));//true
</script>
</html>
2、hasOwnProperty:每个实例对象都有一个hasOwnProperty()方法,用来判断某一个属性到底是本地属性,还是继承自prototype对象的属性。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sex = 'man'
Person.prototype.work = function (type) {
console.log(this.name + ' is work for ' + type);
}
var p1 = new Person('tom', 44)
var obj = new Object()
var p2 = p1
console.log(p1.hasOwnProperty('name'));//true
console.log(p1.hasOwnProperty('age'));//true
console.log(p1.hasOwnProperty('sex'));//false
</script>
</html>
3、getOwnPropertyDescriptor:用来获取对象属性的描述信息,其中包括属性的value(值)、writable(是否可写)、configurable(是否可设置)、enumerable(是否可枚举)。
注意:必须通过Object对象调用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sex = 'man'
Person.prototype.work = function (type) {
console.log(this.name + ' is work for ' + type);
}
var p1 = new Person('tom', 44)
var obj = new Object()
var p2 = p1
console.log(Object.getOwnPropertyDescriptor(p1, 'name'));// {value: "Bill", writable: true, enumerable: true, configurable: true}
console.log(Object.getOwnPropertyDescriptor(p1, 'sex')); // undefined sex为非私有属性,不能获取对应的描述
</script>
</html>
4、defineProperty:用来修改对象属性的描述信息。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sex = 'man'
Person.prototype.work = function (type) {
console.log(this.name + ' is work for ' + type);
}
var p1 = new Person('tom', 44)
var obj = new Object()
var p2 = p1
Object.defineProperty(p1, 'name', {
configurable: false,
writable: false,
enumerable: true,
value: 'tom'
})
console.log(p1);
for (var i in p1) {
console.log(p1[i]);
}
</script>
</html>
5、propertyIsEnumerable:用来判断改对象的属性是否可以用for...in枚举(遍历)。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sex = 'man'
Person.prototype.work = function (type) {
console.log(this.name + ' is work for ' + type);
}
var p1 = new Person('tom', 44)
var obj = new Object()
var p2 = p1
console.log(p1.propertyIsEnumerable('name'));//true
console.log(p1.propertyIsEnumerable('sex'));//false
</script>
</html>
三、对象运算符
1、in:用来判断某个实例是否含有某个属性,不管是不是本地属性。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
function Person(name, age) {
this.name = name
this.age = age
this.show = function () {
console.log(this.name);
}
}
Person.prototype.sex = 'man'
Person.prototype.work = function (type) {
console.log(this.name + ' is work for ' + type);
}
var p1 = new Person('tom', 34)
console.log('name' in p1);//true
console.log('age' in p1);//true
console.log('sex' in p1);//true
console.log('addr' in p1);//false
</script>
</html>
2、delete:用于删除对象对其属性的引用,当属性失去引用之后会被垃圾回收,不再存在。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
function Person(name, age) {
this.name = name
this.age = age
this.show = function () {
console.log(this.name);
}
}
Person.prototype.sex = 'man'
Person.prototype.work = function (type) {
console.log(this.name + ' is work for ' + type);
}
var p1 = new Person('tom', 34)
console.log(p1);
delete p1.name
delete p1.show
console.log(p1);
</script>
</html>
3、instanceof:判断一个实例是否属于某种类型。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
function Person(name, age) {
this.name = name
this.age = age
this.show = function () {
console.log(this.name);
}
}
Person.prototype.sex = 'man'
Person.prototype.work = function (type) {
console.log(this.name + ' is work for ' + type);
}
var p1 = new Person('tom', 34)
console.log([] instanceof Array);//true
console.log([] instanceof Object);//true
console.log([] instanceof String);//false
console.log(p1 instanceof Person);//true
</script>
</html>
四、继承(面试中重点)
1、什么是继承?
让子类(构造函数)拥有父类(构造函数)所有特性(包括属性和方法)。在JS中,一切皆对象,所有的类都继承于Object。
如:Array -> Object fn -> Function -> Object
instanceof用于判断指定对象属于哪个类,或者判断是不是某一个类的原型。
2、继承实现
(1)子类实例化出来的对象拥有父类中所有属性和方法;
(2)子类实例化出来的对象属于子类,也属于父类。
3、实现继承的方式
3.1、构造继承
3.1.1、基本思想
利用call或者apply把父类中通过this指定的属性和方法复制(借用)到子类创建的实例中。
3.1.2、核心
使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)。
3.2.3、缺点
(1)方法都在构造函数中定义;
(2)只能继承父类的实例属性和方法,不能继承原型属性/方法,无法实现函数复用;
(3)每个子类都有父类实例函数的副本,影响性能。
3.2.4、语法
父类.call(this, 参数列表)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
//定义父类
function Person(name, sex) {
this.name = name
this.sex = sex
this.eat = function () {
return this.name + ' like eat fruits'
}
}
//定义子类
function Student(name, sex, age) {
//继承父类Person
Person.call(this, name, sex)
//子类拥有自己的属性和方法
this.age = age
this.study = function (course) {
return this.name + ' like study' + course
}
}
var stu1 = new Student('zs', 'man', 20)
console.log(stu1);
console.log(stu1.eat());
console.log(stu1.study('web'));
</script>
</html>
父类.apply(this, [参数列表]) 或: 父类.apply(this, arguments)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
//定义父类
function Person(name, sex) {
this.name = name
this.sex = sex
this.eat = function () {
return this.name + ' like eat fruits'
}
}
Person.prototype.addr = '陕西西安'//构造继承不能继承原型
//定义子类
function Student(name, sex, age) {
Person.apply(this, [name, sex])
//子类拥有自己的属性和方法
this.age = age
this.study = function (course) {
return this.name + ' like study' + course
}
}
var stu1 = new Student('zs', 'man', 23)
console.log(stu1);
console.log(stu1.eat());
console.log(stu1.study('web'));
</script>
</html>
3.2、原型链继承
3.2.1、每创建一个函数,该函数就会自动带有一个 prototype 属性。该属性是个指针,指向了原型对象,并且可以访问原型对象上的所有属性和方法。
3.2.2、核心 :将父类的实例作为子类的原型。
3.2.3、缺点:父类新增原型方法/原型属性,子类都能访问到,父类一变其它的都变了。
补充:__proto__是每个对象所特有的属性,函数有prototype属性。生成对象时,对象的__proto__指向函数的prototype。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
//父类原型
function Person() { }
Person.prototype.type = 'chinese'
Person.prototype.eat = function () {
console.log(this.type + 'like eat');
}
//子类
function Teacher() { }
//子类继承父类原型
Teacher.prototype = new Person()
//让子类拥有自己的属性和方法
Teacher.prototype.name = 'zs'
Teacher.prototype.teach = function (course) {
console.log(this.name + '主讲' + course);
}
var t1 = new Teacher()
Person.prototype.sex = 'man'
console.log(t1);
t1.eat()
t1.teach('web')
</script>
</html>
3.3、对象冒充(对象伪造)
对象冒充是指一个对象冒充另外一个对象来实行其他对象的方法,即一个对象将其他对象的方法当做是自身的方法来执行。它不是真正意义的继承。子类实例化出来的对象拥有父类所有属性和方法,但子类并不属于这个父类。
实现步骤:
第一步:克隆父类构造器(原生)中的属性和方法
this.父类 = 父类
this.父类(name, sex)
delete this.父类
第二步:克隆父类原型中的属性和方法
for(var i in 父类.prototype){
子类.prototype[i] = 父类.prototype[i]
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
// 定义父类
function Person(name, sex) {
this.name = name
this.sex = sex
this.fn = function () {
console.log('.......')
}
}
Person.prototype.addr = '广东深圳'
Person.prototype.eat = function () {
console.log(this.name + '最爱吃水果!')
}
//定义子类
function Student(name, sex, age) {
//第一步:克隆父类构造器(原生)中的属性和方法
this.Person = Person
this.Person(name, sex)
delete this.Person
this.age = age
this.study = function (course) {
console.log(this.name + ' like ' + course);
}
}
//第二步:克隆父类原型中的属性和方法
for (var i in Person.prototype) {
Student.prototype[i] = Person.prototype[i]
}
Student.prototype.sports = function () {
console.log(this.name + 'like sports');
}
var stu1 = new Student('ls', 'man', 32)
console.log(stu1);
stu1.eat()
stu1.study('web')
console.log(stu1 instanceof Student);//true
console.log(stu1 instanceof Person);//false
</script>
</html>
3.4、组合继承(构造继承+原型链继承)(真正的继承)
3.4.1、基本思想
所有的实例都能拥有自己的属性,并且可以使用相同的方法,组合继承避免了原型链和借用构造函数的缺陷,结合了两个的优点,是最常用的继承方式。
3.4.2、核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后再通过将父类实例作为子类原型,实现函数复用。
3.4.3、缺点:调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)。
3.4.4、实现步骤
(1)继承父类构造器上的属性和方法
父类.apply(this,arguments)
(2)继承父类原型链上的属性和方法
子类.prototype = Object.create(父类.prototype)
(3)找回子类丢失的构造器
子类.prototype.constructor = 子类
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
// 定义父类
function Person(name, sex) {
this.name = name
this.sex = sex
this.fn = function () {
console.log('.......')
}
}
Person.prototype.addr = '广东深圳'
Person.prototype.eat = function () {
return this.name + '最爱吃水果!'
}
//定义子类
function Student(name, sex, age) {
//继承的第一步,继承父类的构造器上的属性和方法
// Person.call(this, name, sex)
Person.apply(this, arguments)
// console.log(arguments)// arguments用来获取传过来的实参,它是一个伪数组
this.age = age
this.study = function (course) {
console.log(this.name + " like " + course);
}
}
//继承得第二步:继承父类原型链上的属性和方法
//方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
Student.prototype = Object.create(Person.prototype)
//继承的第三步:找回丢失的构造器
Student.prototype.constructor = Student
var stu = new Student('zs', 'man', 20)
console.log(stu);
console.log(stu.eat());
stu.study('web')
</script>
</html>
五、对象克隆(拷贝)
对象克隆即把父对象的所有属性和方法,拷贝进子对象。对象克隆分为浅克隆和深克隆。
1、浅克隆(浅拷贝)
只复制基本数据类型的数据。如果用浅拷贝复制引用类型的数据,会出现修改被克隆对象的数据。注意:浅拷贝只适合克隆基本类型的数据。
补充:JS中数据类型分为基本类型和引用类型两种。基本类型的数据存放在栈中,引用类型的数据存放在堆中。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
var student = {
sno: '001',
sname: 'ww',
sex: 'man',
score: [12, 12, 12]
}
//封装浅克隆的方法
function cloneObj(obj) {
var newObj = {}
for (var key in obj) {
newObj[key] = obj[key]
}
return newObj
}
var obj1 = cloneObj(student)
//修改obj1这个克隆过来的数据
obj1.name = 'zs'
console.log('克隆过来的数据', obj1);
console.log('原对象的数据', student);
// 以上代码说明克隆过来的对象的基本类型的数据的修改不会影响到原对象的数据
obj1.score.shift()
obj1.score.push(100)
console.log('克隆过来的对象', obj1)
console.log('原对象的数据', student)
// 如果用浅拷贝复制引用类型的数据,会出现修改被克隆对象的数据。
// 结论:浅拷贝只适合克隆基本类型的数据。
</script>
</html>
2、深克隆(深拷贝)
深克隆就是能够实现真正意义上的数组和对象的拷贝。思路:递归调用"浅克隆"。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var student = {
sno: '0001222',
sname: '王老五',
sex: '男',
tel: null,
score: [89, 98, 90, 78],
course: {
cid: '001',
cname: 'H5网页设计'
}
}
// 封装深克隆的方法
function deepCloneObj(data) {
var newData = data.constructor === Array ? [] : {}
for (var key in data) {
// 判断值是否为null,因为null的类型是'object'
if (data[key] === null) {
newData[key] = null
} else {
// 判断是否为引用类型
if (typeof data[key] === 'object') { // 如果是引用类型的数据用深拷贝
newData[key] = deepCloneObj(data[key])
} else { // 如果是基本类型的数据用浅拷贝
newData[key] = data[key]
}
}
}
return newData;
}
var newStudent = deepCloneObj(student);
console.log(newStudent);
newStudent.score.push(88)
newStudent.sname = '侯先生'
console.log('克隆过来的对象', newStudent)
console.log('原对象的数据', student)
</script>
</body>
</html>