复习对象

对象创建方法

  • 对象字面量

    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
    6
    var obj = {
    name: "xiaoming",
    age: 18
    };
    // 将obj作为obj1的原型
    var obj1 = Object.create(obj)



构造函数的内部原理

  1. 在函数体最前面隐式地加上this={}
  2. 执行this.xxx = xxx;
  3. 隐式地返回this

构造函数能构造出对象的前提是要new它!从而触发构造函数的内部原理。

1
2
3
4
5
6
7
8
9
10
function Person(name,height){
// var this ={}
this.name = name;
this.height = height;
this.say = function(){
console.log(this.say);
}
// return this
}
var person = new Person('xiaolin',180);

⚠来点捣乱的

1
2
3
4
5
6
7
8
9
function Person(name, height) {
this.name = name;
this.height = height;
this.say = function () {
console.log(this.say);
}
return {};//返回一个空对象
}
var person = new Person('xiaolin', 180);

结果person的值就变成了return出来的空对象。

在构造函数内部允许显示地return出一个对象(空对象也好其他对象也好),则new出来的实例结果就是对应的对象。⚠但是如果是显示return出数字、字符串、undefined或者null,即return的是五大原始值,则new出实例的时候仍然是return语句前面构造出来的this对象!

1570849572989



包装类

原始值是不能有属性和方法的

看下面👇

1570850068707

这里的num依然是数字,但是它是用包装类Number new出来的数字对象,那么它就可以加属性和方法。同时它依旧可以运算,但是运算结果是原始值的数字。

1570850243772

同样的也有:

1570850309748

前面说了原始值不能有属性和方法,那么看下面的栗子:

1
2
3
var num = 4;
num.len = 3;
console.log(num.len);

打印结果为undefined,也不报错。原因是存在包装类的隐式调用,如下:

1
2
3
4
5
6
7
8
var num = 4;
num.len = 3;
// 原始值是不能有属性和方法的,但是在这里系统会自动执行:new Number(num).len = 3以避免报错
// 但随即又delete自己。即刚new完Number随机就把自己删了

// 然后在console.log(num.len)的时候,讲道理num.len已经不存在了会报错的
// 但是系统为了避免报错又隐式地执行了new Number(num).len,所以结果为输出undefined
console.log(num.len);



对于数组,有以下的可行操作:

1
2
3
var arr = [1, 2, 3, 4];
arr.length = 2;
console.log(arr);//[1,2]

数组是可以通过length来截断的,但字符串不行!

1
2
3
var str = "1234";
str.length = 2;
console.log(str);//"1234"

分析:

1
2
3
4
5
6
7
8
var str = "1234";
str.length = 2;
// 执行到str.length = 2的时候系统隐式地执行了new Number(str).length=2,
// 以避免报错,但随机就销毁了
console.log(str);//"1234"
// 执行console.log(str.length)的时候系统隐式地执行了new Number(str).length,
// 而length是String类型内置的属性,可以访问
console.log(str.length);//4



来练练

1
2
3
4
5
6
7
var str = "abc";
str += 1;
var test = typeof (str);
if (test.length == 6) {
test.sign = "hello world";
}
console.log(test.sign);

分析如下:

1
2
3
4
5
6
7
8
9
var str = "abc";
str += 1; //str变成了"abc1"
var test = typeof (str); //test为"string"
if (test.length == 6) {
test.sign = "hello world";//test是字符串,原始值不能有属性,隐式地执行
//new Number(test).sign="hello world",随即销毁
}
//隐式地执行包装类转换:new Number(test).sign,随即销毁
console.log(test.sign);//undefined


同闭包知识点结合的一题👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Person(name, age, sex) {
var a = 0;
this.name = name;
this.age = age;
this.sex = sex;
function sss() {
a++;
console.log(a);
}
this.say = sss;
}

var person1 = new Person();
person1.say();
person1.say();
var person2 = new Person();
person2.say();



原型prototype

  • 定义: 原型是function对象的一个属性,它定义了构造函数制造出的对象的公共祖先。通过该构造函数产生的对象,可以继承原型的属性和方法。原型也是对象。

  • 利用原型特点和概念,可以提取共有属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Car.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

    1570862901392

1570863065304

  • 对象如何查看对象的构造函数 通过constructor

    1570863239261

    constructor存在于原型中。



看典例👇

1
2
3
4
5
6
7
8
9
10
Person.prototype.name = "sunny";
function Person() {

}
var person = new Person();
console.log(person.name); //"sunny"

Person.prototype.name = "jenny";

console.log(person.name); //"jenny"

上面这个很容易理解,但下面改一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Person.prototype.name = "sunny";
function Person() {
}
var person = new Person();
// 在new的时候存在一个隐式过程👇
// function Person() {
// var this = {
// __proto__: Person.prototype 用__proto__记录了Person.prototype的地址
// }
// }
console.log(person.name); //"sunny"

Person.prototype = {
name: 'jenny'
};
// 将Person.prototype重新指向了一个新的空间,而person作为new出来的实例是通过__proto__查找
// 原型的,__proto__中记录的东西并没有改变,仍指向原来的Person.prototyped空间
console.log(person.name); //"sunny"



原型链

原型也有自己的原型,就形成了原型链。原型链的终端是Object.prototype。可以说绝大多数对象的最终都会继承自Object.prototype。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GrandFather.prototype.lastName = "wang"
function GrandFather() {
this.name = "祖父"
}
var grandfather = new GrandFather();

Father.prototype = grandfather;
function Father() {
this.name = "父亲"
}
var father = new Father();

Son.prototype = father;
function Son() {
this.name = "儿子"
}
var son = new Son();
console.log(son.lastName);//"wang"

1570865370449


前面有提到【绝大多数的对象最终都继承自Object.prototype】,那么什么对象例外呢?

1570866805009


1570867335732

原始值的123是没有方法的,所以要用包装类包装成Number类型,再调用toString方法,注意这里的toString方法是Number()自己重写的,不是Object()的toString,Object()的toString()打印出来的是[Object, Object]



call/apply

作用: 都能改变this指向

区别: 后面传递的参数形式不同

1
2
3
4
5
6
7
8
9
function Person(name, age) {
this.name = name;
this.age = age;
}

Person('lin', 18);
// 这里直接调用Person函数就没把它当作构造函数new了,里面的this就指向全局window
console.log(name);
console.log(age);

这里说一下:

1
test()实际上是test.call()

使用call改变this指向:

1
2
3
4
5
6
7
8
function Person(name, age) {
this.name = name;
this.age = age;
}
var obj = {};

Person.call(obj, 'lin', 18);
console.log(obj);//obj里面age为18,name为lin

这里使用call将this的指向改变成了obj,或者说这里就把对象obj拿来取代了this,或者说this就成了obj。

来个应用栗子👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Person(name, age) {
this.name = name;
this.age = age;
this.sex = sex;
}

function Student(name, age, sex, tel, grade) {
this.name = name;
this.age = age;
this.sex = sex;
this.tel = tel;
this.grade = grade;
}

var student = new Student('lin', 18, 'male', 137, 6)

这样代码太冗余了,用call方法改进一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Person(name, age) {
this.name = name;
this.age = age;
this.sex = sex;
}

function Student(name, age, sex, tel, grade) {
Person.call(this, name, age, sex)
//上面结束后实际上 就有一个 this = {name:'lin',age:'18',sex:'male' }
this.tel = tel;
this.grade = grade;
}

var student = new Student('lin', 18, 'male', 137, 6)


call需要把实参按照形参的个数一个个传进去,

apply需要传入一个实参数组

再来个栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Wheel(WheelSize, style) {
this.WheelSize = WheelSize;
this.style = style;
}
function Sit(c, sitColor) {
this.c = c;
this.sitColor = sitColor
}
function Model(height, width, len) {
this.height = height;
this.width = width;
this.len = len;
}
function Car(WheelSize, style, c, sitColor, height, width, len) {
Wheel.apply(this, [WheelSize, style]);
Sit.apply(this, [c, sitColor]);
Model.apply(this, [height, width, len])
}
var myCar = new Car(100, '花里胡哨', '真皮', 'red', 1800, 1900, 4900)
console.log(myCar);



继承

继承发展史:

  1. 原型链

    缺点:过多的继承了没用的属性

  2. 借用构造函数+call/apply

    我上面讲call/apply的两个栗子就是

    缺点:无法继承借用来的构造函数的原型,而且不方便。

  3. 共有原型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    Father.prototype.lastName = "xi"
    function Father() {

    }
    function Son() {

    }

    function inherit(Target, Origin) {
    Target.prototype = Origin.prototype;
    }

    inherit(Son, Father);//一定要先继承再定义哦
    var son = new Son();

    这种做法有缺点,如果想要给Son对象的原型添加些属性,那么很不好的事,Father对象并不需要这些属性,因为要知道,它俩的原型指向的是同一个。

  4. 圣杯模式

    借用一个中间层。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function 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();

    1570887149405

    上面这个继承还可以再优化一下,使用立即执行函数和闭包来优化:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
       var 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
   var init = (function () {
var name = 'abc';
function callName() {
console.log(name);
}
return function () {
callName();
}
}())


var _init = (function () {
var name = 'def';
function callName() {
console.log(name);
}
return function () {
callName();
}
})()

init();
_init();

上面这两个函数变量命名互不影响,因为都存在于立即执行函数里面,而变量被闭包保存return带出来。实现模块开发,防止污染全局变量



对象的遍历

对象遍历不可能像遍历数组那样for循环,因为根本不知道对象的“长度”

所以要使用for key in obj的方法:

1
2
3
4
5
6
7
8
9
10
var obj = {
name: 'lin',
age: 12,
sex: "male",
height: 180
}

for (var key in obj) {
console.log(obj[key]);//这里的每一个key本身都是String类型
}

我们知道obj.xxxobj['xxx']都可以访问到对象属性,但是在for key in obj循环里必须十一用obj['xxx']的方式访问,obj.xxx输出的为undefined

obj.name实际上本质会先转化为obj['name'],索引框里必须是字符串类型。

在上面代码中如果使用obj.xxx的方式的话:

1
2
3
4
5
6
7
8
9
10
var obj = {
name: 'lin',
age: 12,
sex: "male",
height: 180
}

for (var key in obj) {
console.log(obj.key);//这里变成了obj['key'],可是obj中没有key这个键,所以undefined
}

for key in obj可以遍历出原型里的东西吗?答案是可以的(除了Object.prototype)。

但是我就只想遍历出目标对象自己的属性,怎么做?

利用hasOwnProperty(xxx)方法来判断xxx是不是obj自身的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var obj = {
name: 'lin',
age: 12,
sex: "male",
height: 180,
__proto__: {
hobby: "guitar"
}
}

for (var key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(obj[key]);
}
}


除了hasOwnProperty()可以判断是不是属性,in也可以。

1
2
console.log('height' in obj);//true
console.log('hobby' in obj);//true

⚠注意:hasOwnProperty()是判断是不是对象自己的非继承属性,in指判断是不是属性,不管是不是继承来的。


💡再来说说instanceof

1
A instanceof B

它的语法理解就是:

​ 看A对象是不是B构造函数构造出来的

​ 更深层次的理解是看A对象的原型链上有没有B的原型

应用:判断一个变量是数组还是对象?

方法一:看constructor

1570897037168

方法二:用instanceof

1570897168419

方法三:看toString()

1570897447379



this

  • 函数预编译过程中 this—>window

  • 全局作用域里 this—>window

  • call/apply可以改变函数运行时this的指向

  • obj.func(); func()里面的this指向obj;即谁调用this就指向谁


这里补充一下预编译:

1
2
3
4
5
function test(c{
      var a = 123;
      function b({ }
    }
     test(1);

1570903506616

1
2
3
4
5
function test(c{
      var a = 123;
      function b({ }
    }
    new test(1);

1570903422900

例题👇:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var name = "222";
var a = {
name: "111",
say: function () {
console.log(this.name);
}
}
var fun = a.say;
fun()
a.say()

var b = {
name: "333",
say: function (fun) {
fun()
}
}
b.say(a.say);
b.say = a.say;
b.say();
1
2
3
4
5
6
var foo = 123;
function pr() {
this.foo = 234;//this指向全局window
console.log(foo);
}
pr();
1
2
3
4
5
6
7
var foo = 123;
function pr() {
this.foo = 234;//this指向Object.create(pr.prototype)
console.log(foo);//123
console.log(this.foo);//234
}
new pr();
1
2
3
4
5
6
7
8
9
10
var a = 5;
function test() {
a = 0;
alert(a);
alert(this.a);
var a;
alert(a);
}

test();//0 5 0
1
2
3
4
5
6
7
8
9
10
var a = 5;
function test() {
a = 0;
alert(a);
alert(this.a);
var a;
alert(a);
}

new test();//0 undefined 0



arguments.callee和function.caller

1570931723015

calleearguments 对象的一个属性。它可以用于引用该函数的函数体内当前正在执行的函数。这在函数的名称是未知时很有用,例如在没有名称的函数表达式 (也称为“匿名函数”)内。

1
2
3
4
5
6
7
//求阶乘
var num = (function (n) {
if (n == 1) {
return 1;
}
return arguments.callee(n - 1) * n
})(100)

来说说Function.caller

如果一个函数f是在全局作用域内被调用的,则f.caller为`null,相反,如果一个函数是在另外一个函数作用域内被调用的,则f.caller指向调用它的那个函数.`

⚠该特性是非标准的,官方建议不要再生产环境中使用。

1
2
3
4
5
function test1() {
console.log(arguments.callee.caller);
}
test1();
//执行结果为null


1
2
3
4
5
6
7
8
9
function test1() {
test2()
}
function test2() {
console.log(arguments.callee.caller);
console.log(arguments.callee.caller === test1);
}
test1();
//执行打印结果为test1函数体和true



浅克隆/深克隆

【浅克隆】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var obj = {
name: 'abc',
age: 18,
sex: 'male'
}

function clone(origin, target) {
var target = target || {};
for (var key in origin) {
target[key] = origin[key]
}
return target;
}
var newObj = clone(obj);

1570935515334

浅克隆的原始值属性更改,另一个对象不受影响。但是引用值属性一改就全都改了。

1570936074035

为了避免这个问题就需要进行深度克隆

思想:

  1. 遍历对象 for (var prop in origin)
  2. 判断是不是原始值 ,同时排除null。 origin[prop] !== null && typeof (origin[prop]) == 'object'
  3. 判断是数组还是对象,利用Object.prototype.toStringcall(origin[prop])方法
  4. 建立相应的数组或对象
  5. 递归


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Object.prototype.num = 1;
var person1 = {
name: 'zhang',
age: 18,
sex: 'male',
say: function () { console.log(666); },
children: {
first: {
name: 'zhang2',
age: 11
}
},
car: ['Benz', 'Mazda']
}

var person2 = deepClone(person1);

function deepClone(origin, target) {
var target = target || {},
toStr = Object.prototype.toString,
arrStr = "[object Array]";

for (var prop in origin) {
if (origin.hasOwnProperty(prop)) {
if (origin[prop] !== null && typeof (origin[prop]) == 'object') {
target[prop] = toStr.call(origin[prop]) == arrStr ? [] : {};
deepClone(origin[prop], target[prop]);
} else {
target[prop] = origin[prop];
}
}
}
return target;
}

💡另外还可以使用 JSON 来实现深度克隆:

1
2
var str = JSON.stringify(person1);
var person2 = JSON.parse(str);

但是json克隆无法克隆方法

haikyi