或许为了自身写,或许为了知己写!

学习JavaScript基础篇

前言

浅入JS基础。本篇从工作、学习总结归纳JavaScript,列举以下技巧。

  • 文章涉及:
  • 变量声明
  • typeof
  • 数据类型
  • 理解函数
  • 全局对象(GO)
  • 闭包
  • 作用域
  • 作用域链
  • 立即执行函数与函数表达式
  • 运算符
  • 对象
  • new关键字
  • 包装类
  • 原型
  • 原型链
  • Object.create()
  • call
  • apply
  • 继承
  • 命名空间
  • 链式调用
  • 对象枚举
  • this
  • 数组
  • 类数组
  • try catch

var

  • 即任何变量,如果未经声明就使用,此变量就为全局变量所拥有。
1
2
3
4

// 没有使用var,在全局声明变量num,变量num属于window
num = 10;
console.log(window.num) // 10
  • 一切声明的全局变量,全是window的属性。
1
2
3
// 使用var在全局声明变量num,变量num就属于window。
var num = 10;
console.log(window.num) // 10

注意

一旦经历var操作,所得出的属性属于window,这种属性叫做不可配置属性,不可配置属性不能delete

1
2
var num = 1;
delete window.num // false
  • 全局定义相同变量,后面变量能把前面变量覆盖。
1
2
3
4
var num = 1; // 首次定义  
...
var num = 10; // 后面再次定义
console.log(num) // 10

typeof

返回数据类型字符串表达。

1
2
3
4
5
6
7

var num = 1;
typeof num // 'number'

var x = 'app';
typeof x // 'string'
...

typeof 存在比较特殊地方。

1
2
3

typeof null // 'object'
typeof Array // 'object'

通过typeof能返回:number/string/boolean/object/function类型。

能判断:undefined/number/boolean/function

不能判断: null/object/Array。

对于NaN,返回number,NaN是not a number的缩写。

数据类型

包括基本数据类型与引用数据类型。

基本数据类型(栈stack) 包括:number/string/boolean/null/undefined。基本数据类型访问顺序按值访问,由高向低分配,占内存最大是8MB,其中string是特殊的栈,由程序员分配。

引用类型(堆heap) 包括:function/object/Array。引用类型在栈内存中保存的是对象在堆内存中的引用地址(指针),向高分配,系统自由分配。

  • 空间分配方式:
    栈:由操作系统自由分配释放。
    堆:一般由程序员分配释放。
  • 数据结构:
    栈:先进后出的模式。
    堆:可以看成一颗树。

函数

预编译发生在函数执行前一刻。
预编译过程:

  • 创建AO对象:Activetion Object
  • 找形参和变量声明,将变量和形参名作为AO属性名,值为:undefined
  • 将实参值和形参统一
  • 在函数体里面找函数声明,值赋予函数体

代码片段:

1
2
3
4
5
function fn(x, y) {
z = 3;
function bar() {}
}
fn(1, 2);

  • 首次创建Activetion Object
    1
    2
    3
    4
    5
    x:         undefined
    y: undefined
    z: undefined
    bar: undefined
    arguments: undefined
  • 然后实参值与形参统一
    1
    2
    3
    4
    5
    x:         1
    y: 2
    z: 3
    bar: undefined
    arguments: <1, 2>
  • 函数体中如果有函数声明,值赋予函数体
    1
    2
    3
    4
    5
    x:         1
    y: 2
    z: 3
    bar: <function>
    arguments: <1, 2>

Global Object(GO)

简称全局对象,与widow关系是全等(Global Object === window)。

代码片段

1
2
3
4
5
6
7
8
function a() {
function b() {
function c() {}
c();
}
b();
}
a();

通过上述代码片段,函数a在全局定义,同时创建自己的scope属性,指向它父函数作用域链Global Object;函数a执行,scope属性指向自身Activetion Object;通过全局Global Object返回,拿到函数a结果。

1
2
3
4
5

a defined a.[[scope]] --> 0: GO

a doing a.[[scope]] --> 0: a AO
--> 1: GO

函数b定义,是在函数a执行结果上定义,同时创建自己的scope;函数b执行,scope属性指向自身Activetion Object,经过函数a的Activetion Object,传递给全局Global Object返回,拿到函数b结果。

1
2
3
4
5
6
7
// ...省略函数a 
b defined b.[[scope]] --> 0: aAO
--> 1: GO

b doing b.[[scope]] --> 0: bAO
--> 1: aAO
--> 2: GO

函数c定义,是在函数b执行结果上定义,同时创建自己的scope;函数c执行,scope属性指向自身Activetion Object,经过函数b的Activetion Object,经过函数a的Activetion Object,通过全局Global Object返回,拿到函数c结果。

1
2
3
4
5
6
7
8
9
10
// ...省略函数a 
// ...省略函数b
c defined c.[[scope]] --> 0: bAO
--> 1: aAO
--> 2: GO

c doing c.[[scope]] --> 0: cAO
--> 1: bAO
--> 2: aAO
--> 3: GO

通过上述分析可以看到,函数是定义在它父劳动成果上。

闭包

内部的函数被保存到外部,将生成闭包。闭包会导致原作用域链不释放,造成内存泄露。通俗理解里面的函数比外面的函数活着还长。

规则:

  • 一定是嵌套函数
  • 内层函数一定操作了外层函数的局部变量
  • 外层函数一定将内层函数返回外部, 并且被全局变量保存

代码片段:

1
2
3
4
5
6
7
8
9
10
function fn() {
function bar() {
var y = 2;
console.log(x);
}
var x = 1;
return bar;
}
var f = fn();
f();

闭包运用

1
2
3
4
5
6
7
8
9
10
11
12
13
function fn() {
var arr = [];
for(var i=0; i<10; i++) {
arr[i] = function() {
console.log(i) // 输出10次10
}
}
return arr;
}
var myArr = fn();
for(var j=0; j<10; j++) {
myArr[i]();
}

打印输出10次10,是因为声明i变量是全局变量。
for语句可以这样

1
2
3
4
5
6
7
8
for(var i=0; i<10; i++) { 
// ...
}
// 修改下面是等效
var i = 0;
for(; i<10; i++) {
// ...
}

对于上面代码打印0-10,可通过闭包修改,设置局部变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function fn() {
var arr = [];
for(var i=0; i<10; i++) {
(function(j) { // 形参
arr[j] = function() {
console.log(j) // 输出0-10
}
})(i)
}
return arr;
}
var myArr = fn();
for(var j=0; j<10; j++) {
myArr[i]();
}

作用域

ES5中提出全局作用域和函数作用域。

全局作用域忘记,可以往上翻翻开篇提到的部分;这里补充函数作用域,函数作用域是在函数内部的变量。

代码片段:

1
2
3
4
5
6
7
8
9
10
function fn() {
var num = 10;
console.log(num); // 10
function bar() {
console.log(num) // 10
}
bar();
}
console.log(num) // 抛出 error
fn();

通过上述结果可以理解,函数会创建自己的作用域’{}’,并且作用域分层,函数内层域能访问函数外层域,反之不行
值得注意的是在if/for/switch/while语句后面的“{}”,不会创建自己的作用域,定义的变量保存在已经定义的作用域中。

1
2
3
4
if(true) {
var num = 10;
}
console.log(num) // 10

变量num虽然在’{ }’中,但是if不会创建自己的作用域,变量num相当于定义在全局。

作用域链

链可以理解为一层一层的向上寻找。在作用域中呢?
看看相关代码:

1
2
3
4
5
var num = 10;
function fn() {
console.log(num);
}
fn(); // 输出结果 10

函数fn创建自己的域,在函数内层域中寻找变量num,发现当前域没有定义变量num,继续向函数外层域寻找变量,最后在外层域中找到变量num,输出10。

目前说明作用域链是一层一层向上寻找,直到找到所需变量或者最顶层也没找到就停止寻找并返回结果

那么能不能理解成一层一层向函数父域中寻找呢?看下面相关代码:

1
2
3
4
5
6
7
8
9
10
11
12
13

var num = 10;
function fn() {
console.log(num);
}

function bar() {
var num = 20;
(function() {
fn();
})()
}
bar(); // 输出结果10

上述代码输出结果10。通过这个可以分析函数会在创建函数的这个域中寻找值,不是调用这个函数域,说向父域中不准确。

立即执行函数

可以理解成执行完就释放。
代码片段:

1
2
3
(function(x, y) { // x, y是形参 
console.log(x + y);
})(1, 2) // 1, 2 是实参

可以定义一个变量接收。

1
2
3
4
5
6
var num = (function(x, y) {
var z = x + y;
return z;
})(1, 2)

console.log(num) // 3

注意:只有表达式才能被执行符号“( )”执行

1
2
3
4
5
var fn = function() {
console.log('hello');
}()
// 首次调用输出 hello
// 再次调用console.log(fn) 输出 undefined

函数调用。

1
2
3
4
function fn() {
console.log(this);
}
fn();

函数使用call方法调用。

1
2
3
4
function foo() {
console.log(this);
}
foo.call();

这两个函数打印值是相等。

函数后面添加运算符”( )”,调用函数什么也不输出。

1
2
3
function fn(x, y) {
console.log(x, y); // 无输出
}(1, 2)

函数前面支持正负非符号。

1
2
3
4
+|-|! function fn() {
console.log('hello'); // hello
}
fn();

“( )”运算符。

1
2
var num = (1 - 1, 1 + 1); 
console.log(num) // 2 计算前面结果,再计算后面运算结果,返回后面结果

if语句有函数。

1
2
3
4
5
var x = 1;
if(function() {}) {
x += typeof f;
}
console.log(x) // 1undefined 此时函数转成表达式,在执行

对象模型

由属性和属性对应值组成。

1
2
3
4
5
6
7
8
var obj = {
// 属性:值
name: 'sunny',
age: 18,
say: function() {
console.log('hello')
}
}

可以操作对象:

  • 访问属性:obj.name
  • 删除属性:delete obj.age
  • 修改属性:obj.age = 28
  • 添加属性:obj.sex = ‘male’

创建对象

  • 使用字面量创建对象

    1
    var  obj = {}
  • 使用系统自带的构造函数

    1
    var obj = new Object()
  • 自定义

    1
    2
    function Person(){}
    var person = new Person();

注意:
构造函数和普通函数结构上没区别,存在new才会返回一个对象。
构造函数命名规则,大驼峰命名

new关键字

在构造函数前面添加new关键字,执行以下操作:

  • 在函数体最前面隐式的加上this = { }
  • 执行 this.xxx = xxx
  • 隐式的返回 this
    相关代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function Student(name, age) {
    // 隐式添加 this = {
    // name: name,
    // ...
    // }
    this.name = name;
    this.age = age;
    this.sex = 'male';
    // 隐式添加 return this
    }
    var student = new Student('sunny', 18);

构造函数显示return空对象或者原始值;输出的结果不一样。

return对象

1
2
3
4
5
6
7
8
function Student(name, age) {
this.name = name;
this.age = age;
this.sex = 'male';
return {}; // 返回一个空对象
}
var student = new Student('sunny', 18);
console.log(student) // {}

return原始值

1
2
3
4
5
6
7
8
9
function Student(name, age) {
this.name = name;
this.age = age;
this.sex = 'male';
// 或者return 原始值; 相当于return this,返回一个对象
return 123;
}
var student = new Student('sunny', 18);
console.log(student) // {name: "sunny", age: 18, sex: "male"}

自定义对象好处

1
2
3
4
5
6
7
8
9
10
function Car(color) { // color参数 
this.color = color; // color 接受参数
this.health = 100;
this.run = function() {
this.health --;
}
}
// car变量写成Car走预编译环节,进行覆盖
var car1 = new Car('red'); // {color: "red", health: 100, run: ƒ}
var car2 = new Car('green');// {color: "green", health: 100, run: ƒ}

通过上述代码结果可以看出car1与car2属性相互各自独立互不影响。

包装类

原始值没有属性和方法。

1
2
3
4
5
6
// eg 
var num = 1;
var num = new Number(num);
num.a = 'a';
console.log(num) // Number {1, a: "a"}
console.log(num * 2) // 2 能计算,又返回原类型

String添加属性:

1
2
3
4
var str = 'a';
var str = new String(str);
str.a = 'a';
console.log(str.a) // a

Number添加属性:

1
2
3
4
// eg: 
var num = 4;
num.len = 3;
console.log(num.len) // undefined

执行过程:

  • 执行num.len时,发生new Number(num).len = 3新建一个数字对象,该对象len赋值为3,然后delete销毁。
  • 再次访问重新创建数字对象,添加一个len属性,与上一步new Number不同。
  • 输出结果undefined。

boolean添加new。

1
2
3
4
5
6
7
8
9
10
var bol = new Boolean('true');

// eg
var str = 'abc';
str +=1;
var test = typeof str;
if(test.length == 6) {
test.sign = 'typdof返回的结果可能是string';
}
console.log(test.sign);

在if里面创建 new String(‘test’).sign = ‘typdof返回的结果可能是string’。
在最外层再次访问,重新创建new String(‘test’).sign。
最终输出结果 undefined。

解析片段:

1
2
3
4
5
6
7
8
9
var str = 'abc';
str +=1;
var test = typeof str;
if(test.length == 6) {
// 创建new String('test').sign = 'typdof返回的结果可能是string'
test.sign = 'typdof返回的结果可能是string';
}
// 再次访问重新创建new String('test').sign
console.log(test.sign);

注:undefined / null 没有String。

原型

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

相关代码:

1
2
3
4
5
6
7
8
// Person.prototype 看做原型 
// Person.prototype = {} 看做祖先
Person.prototype.name = 'sunny';
function Person() {}
var p = new Person();
// p.__proto__.constructor 指向 functon Person(){}
// p.__proto__ === Person.prototype
console.log(p.name) // sunny

通过constructor更改指向。

1
2
3
4
5
6
7
function Person() {}
function Car() {}
Car.prototype = {
// constructor更改了指向
constructor: Person;
}
var car = new Car();

new过程构造函数内部做了什么。

  • 内部隐式添加this对象;
  • 改变this执向(指向原构造函数原型);
  • 返回一个新对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
// eg 
Person.prototype.name = 'sunny';
function Person() {
// var this = {
// __proto__: Person.prototype
// }
}
var person = new Person();
var obj = {
name: 'cherry'
}
person.__proto__ = obj;
console.log(person.name) // cherry

prototype在new前

1
2
3
4
5
Person.prototype.name = 'sunny';
function Person() {}
Person.prototype.name = 'cherry';
var p = new Person();
console.log(p.name) // cherry

通过上述代码看出prototype有提升作用,后面定义能把之前定义覆盖

prototype在new后

1
2
3
4
5
Person.prototype.name = 'sunny';
function Person() {}
var p = new Person();
Person.prototype.name = 'cherry';
console.log(p.name) // cherry

可以看出prototype定义在new之后,值没有变化;prototype有提升作用,后面重新定义能把之前定义覆盖。

prototype对象形式 写法
在new前

1
2
3
4
5
6
7
Person.prototype.name = 'sunny';
function Person() {}
Person.prototype = {
name: 'cherry'
}
var p = new Person();
console.log(p.name) // cherry

通过上述看出Person原型给修改了,没有new生成就改变了

在new之后

1
2
3
4
5
6
7
Person.prototype.name = 'sunny';
function Person() {}
var p = new Person();
person.prototype = {
name: 'cherry'
}
console.log(p.name) // sunny

Person函数里面proto没有修改,只是在原基础上修改Person.prototype属性。

原型链

Object.prototype是所有对象的最终原型。

1
2
3
4
5
// eg 
Person.prototype.__proto__ = Object.prototype
function Person() {}
Person.prototype.name = 'sunny';
var p = new Person()

引用值可以自己操作自己 (原型链继承模式)

1
2
3
4
5
6
7
8
9
10
11
12

function Father() {
this.name = 'sunny';
this.age = 46;
}
var f = new Father();

Son.prototype = f;
function Son() {}
var s = new Son();
s.age++
console.log(f.age) // 46

揭秘原型与原型链取值

相关代码:

1
2
3
4
5
6
7
8
9
Person.prototype = { 
name: 'sunny',
say: function() {
console.log(this.name)
}
}
function Person() {}
var p = new Person();
console.log(p.say) // sunny

修改上面代码:

1
2
3
4
5
6
7
8
9
10
11
Person.prototype = { 
name: 'sunny',
say: function() {
console.log(this.name)
}
}
function Person() {
this.name = 'cherry';
}
var p = new Person();
console.log(p.name) // cherry

通过上述代码片段发现实例对象就近取值,如果没有找到值,沿着原型链继续往上寻找。

Object.create

语法:Object.create(原型)。

1
2
3
4
var obj = { 
name: 'sunny'
}
var obj1 = Object.create(obj);

1
2
3
Person.prototype.name = 'sunny'; 
function Person() {}
var p = Object.create(Person.prototype);

绝大多数对象最终都会继承自Object.create,null除外。

call (继承)

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

function Son(name, age, sex) {
Person.call(this, name, age);
this.sex = sex;
}
var son = new Son('sunny', 18, 'male');
console.log(son) // {name: "sunny", age: 18, sex: "male"}

上述代码看出:能共用父类属性,支持传参。

apply (继承)

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

function Son(name, age, sex) {
Person.apply(this, [name, age]);
this.sex = sex;
}
var son = new Son('sunny', 18, 'male')
console.log(son) // {name: "sunny", age: 18, sex:"male"}

上述代码看出:能共用父类属性,支持传参,参数是数组形式。
对比可以总结归纳:call与apply传参序列不同,都能改变this指向
call或者apply 参数为null/undefined时,执行JS全局对象浏览器中的window。

1
2
3
4
5
6
7
8
function fn() { 
foo.apply(null, arguments);
}

function foo() {
console.log(arguments)
}
foo(1, 2, 3) // 1, 2, 3

继承

  • 原型链继承,结合子类原型与父类实例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 代码片段 
    Person.prototype.sleep = function() {}
    function Person(name, age) {
    this.name = name;
    this.age = age;
    }
    function Son(sex) {
    this.sex = sex;
    }
    Son.prototype = new Person(); // 子类的原型为父类实例对象
    var son = New Son('male');
    console.log(son)

javascript

如果在子类原型添加方法,能继承吗? 修改代码:

在new Person之前:

1
2
3
4
5
6
7
8
9
10
11
12
Person.prototype.sleep = function() {} 
function Person(name, age) {
this.name = name;
this.age = age;
}
function Son(sex) {
this.sex = sex;
}
Son.prototype.eat = function() {} // 在这里...
Son.prototype = new Person();
var son = new Son('male');
console.log(son)

在new Person()之后:

1
2
3
4
5
6
7
8
9
10
11
12
13

Person.prototype.sleep = function() {}
function Person(name, age) {
this.name = name;
this.age = age;
}
function Son(sex) {
this.sex = sex;
}
Son.prototype = new Person();
SOn.prototype.eat = function() {}; // 在这里...
var son = new Son('male');
console.log(son)

上述代码在new Person之前子类原型添加方法失败,原因是更改了原型的指向。
该继承缺点:无法实现多继承;
在子类原型添加方法和属性,需在Son.prototype=new Person之后。

call/apply构造函数继承,如果忘记可以查看上面call/apply介绍。
这里补充父类上原型属性和方法能否被call/apply继承。
相关代码:

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

Son.prototype.eat = function() {}
function Son(name, age, sex) {
Person.call(this, name, age); // ...能不能继承sleep函数
this.sex = sex;
}
var son = new Son('sunny', 18, 'male');
console.log(son);

实例son输出说明,call方法不能继承父类原型上的方法和属性;父类构造函数调用两次:一次在子类构造函数内调用,一次在创建子类原型调用。

原型链与构造函数组合
相关代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Person.prototype.sleep = function() {}
function Person(name, age) {
this.name = name;
this.age = age;
}

function Son(name, age, sex){
Person.call(this, name, age);
this.sex = sex;
}
Son.prototype = new Person(); // 第一步
Son.prototype.constructor = Son; // 第二步
Son.prototype.eat = function() {};
var son = new Son('sunny', 18, 'male');
console.log(son)

实例son输出说明,子类可以继承父类属性以及父类原型属性和方法;缺点父类的构造函数生成两份实例。

组合继承
通过子类原型和父类原型指向同一个对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Person.prototype.sleep = function() {}
function Person(name, age) {
this.name = name;
this.age = age;
}

function Son(name, age, sex) {
Person.call(this, name, age);
this.sex = sex;
}
Son.prototype = Person.prototype; // ...在这里
Son.prototype.eat = function() {};
var son = new Son('sunny', 18, 'male');
console.log(son)

代码son输出说明,子类可以继承父类原型上方法和属性。

组合继承优化

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

function Son(name, age, sex) {
Person.call(this, name, age);
this.sex = sex;
}
Son.prototype = Object.create(Person.prototype);
Son.prototype.constructor = Son;
Son.prototype.eat = function() {};
var son = new Son('sunny', 18, 'male');
console.log(son);

实例son继承借助创建对象,子类继承父类所有属性和方法。

函数共享原型
对于指向同一个对象继承。
相关代码:

1
2
3
4
5
6
Person.prototype.name = 'sunny'; 
function Person() {}
function Son() {}
Son.prototype = Person.prototype;
var son = new Son();
console.log(son.name); // sunny

可以将代码整合优化,编写一个公共函数共享原型,一定要在new之前使用。

1
2
3
4
5
6
7
8
9
10
Person.prototype.name = 'sunny'; 
function Person() {}
function Son() {}
// 编写inherit函数共享原型
function inherit(target, origin) {
target.prototype = origin.prototype;
}
inherit(Son, Person); // new之前先继承后使用
var son = new Son();
console.log(son.name); // sunny

继续对上面代码进行修改,在函数inherit内部使用一个构造函数中转proto指向。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

Person.prototype.name = 'sunny';
function Person() {}
function Son() {};
function inherit(target, origin) {
function F(){
F.prototype = origin.prototype;
target.prototype = new F();
target.prototype.constructor = target;
target.prototype.uber = origin.prototype;
}
}
inherit(Son, Person);
var person = new Person();
var son = new Son();
console.log(son.name) // undefined
console.log(person.name) // sunny

// Son原型添加属性,不影响原构造函数与原 原型
Son.prototype.sex = 'male';
console.log(son.sex) // male
console.log(Person.sex) // undefined

上述代码块中沿着目标实例son.proto找到构造函数F.prototype,指给原构造函数Person.prototype,此方法称为圣杯模式继承

抽离核心代码:

1
2
3
4
5
6
7
8
9
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;
}
})()

命名空间

管理变量,防止污染全局,适用于模块化开发。
之前开发中使用对象形式。

1
2
3
4
5
6
7
var obj = { 
name: 'sunny',
sayName: function() {
console.log(this.name)
}
}
obj.sayName(); // 打印结果: sunny

有了立即执行函数结合闭包,演变出以下改进。

1
2
3
4
5
6
7
8
9
10
11
12
var name = 'sunny';
var init = (function(){
var name = 'cherry';
function sayName() {
console.log(name); // cherry
}

return function() {
sayName();
}
})()
init();

上述代码使用闭包形式通过私有属性执行。

链式调用

模仿jQuery链式调用。

1
obj.eat().drink.slee();

浏览器控制台输出函数结果,会默认值返回undefined。利用对象方法中this指向当前对象,模仿jQuery链式调用,默认返回this即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var obj = { 
eat: function() {
console.log('eat');
return this;
},
drink: function() {
console.log('drink');
return this;
},
sleep: function() {
console.log('sleep');
return this;
}
}
obj.eat().drink().sleep(); // eat, drink, sleep

对象枚举

打印对象属性对应的值一般使用for…in 。

1
2
3
4
5
6
7
var obj = {
name: 'sunny',
age: 18
}
for(var prop in obj) {
console.log(obj.prop); // undefined
}

在接收对象值应该写成这样obj[prop],因为在对象内部会隐式转换成obj.prop。

hasOwnProperty:检测该属性在该对象中是否存在。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var obj = { 
name: 'sunny',
age: 18,
__proto__: {
lastname: 'cherry',
__proto__: Object.prototype
}
}

for(var prop in obj) {
if(obj.hasOwnProperty(prop)) {
console.log(obj[prop]) // sunny, 18, cherry ...
}
}

  • 只有对象能用hasOwnProperty。
  • 是对象自己的属性返回true,反之false。
  • 能返回原型及原型链上的属性,一旦延伸到原型链顶端不会找到该属性,返回false(系统自带的返回true,自己设置的返回true)。

in:能不能访问对象属性,包括原型。

1
2
3
4
5
6
7
8
9
var obj = { 
name: 'sunny',
age: 18,
__proto__:{
lastname: 'cherry',
__proto__: Object.prototype
}
}
console.log('name' in obj) // true

instanceof
官方解释:
A instanceof B,A对象是不是B构造函数构造出来的。
个人理解换种说法:A对象的原型上有没有B的原型。
相关代码:

1
2
3
4
5
6
7
8
9
10
function Person() {} 
var person = new Person();
var obj = {};

person instanceof Person // true
person instanceof Object // true
[] instanceof Object // true
[] instanceof Array // true
obj instanceof Person // false
obj instanceof Array // false

通过上述代码发现数组与对象在instanceof中返回值,可以使用instanceof区分数组与对象。

数组与对象的区分还可以使用constructor和toString.call()。

1
2
3
4
5
6
7
8
var arr = []; 
var obj = {};
console.log(arr.constructor) // ƒ Array() { [native code] }
console.log(obj.constructor) // ƒ Object() { [native code] }

// toString.call() 在Object原型上属性
console.log(Object.prototype.toString.call(arr)) // [object Array]
console.log(Object.prototype.toString.call(obj)) // [object Object]

toString.call()可以实现深度克隆。

1
2
3
4
5
6
7
8
9
10
11
12
var obj = { 
name: 'sunny',
age: 18,
car: ['visa', 'BMW'],
wife: {
name: 'cherry',
son: {
name: 's'
}
}
}
var targetObj = {};

实现targetObj对象拥有对象obj所有属性。
思路:

  • 首先使用for…in遍历对象。
  • 判断是否属于原始值,是数组还是对象。
  • 建立相应数组与对象。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    // ...
    function deepClone(target, origin) {
    var target = target || {};
    var toStr = Object.prototype.toString;
    var 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(target[prop], origin[prop]);
    }else{
    target[prop] = origin[prop];
    }
    }
    }
    return target;
    }
    deepClone(targetObj, obj);

相反,对象拥有简单属性类型,可以实现浅度克隆。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

var obj = {
name: 'sunny',
age: 18
}
var targetObj = {};

function clone(target, origin) {
var target = target || {};
for(var prop in origin) {
if(origin.hasOwnProperty(prop)) {
target[prop] = origin[prop]
}
}
return target;
}
clone(targetObj, obj);

this

  • this规则:
  • 由new调用,绑定到新创建的对象。
  • 由call/apply调用,绑定到指定的对象。
  • 由上下文对象调用,绑定到那个上下文对象。
  • 默认在严格模式下绑定到undefined,否则绑定到全局。

这里补充this案例

1
2
3
4
5
6
7
8
9
10
var name = 'sunny'; 
var obj = {
name: 'cherry',
say: function() {
console.log(this.name)
}
}
var fn = obj.say;
fn(); // sunny
obj.say(); // cherry

fn运行过程可以理解下面这样,把对象obj中函数say方法交给函数fn拥有,当前this就指向所在全局window。

1
2
3
4
5
// ...变量省略 
var fn = say function () {
console.log(this.name)
}
// ...输出语句省略

案例2

1
2
3
4
5
6
7
var foo = 123; 
function print() {
var foo = 456;
this.foo = 789;
console.log(foo);
}
print();

可以使用上述规则④,this绑定到全局,因此运行函数输出 456。

案例3

1
2
3
4
5
6
7

var foo = 123;
function Print() {
this.foo = 234;
console.log(foo)
}
new Print();

函数Print运行,使用上述规则④,this绑定到全局,改变了全局foo值,当前构造函数Print没有this,沿着原型链向往寻找,最终在window上找到,因此new print()输出234。

arguments.callee

1
2
3
4
5
6
7
8
9
10
11
12
13
function foo() {
console.log(arguments);
//Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]

console.log(arguments.callee);
// ƒ foo() {
// console.log(arguments.callee)
// }

console.log(arguments.callee == foo) // true

}
foo(1, 2);

上述输出值,arguments.callee指运行时的函数。

fn.caller

1
2
3
4
5

function foo() {
console.log(foo.caller) // foo函数
}
foo();

上述代码输出值,fn.caller指函数自身foo。
callee和caller在es5严格模式下都不能使用

数组

改变原数组

  • push() 向数组最后一位添加
  • pop() 从最后一位开始剔出去
  • shift() 从数组前面删除
  • unshift() 在数组前面添加
  • reserver() 逆转数组
  • sort() 排序
  • splice(从第几位开始, 截取长度, 在切口处添加新的数据)

不改变数组

  • concat() //合并数组
  • slice(从该位置开始截, 截取到该位)
  • split() 返回字符串

类数组

类数组必须有length属性,能像数组/对象一样使用。
原始类型的值不能拥有属性和方法。

如何使用:
自动创建(看生命周期),自动销毁(调用完函数之后)

典型类数组arguments

1
2
3
4
5

function foo() {
console.log(arguments)
}
foo(1, 2, 3);

向类数组push

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var obj = { 
'2': 'a',
'3': 'b',
'length': 2,
'push': Array.prototype.push,
'splice': Array.prototype.splice
}
obj.push('d');
console.log(obj)
// {2: "d", 3: "b", length: 3, push: ƒ}

obj.push('f');
console.log(obj);
// {2: "d", 3: "f", length: 4, push: ƒ}

通过输出,push后类对象length递增,类对象属性为索引,索引最大length-1,根据当前length-1取索引值。

try…catch

规则

  • try里面发生错误,不会执行错误后try里面的代码。
  • 如果try里面没有报错,不会执行catch里面代码;反之会执行catch里面代码。
    1
    2
    3
    4
    5
    try{ 
    console.log('ok')
    }catch(e) {
    console.log('error');
    }

归纳六种error信息

  • EvalError:eval的使用与定义不一致。
  • RangeRrror:数值越界。
  • ReferenceError:非法或不能识别的引用数组。
  • SyntaxError:发生语法解析错误。
  • TypeError:操作数据类似错误。
  • URIRrror:URI处理函数使用不当。

ES5严格模式
使用’use strict’(字符串,反之代表是JS语句,JS语句没有规定这种写法)启动严格模式 遵循es5.0模式,不再兼容es3一些不规则语法,推荐局部。

不支持 with/arguments/callee/caller,变量赋值前必须声明。

局部this必须被赋值,赋值什么就是什么,拒绝重复属性和参数,不能使用eval()。

1
2
3
4
5
6
7
8
9
10

var obj = {};
var name = 'window';
function foo() {
var name = 'scope';
with(obj) { // with改变作用域链
console.log(name);
}
}
foo();

es5.0与es3.0规则

  • 浏览器基于es3.0方法 + es5.0新增方法。
  • es3.0与es5.0发生冲突部分,使用es5.0模式,反之使用es3.0模式。
———— / END / ————
0%