Skip to content

前言

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

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

var

  • 即任何变量,如果未经声明就使用,此变量就为全局变量所拥有。
// 没有使用var,在全局声明变量num,变量num属于window
num = 10;
console.log(window.num) // 10
// 没有使用var,在全局声明变量num,变量num属于window
num = 10;
console.log(window.num) // 10
  • 一切声明的全局变量,全是window的属性。
// 使用var在全局声明变量num,变量num就属于window。
var num = 10; 
console.log(window.num) // 10
// 使用var在全局声明变量num,变量num就属于window。
var num = 10; 
console.log(window.num) // 10

注意

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

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

typeof

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

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

var x = 'app';
typeof x // 'string' 
...
var num = 1;
typeof num // 'number' 

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

typeof 存在比较特殊地方。

typeof null // 'object' 
typeof Array // 'object'
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
  • 将实参值和形参统一
  • 在函数体里面找函数声明,值赋予函数体

代码片段:

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

Global Object(GO)

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

代码片段

function a() {
  function b() {
    function c() {}
    c();
  }
  b();
}
a();
function a() {
  function b() {
    function c() {}
    c();
  }
  b();
}
a();

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

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

a   doing   a.[[scope]] --> 0: a AO
                        --> 1: GO
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结果。

// ...省略函数a 
b   defined b.[[scope]] --> 0: aAO
                        --> 1: GO

b   doing   b.[[scope]] --> 0: bAO
                        --> 1: aAO
                        --> 2: GO
// ...省略函数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结果。

// ...省略函数a 
// ...省略函数b 
c   defined c.[[scope]] --> 0: bAO
                        --> 1: aAO
                        --> 2: GO

c   doing   c.[[scope]] --> 0: cAO
                        --> 1: bAO
                        --> 2: aAO    
                        --> 3: GO
// ...省略函数a 
// ...省略函数b 
c   defined c.[[scope]] --> 0: bAO
                        --> 1: aAO
                        --> 2: GO

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

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

闭包

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

规则:

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

代码片段:

function fn() {
  function bar() {
    var y = 2;
    console.log(x);
  }
  var x = 1;
  return bar;
}
var f = fn();
f();
function fn() {
  function bar() {
    var y = 2;
    console.log(x);
  }
  var x = 1;
  return bar;
}
var f = fn();
f();

闭包运用

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]();
}
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语句可以这样

for(var i=0; i<10; i++) { 
  // ...  
}
// 修改下面是等效 
var i = 0;
for(; i<10; i++) {
  // ... 
}
for(var i=0; i<10; i++) { 
  // ...  
}
// 修改下面是等效 
var i = 0;
for(; i<10; i++) {
  // ... 
}

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

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]();
}
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中提出全局作用域和函数作用域。

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

代码片段:

function fn() {
  var num = 10;
  console.log(num); // 10 
  function bar() {
    console.log(num) // 10 
  }
  bar();
}
console.log(num) // 抛出 error 
fn();
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语句后面的“{}”,不会创建自己的作用域,定义的变量保存在已经定义的作用域中。

if(true) {
  var num = 10;
}
console.log(num) // 10
if(true) {
  var num = 10;
}
console.log(num) // 10

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

作用域链

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

var num = 10;
function  fn() {
  console.log(num);
}
fn(); // 输出结果 10
var num = 10;
function  fn() {
  console.log(num);
}
fn(); // 输出结果 10

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

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

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

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

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

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

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

立即执行函数

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

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

可以定义一个变量接收。

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

console.log(num)  // 3
var num = (function(x, y) {
  var z = x + y;
  return z;
})(1, 2)

console.log(num)  // 3

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

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

函数调用。

function fn() {
  console.log(this);
}
fn();
function fn() {
  console.log(this);
}
fn();

函数使用call方法调用。

function foo() {
  console.log(this);
}
foo.call();
function foo() {
  console.log(this);
}
foo.call();

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

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

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

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

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

“( )”运算符。

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

if语句有函数。

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

对象模型

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

var obj = {
 // 属性:值  
  name: 'sunny',
  age: 18,
  say: function() {
    console.log('hello')
  }
}
var obj = {
 // 属性:值  
  name: 'sunny',
  age: 18,
  say: function() {
    console.log('hello')
  }
}

可以操作对象:

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

创建对象

  • 使用字面量创建对象
var  obj = {}
var  obj = {}
  • 使用系统自带的构造函数
var obj = new Object()
var obj = new Object()
  • 自定义
function Person(){}
var person = new Person();
function Person(){}
var person = new Person();

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

new关键字

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

  • 在函数体最前面隐式的加上this =
  • 执行 this.xxx = xxx
  • 隐式的返回 this 相关代码:
function Student(name, age) {
  // 隐式添加 this = { 
  // name: name, 
  // ... 
  // } 
  this.name = name;
  this.age = age;
  this.sex = 'male';
  // 隐式添加 return this 
}
var student = new Student('sunny', 18);
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对象

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

return原始值

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"}
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"}

自定义对象好处

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: ƒ}
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属性相互各自独立互不影响。

包装类

原始值没有属性和方法。

// eg 
var num = 1;
var num = new Number(num);
num.a = 'a';
console.log(num) // Number {1, a: "a"} 
console.log(num * 2) // 2 能计算,又返回原类型
// 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添加属性:

var str = 'a';
var str = new String(str);
str.a = 'a';
console.log(str.a) // a
var str = 'a';
var str = new String(str);
str.a = 'a';
console.log(str.a) // a

Number添加属性:

// eg: 
var num = 4;
num.len = 3;
console.log(num.len) // undefined
// 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。

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);
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。

解析片段:

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);
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对象的一个属性,它定义了构造函数制造出的对象的公共祖先。通过该构造函数产生的对象,可以继承该原型的属性和方法,原型也是对象。

相关代码:

// 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
// 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更改指向。

function Person() {}
function Car() {}
Car.prototype = {
   // constructor更改了指向 
  constructor: Person;
}
var car = new Car();
function Person() {}
function Car() {}
Car.prototype = {
   // constructor更改了指向 
  constructor: Person;
}
var car = new Car();

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

  • 内部隐式添加this对象;
  • 改变this执向(指向原构造函数原型);
  • 返回一个新对象。
// 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
// 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前

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

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

prototype在new后

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

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

prototype对象形式 写法 在new前

Person.prototype.name = 'sunny';
function Person() {}
Person.prototype = { 
  name: 'cherry'
}
var p = new Person();
console.log(p.name) // cherry
Person.prototype.name = 'sunny';
function Person() {}
Person.prototype = { 
  name: 'cherry'
}
var p = new Person();
console.log(p.name) // cherry

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

在new之后

Person.prototype.name = 'sunny';
function Person() {}
var p = new Person();
person.prototype = { 
  name: 'cherry'
}
console.log(p.name) // sunny
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是所有对象的最终原型。

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

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

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
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

揭秘原型与原型链取值

相关代码:

Person.prototype = { 
  name: 'sunny', 
  say: function() { 
    console.log(this.name) 
  } 
}
function Person() {} 
var p = new Person(); 
console.log(p.say) // sunny
Person.prototype = { 
  name: 'sunny', 
  say: function() { 
    console.log(this.name) 
  } 
}
function Person() {} 
var p = new Person(); 
console.log(p.say) // sunny

修改上面代码:

Person.prototype = { 
  name: 'sunny', 
  say: function() { 
    console.log(this.name) 
  }
}
function Person() { 
  this.name = 'cherry'; 
}
var p = new Person(); 
console.log(p.name) // cherry
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(原型)。

var obj = { 
  name: 'sunny' 
}
var obj1 = Object.create(obj);
var obj = { 
  name: 'sunny' 
}
var obj1 = Object.create(obj);
Person.prototype.name = 'sunny'; 
function Person() {} 
var p = Object.create(Person.prototype);
Person.prototype.name = 'sunny'; 
function Person() {} 
var p = Object.create(Person.prototype);

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

call (继承)

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"}
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 (继承)

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"}
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。

function fn() { 
  foo.apply(null, arguments); 
}

function foo() { 
  console.log(arguments) 
}
foo(1, 2, 3) // 1, 2, 3
function fn() { 
  foo.apply(null, arguments); 
}

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

继承

  • 原型链继承,结合子类原型与父类实例
// 代码片段 
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)
// 代码片段 
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)

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

在new Person之前:

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)
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()之后:

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)
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继承。 相关代码:

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);
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方法不能继承父类原型上的方法和属性;父类构造函数调用两次:一次在子类构造函数内调用,一次在创建子类原型调用。

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

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)
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输出说明,子类可以继承父类属性以及父类原型属性和方法;缺点父类的构造函数生成两份实例。

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

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)
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输出说明,子类可以继承父类原型上方法和属性。

组合继承优化

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);
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继承借助创建对象,子类继承父类所有属性和方法。

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

Person.prototype.name = 'sunny'; 
function Person() {} 
function Son() {}  
Son.prototype = Person.prototype; 
var son = new Son(); 
console.log(son.name); // sunny
Person.prototype.name = 'sunny'; 
function Person() {} 
function Son() {}  
Son.prototype = Person.prototype; 
var son = new Son(); 
console.log(son.name); // sunny

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

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
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指向。

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
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,此方法称为圣杯模式继承

抽离核心代码:

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; 
  }
})()
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; 
  }
})()

命名空间

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

var obj = { 
  name: 'sunny', 
  sayName: function() { 
    console.log(this.name) 
  }
}
obj.sayName(); // 打印结果: sunny
var obj = { 
  name: 'sunny', 
  sayName: function() { 
    console.log(this.name) 
  }
}
obj.sayName(); // 打印结果: sunny

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

var name = 'sunny';
var init = (function(){
  var name = 'cherry';
  function sayName() {
    console.log(name); // cherry
  }

  return function() {
    sayName();
  }
})()
init();
var name = 'sunny';
var init = (function(){
  var name = 'cherry';
  function sayName() {
    console.log(name); // cherry
  }

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

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

链式调用

模仿jQuery链式调用。

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

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

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
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 。

var obj = {
  name: 'sunny', 
  age: 18 
}
for(var prop in obj) { 
  console.log(obj.prop); // undefined 
}
var obj = {
  name: 'sunny', 
  age: 18 
}
for(var prop in obj) { 
  console.log(obj.prop); // undefined 
}

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

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

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 ... 
  }
}
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:能不能访问对象属性,包括原型。

var obj = { 
  name: 'sunny', 
  age: 18, 
  __proto__:{ 
    lastname: 'cherry', 
    __proto__: Object.prototype 
  }
}
console.log('name' in obj) // true
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的原型。 相关代码:

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
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()。

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]
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()可以实现深度克隆。

var obj = { 
  name: 'sunny', 
  age: 18, 
  car: ['visa', 'BMW'], 
  wife: { 
    name: 'cherry', 
    son: { 
      name: 's' 
    }
  }
}
var targetObj = {};
var obj = { 
  name: 'sunny', 
  age: 18, 
  car: ['visa', 'BMW'], 
  wife: { 
    name: 'cherry', 
    son: { 
      name: 's' 
    }
  }
}
var targetObj = {};

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

  • 首先使用for...in遍历对象。
  • 判断是否属于原始值,是数组还是对象。
  • 建立相应数组与对象。
// ...
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);
// ...
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);

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

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);
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案例

var name = 'sunny'; 
var obj = { 
  name: 'cherry', 
  say: function() { 
    console.log(this.name) 
  }
}
var fn = obj.say; 
fn(); // sunny 
obj.say(); // cherry
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。

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

案例2

var foo = 123; 
function print() { 
  var foo = 456; 
  this.foo = 789; 
  console.log(foo); 
}
print();
var foo = 123; 
function print() { 
  var foo = 456; 
  this.foo = 789; 
  console.log(foo); 
}
print();

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

案例3

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

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

arguments.callee

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);
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

function foo() {
  console.log(foo.caller) // foo函数
}
foo();
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

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

向类数组push

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: ƒ}
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里面代码。
try{ 
  console.log('ok') 
}catch(e) { 
  console.log('error'); 
}
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()。

var obj = {}; 
var name = 'window';
function foo() {
  var name = 'scope';
  with(obj) { // with改变作用域链
    console.log(name); 
  }
}
foo();
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模式。