对象创建方法
对象字面量
1
var obj = { };
系统自带的构造函数Object()
1
var obj = new Object();
自定义构造函数
1
2
3
4
5
6
7
8
9
10
11
12// 与普通函数区别:1、函数名大驼峰命名;2、定义属性和方法为纯语句
function Teacher(name, sex) { // 传入参数-单个属性
// 当没有经过关键字new构造函数,就还没有实例化,此时this不存在
this.name = name;
this.sex = sex;
this.age = 25; // 默认属性
this.smoke = function() {
console.log('I am smoking');
}
}
var teacher = new Teacher('dunteng','male');Object.create()创建对象
1
2
3
4
5
6var obj = {
name: "xiaoming",
age: 18
};
// 将obj作为obj1的原型
var obj1 = Object.create(obj)
构造函数的内部原理
- 在函数体最前面隐式地加上
this={}
- 执行
this.xxx = xxx;
- 隐式地返回
this
构造函数能构造出对象的前提是要new它!从而触发构造函数的内部原理。
1 | function Person(name,height){ |
⚠来点捣乱的
1 | function Person(name, height) { |
结果person的值就变成了return出来的空对象。
在构造函数内部允许显示地return出一个对象(空对象也好其他对象也好),则new出来的实例结果就是对应的对象。⚠但是如果是显示return出数字、字符串、undefined或者null,即return的是五大原始值,则new出实例的时候仍然是return语句前面构造出来的this对象!
包装类
原始值是不能有属性和方法的。
看下面👇
这里的num依然是数字,但是它是用包装类Number new出来的数字对象,那么它就可以加属性和方法。同时它依旧可以运算,但是运算结果是原始值的数字。
同样的也有:
前面说了原始值不能有属性和方法,那么看下面的栗子:
1 | var num = 4; |
打印结果为undefined,也不报错。原因是存在包装类的隐式调用,如下:
1 | var num = 4; |
对于数组,有以下的可行操作:
1 | var arr = [1, 2, 3, 4]; |
数组是可以通过length来截断的,但字符串不行!
1 | var str = "1234"; |
分析:
1 | var str = "1234"; |
来练练
1 | var str = "abc"; |
分析如下:
1 | var str = "abc"; |
同闭包知识点结合的一题👇
1 | function Person(name, age, sex) { |
原型prototype
定义: 原型是function对象的一个属性,它定义了构造函数制造出的对象的公共祖先。通过该构造函数产生的对象,可以继承原型的属性和方法。原型也是对象。
利用原型特点和概念,可以提取共有属性
1
2
3
4
5
6
7
8
9
10Car.prototype.height = 1400;
Car.prototype.lang = 4900;
Car.prototype.carName = "BMW";
function Car(color, owner) {
this.owner = owner;
this.color = color;
}
var car = new Car('red', 'dunteng')
var car = new Car('pink', 'xiaolin')对象如何查看原型 通过隐式属性
__proto__
访问对象实例的属性的时候,先查看实例对象自己有没有,没有的话就通过它的隐式属性
__proto__
查看原型里有没有这个属性。💡
对象实例.__proto__
全等于对象构造函数.prototype
对象如何查看对象的构造函数 通过
constructor
constructor存在于原型中。
看典例👇
1 | Person.prototype.name = "sunny"; |
上面这个很容易理解,但下面改一下:
1 | Person.prototype.name = "sunny"; |
原型链
原型也有自己的原型,就形成了原型链。原型链的终端是Object.prototype。可以说绝大多数对象的最终都会继承自Object.prototype。
1 | GrandFather.prototype.lastName = "wang" |
前面有提到【绝大多数的对象最终都继承自Object.prototype】,那么什么对象例外呢?
原始值的123是没有方法的,所以要用包装类包装成Number类型,再调用toString方法,注意这里的toString方法是Number()自己重写的,不是Object()的toString,Object()的toString()打印出来的是[Object, Object]
call/apply
作用: 都能改变this
指向
区别: 后面传递的参数形式不同
1 | function Person(name, age) { |
这里说一下:
1 | test()实际上是test.call() |
使用call
改变this指向:
1 | function Person(name, age) { |
这里使用call将this的指向改变成了obj,或者说这里就把对象obj
拿来取代了this,或者说this就成了obj。
来个应用栗子👇
1 | function Person(name, age) { |
这样代码太冗余了,用call方法改进一下:
1 | function Person(name, age) { |
call需要把实参按照形参的个数一个个传进去,
apply需要传入一个实参数组
再来个栗子:
1 | function Wheel(WheelSize, style) { |
继承
继承发展史:
原型链
缺点:过多的继承了没用的属性
借用构造函数+call/apply
我上面讲call/apply的两个栗子就是
缺点:无法继承借用来的构造函数的原型,而且不方便。
共有原型
1
2
3
4
5
6
7
8
9
10
11
12
13
14Father.prototype.lastName = "xi"
function Father() {
}
function Son() {
}
function inherit(Target, Origin) {
Target.prototype = Origin.prototype;
}
inherit(Son, Father);//一定要先继承再定义哦
var son = new Son();这种做法有缺点,如果想要给Son对象的原型添加些属性,那么很不好的事,Father对象并不需要这些属性,因为要知道,它俩的原型指向的是同一个。
圣杯模式
借用一个中间层。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function inherit(Target, Origin) {
function F() { }
F.prototype = Origin.prototype;//这行和下一行顺序一定不能调换,否则就错了
Target.prototype = new F(); //F作为中间层,这样就算Target会修改原型,也只是修改了F,影响不到Origin
Target.prototype.constructor = Target; //手动添加构造器constructor
Target.prototype.uber = Origin.prototype; //记录真实的原型
}
function Father() { }
function Son() { }
Father.prototype = {
lastName: 'Lin'
}
inherit(Son, Father);
var son = new Son();
var father = new Father();上面这个继承还可以再优化一下,使用立即执行函数和闭包来优化:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18var inherit = (function () {
var F = function () { };
return function (Target, Origin) {
F.prototype = Origin.prototype;
Target.prototype = new F();
Target.prototype.constructor = Target;
Target.prototype.uber = Origin.prototype;
}
}())//整个是一个立即执行函数,即用即销毁,优化性能
//inherit里面的F通过闭包保存成了inherit的私有化属性,更加优化
function Father() { }
function Son() { }
Father.prototype = {
lastName: 'lin'
}
inherit(Son, Father)
var father = new Father();
var son = new Son();
命名空间
管理变量,防止污染全局,适用于模块化开发。
传统的方法是用对象包起来形成专有的命名空间,用的时候就通过xxx.xxxx.xxx这样的方式访问。
解决方式:使用立即执行函数和闭包
栗子👇
1 | var init = (function () { |
上面这两个函数变量命名互不影响,因为都存在于立即执行函数里面,而变量被闭包保存return带出来。实现模块开发,防止污染全局变量
对象的遍历
对象遍历不可能像遍历数组那样for循环,因为根本不知道对象的“长度”
所以要使用for key in obj
的方法:
1 | var obj = { |
我们知道obj.xxx
和obj['xxx']
都可以访问到对象属性,但是在for key in obj
循环里必须十一用obj['xxx']
的方式访问,obj.xxx
输出的为undefined
obj.name
实际上本质会先转化为obj['name']
,索引框里必须是字符串类型。
在上面代码中如果使用obj.xxx
的方式的话:
1 | var obj = { |
for key in obj
可以遍历出原型里的东西吗?答案是可以的(除了Object.prototype)。
但是我就只想遍历出目标对象自己的属性,怎么做?
利用hasOwnProperty(xxx)
方法来判断xxx
是不是obj自身的属性。
1 | var obj = { |
除了hasOwnProperty()
可以判断是不是属性,in
也可以。
1 | console.log('height' in obj);//true |
⚠注意:hasOwnProperty()
是判断是不是对象自己的非继承属性,in
指判断是不是属性,不管是不是继承来的。
💡再来说说instanceof
1 | A instanceof B |
它的语法理解就是:
看A对象是不是B构造函数构造出来的
更深层次的理解是看A对象的原型链上有没有B的原型
应用:判断一个变量是数组还是对象?
方法一:看constructor
方法二:用instanceof
方法三:看toString()
this
函数预编译过程中 this—>window
全局作用域里 this—>window
call/apply可以改变函数运行时this的指向
obj.func(); func()里面的this指向obj;即谁调用this就指向谁
这里补充一下预编译:
1 | function test(c) { |
1 | function test(c) { |
例题👇:
1 | var name = "222"; |
1 | var foo = 123; |
1 | var foo = 123; |
1 | var a = 5; |
1 | var a = 5; |
arguments.callee和function.caller
callee 是 arguments
对象的一个属性。它可以用于引用该函数的函数体内当前正在执行的函数。这在函数的名称是未知时很有用,例如在没有名称的函数表达式 (也称为“匿名函数”)内。
1 | //求阶乘 |
来说说Function.caller。
如果一个函数f
是在全局作用域内被调用的,则f.caller为`
null,相反,如果一个函数是在另外一个函数作用域内被调用的,则
f.caller指向调用它的那个函数.`
⚠该特性是非标准的,官方建议不要再生产环境中使用。
1 | function test1() { |
1 | function test1() { |
浅克隆/深克隆
【浅克隆】
1 | var obj = { |
浅克隆的原始值属性更改,另一个对象不受影响。但是引用值属性一改就全都改了。
为了避免这个问题就需要进行深度克隆
思想:
- 遍历对象
for (var prop in origin)
- 判断是不是原始值 ,同时排除null。
origin[prop] !== null && typeof (origin[prop]) == 'object'
- 判断是数组还是对象,利用
Object.prototype.toStringcall(origin[prop])
方法 - 建立相应的数组或对象
- 递归
1 | Object.prototype.num = 1; |
💡另外还可以使用 JSON 来实现深度克隆:
1 | var str = JSON.stringify(person1); |
但是json克隆无法克隆方法
haikyi