js基础

面向对象编程 和 函数 - 复习

2018-07-01  本文已影响34人  woow_wu7

2018-6-28复习
每一次回头看基础,都有新的理解

Function.prototype.call

函数实例的call方法,可以指定函数内部this的指向(即:函数执行时所在的作用域,--- 函数运行时所在的对象 ),然后在所指定的作用域中,调用该函数。
注意: call方法是函数的方法
应用:

  1. 将 ( 类似数组的对象 ) 转换成 ( 数组 )

(1)
[].slice.call(arguments)
等于 Array.prototype.slice.call(arguments)
等于 Array.from(arguments)
--- [].slice.call(arguments, x,y)参数x,y是slice()方法执行时传入的参数



(2)
slice()方法 ----- 截取数组的一部分,返回截取部分的新数组,不改变原数组
slice(start, end)方法,截取数组的一部分,start是启始位置,end是终止位置(不包含)
数组的slice()方法不加参数时,等于数组的拷贝



实例:

Array.prototype.slice.call({ 0: 'a', 1: 'b', length: 2 }) // ['a', 'b']
--- 将slice()方法中的this绑定在参数对象上,并在参数对象的作用域内执行slice()方法

函数实例的call方法,可以指定函数内部this的指向(即:函数执行时所在的作用域,--- 函数运行时所在的对象 ),然后在所指定的作用域中,调用该函数。

var obj = {};

var f = function () {
  return this;
};

f() === window // true
f.call(obj) === obj // true    


--- 把f函数的this绑定在obj对象上,并在obj的作用域中,执行f函数
var n = 123;
var obj = { n: 456 };

function a() {
  console.log(this.n);
}

a.call() // 123
a.call(null) // 123
a.call(undefined) // 123
a.call(window) // 123
a.call(obj) // 456


--- 参数是null,空,undefined时,默认传入的是全局对象 window

Function.prototype.apply()

apply方法的作用与call方法类似,也是改变this指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数

func.apply(ojb, [arg1, arg2, ...])


apply()方法:
第一个参数,是this所要绑定的对象,this所要指向的对象
第二个参数是一个数组,会将成员依次作为函数调用时的参数
function f(x, y){
  console.log(x + y);
}

f.call(null, 1, 1) // 2
f.apply(null, [1, 1]) // 2

解析:
f()函数,本来是接受两个参数,但在使用apply方法后,就可以变成,接受一个数组为参数

应用:

  1. 找出数组中最大的数---(或者最小的数)
Math.max(1,2,5,3) // 5
Math.min(1,2,3) // 1



const arr = [1,6,2,3,4,5];
Math.max.apply(null, arr) // 6   将max方法中的this指向顶层对象,参数是arr数组
等于:Math.max.apply(undefined, arr)
等于:Math.max([...arr])
  1. 将数组的 ( 空元素 ) 变成 ( undefined )
Array.apply(null, ['a', ,'b'])
等于: [...['a', , 'b']]


// [ 'a', undefined, 'b' ]
  1. 转换类似数组的对象

Function.prototype.bind()

bind方法用于将函数体内的this绑定到某个对象,然后返回一个新函数。

var counter = {
  count: 0,
  inc: function () {
    this.count++;
  }
};
var func = counter.inc.bind(counter);
func();
counter.count // 1


解析:
1. 
将counter.inc方法中的this绑定在counter对象上
如果不绑定,var func = counter.inc将该方法赋值给变量后,this指向了顶层对象window
var add = function (x, y) {
  return x * this.m + y * this.n;
}

var obj = {
  m: 2,
  n: 2
};

var newAdd = add.bind(obj, 5);   // add函数的第一个参数
newAdd(5) // 20                  // add函数的第二个参数

bind函数注意点

回调函数是 JavaScript 最常用的模式之一,但是一个常见的错误是,将包含this的方法直接当作回调函数。解决方法就是使用bind方法,将counter.inc绑定counter。

var counter = {
  count: 0,
  inc: function () {
    'use strict';
    this.count++;
  }
};

function callIt(callback) {
  callback();
}

callIt(counter.inc.bind(counter));
counter.count // 1

构造函数的缺点

同一个构造函数的多个实例之间,无法共享属性,从而造成对系统资源的浪费。

function Cat(name, color) {
  this.name = name;
  this.color = color;
  this.meow = function () {
    console.log('喵喵');
  };
}
var cat1 = new Cat('大毛', '白色');
var cat2 = new Cat('二毛', '黑色');
cat1.meow === cat2.meow  --- 不相等,函数是引用类型的数据,说明无法共享属性
// false



obj.hasOwnProperty(prop) --- 表示:prop是否是obj的自身属性,不含继承属性
cat1.hasOwnProperty('name'); // true



总结:
1. 构造函数内部定义的属性,在生成实例对象的时候,会成为实例对象自身的属性。
2. 构造函数各个实例之间无法共享属性,造成系统资源浪费
3. 解决办法是 原型对象

prototype原型对象

原型对象的所有属性和方法,都能被实例对象所共享。

总结:
原型对象的作用就是定义: 所有实例对象所共享的属性和方法

function Animal(name) {
  this.name = name;
}
Animal.prototype.color = 'white';  
// 构造函数的prototype属性在生成实例对象时,会成为实例的原型对象
// prototype对象上的color属性,会被实例共享
var cat1 = new Animal('大毛');
var cat2 = new Animal('二毛');

cat1.color // 'white'
cat2.color // 'white'

原型链

所有对象都有自己原型对象

Object.getPrototypeOf(Object.prototype)

Object.getPrototypeOf() --- 返回参数对象的原型对象

// null
// 表示Object.prototype对象的原型对象是null
var MyArray = function () {};

MyArray.prototype = new Array();
MyArray.prototype.constructor = MyArray;

var mine = new MyArray();    
mine.push(1, 2, 3);
mine.length // 3
mine instanceof Array // true



解析:
1. mine 是构造函数MyArray 生成实例对象
2. 构造函数的 MyArray.prototype对象成为实例对象的原型对象
3. MyArray.prototype指向了数组实例,那么MyArray的实例就具有 数组的属性和方法
4. 所有mine具有数组的属性和方法
5. instanceof运算符    左边是实例   右边是构造函数    返回布尔值

constructor属性

prototype对象有一个constructor属性,默认指向 prototype对象所在的构造函数。---------------------------------------------------- ( 重要 )

( 重要 )


function P() {}
P.prototype.constructor === P // true


解析:
1. P构造函数(首字母大写)的prototype属性是一个对象
2. prototype对象的constructor属性指向prototype对象所在的构造函数
3. P.prototype.constructor === P 布尔值就是true
( 重要 )


function P() {}
var p = new P();
p.constructor === P // true
p.constructor === P.prototype.constructor // true
p.hasOwnProperty('constructor') // false  是继承的,不是自身的属性


解析:
1. prototype对象的constructor属性,指向prototype对象所在构造函数
2. constructor是实例对象的原型对象的属性,会被实例所继承
   所以 
   实例p继承了constructor属性,p.constructor = P = P.prototype.constructor

利用constructor属性,可以从一个实例生成另一个实例
因为: constructor属性指向的就是构造函数




function Constr() {}
var x = new Constr();
var y = new x.constructor();   //注意x.constructor === Constr,这里要执行
// x.constructor间接调用构造函数
y instanceof Constr // true
( 重要 )


function Person(name) {
  this.name = name;
}
Person.prototype.constructor === Person // true
Person.prototype = {
  method: function () {}
};
Person.prototype.constructor === Person // false
Person.prototype.constructor === Object // true


解析:
1. Person.prototype 修改成了对象
2. 构造函数Person的原型对象改了,但没改constructor属性,导致这个属性不再指向Person
3. 对象的构造函数是Object构造函数
4. 所以修改原型对象时,一般要同时修改constructor属性的指向

instanceof运算符

instanceof运算符返回一个布尔值,表示对象是否为某个构造函数的实例。

( 重要 )


v instanceof Vehicle       --------------  v是否是Vehicle的实例
// 等同于
Vehicle.prototype.isPrototypeOf(v)  -----  Vehicle.prototype是否是v的原型


解析:
1. isPrototypeOf() 方法允许你检查一个对象是否存在于另一个对象的原型链上
2. isPrototypeOf()返回的是布尔值
( 重要 )


var obj = Object.create(null);
typeof obj // "object"
Object.create(null) instanceof Object // false


解析:
1. Object.create()
   该方法接受一个对象作为参数,然后以它为原型,返回一个实例对象。
   该实例完全继承原型对象的属性。
   即:object.create()方法  以参数对象为原型,生成实例对象
2. instanceof的原理是检查右边构造函数的prototype对象是否在左边对象的原型链上
3. Object.create(null) instanceof Object
    因为右边 Object.prototype === null,不在左边对象的原型链上
    左边对象的原型链在Object.prototype对象上终止

instanceof 用来判断值得类型

但是注意:instanceof 运算符只能用于对象,不适用原始类型的值。

此外,对于 undefined 和 null ,instanceOf 运算符总是返回 false。



var x = [1, 2, 3];
var y = {};
x instanceof Array // true
y instanceof Object // true
var F = function () {};
var f = new F();
Object.getPrototypeOf(f) === F.prototype // true
undefined instanceof Object // false
null instanceof Object // false

Object.getPrototypeOf()

Object.getPrototypeOf() 返回参数对象的原型,是获取原型对象的标准方法

( 重要 )


var F = function () {};
var f = new F();
Object.getPrototypeOf(f) === F.prototype // true


解析:
1. Object.getPrototypeOf() 返回参数对象的原型
2. f 的原型是 构造函数F的 prototype属性

几种特殊对象的原型


1. 空对象的原型是 Object.prototype

2. Object.prototype 的原型是 null

3. 函数的原型是 Function.prototype

Object.setPrototypeOf() --- 注意 返回的是参数对象

Object.setPrototypeOf方法为参数对象设置原型,返回该参数对象。它接受两个参数,第一个是现有对象,第二个是原型对象。

( 重要 )


var a = {};
var b = {x: 1};
Object.setPrototypeOf(a, b);    // {}
Object.getPrototypeOf(a) === b // true
a.x // 1


解析:
1. Object.setPrototypeOf(参数对象,原型对象)  ---  返回的是参数对象
2. Object.setPrototypeOf()方法的作用是: 把第二个参数设置成第一个参数的原型
( 重要 )  --- new命令的原理



var F = function () {
  this.foo = 'bar';
};
var f = new F();
// 等同于
var f = Object.setPrototypeOf({}, F.prototype);  --- 将空对象的原型设置成 F的prototype属性
F.call(f);   --------------------------------------- 将F中的this指向 空对象,并执行构造函数

Object.create() --- 返回参数对象的实例对象

Object.create()接受一个对象作为参数,然后以它为原型,返回一个实例对象。该实例完全继承原型对象的属性。

// 原型对象
var A = {
  print: function () {
    console.log('hello');
  }
};

// 实例对象
var B = Object.create(A);

Object.getPrototypeOf(B) === A // true
B.print() // hello
B.print === A.print // true
var obj1 = Object.create({});
var obj2 = Object.create(Object.prototype);
var obj3 = new Object();
var obj = Object.create(null);

obj.valueOf()
// TypeError: Object [object Object] has no method 'valueOf
Object.create()
// TypeError: Object prototype may only be an Object or null
Object.create(123)
// TypeError: Object prototype may only be an Object or null
var obj1 = { p: 1 };
var obj2 = Object.create(obj1);

obj1.p = 2;
obj2.p // 2
( 重要 )


function A() {}
var a = new A();
var b = Object.create(a);
b.constructor === A // true
b instanceof A // true


解析:
1. Object.create()方法生成的对象,继承了它的原型对象的构造函数
2. var b = Object.create(a); ---- b的原型是a,  b继承了a的构造函数
   所以 b.constructor === A // true
   所以 b instanceof A // true

Object.prototype.isPrototypeOf() - 返回布尔值

isPrototypeOf方法被实例对象所继承

实例对象的 isPrototypeOf() 方法,用来判断该对象是否是参数对象的原型

Object.prototype.__proto__

componentDidMount() {
   const obj1 = {};
   const obj2 = {'name': 'wang'};
   obj1.__proto__ = obj2;
   console.log(obj2.isPrototypeOf(obj1), '实例对象的isPrototypeOf()放回一个布尔值,表示实例对象是否是参数对象的原型');
   console.log(Object.getPrototypeOf(obj1), 'getPrototypeOf()方法,返回参数对象的原型对象');
}

获取原型对象的方法比较

获取实例对象的原型有三种方法: Object.getPrototypeOf()胜出

  1. obj.__proto__实例对象继承的 __proto__属性
  2. obj.constructor.prototype
    ----- 实例的constructor是继承自实例构造函数的 prototype上的constructor
  3. Object.getPrototypeOf()
(重要)  -- 比较三种获取原型的方法



1. __proto__ 只在浏览器上有     ---- 不靠谱
2. obj.constructor.prototype在手动修改原型时,还要修改构造函数,否则可能会失效



结论:使用Object.getPrototypeOf() 最好

Object.prototype.hasOwnProperty

判断某个属性,是在对象自身属性还是继承属性

in运算符

in运算符返回一个布尔值,表示一个对象是否具有某个属性

for ...in 循环

自身属性和继承属性都会被for...in循环遍历

 const obj1 = {'name': 'wang'};
 const obj2 = {'age': 20};
 Object.setPrototypeOf(obj1, obj2);

 for(let x in obj1) {
     console.log(x, 'x') 
  }
 // name x
 // age x
 
 for(let y in obj1) {
    if( obj1.hasOwnProperty(y) ) {   ---- 参数属性是否是实例对象的自身属性
       console.log(y, 'y')
     }
 }
 // name y

对象的拷贝

如何要拷贝对象,需要有两点相同

  1. 拷贝后的对象,要与原对象具有相同的 原型对象
  2. 考背后的对象,要与元对象具有相同的 实例属性



















2018-7-1

语句

js程序的执行单位是 (行),一般情况下,每一行就是一个语句。

var a = 1 + 3 ; var b = 'abc';


解析:
1. 语句:语句以分号结尾;多个语句可以写在一行内。
2. 表达式:为了得到返回值的计算式
3. 凡是预期为值的地方,都可以使用表达式
4. 赋值语句,等号右边预期为值,所以可以使用各种表达式

表达式

表达式: 是一个为了得到返回值的计算式

语句和表达式的区别

变量

变量是对值的具名引用

函数

js把函数看成一种值,凡是能使用值的地方,就能使用函数

函数名的提升

js将函数名,视同变量名。

( 重要 )



总结: 
用变量赋值的方法声明函数,和function声明都存在变量提升
但是:变量赋值先调用会报错,而function声明不会报错
( 先调用函数,function声明不报错,变量赋值声明报错 )




1. function命令声明时的 ( 函数名提升 )
f();
function f() {}
不会报错




2. 变量赋值声明函数
f();
var f = function (){};
报错TypeError: undefined is not a function,----------------  实际上相当于下面的代码:
var f;
f();
f = function () {};

函数的name属性

返回函数的名字

var myFunc = function () {};
function test(f) {
  console.log(f.name);
}
test(myFunc)    ----------------------- 返回 myFunc


上面代码中,函数test内部通过name属性,就可以知道传入的参数是什么函数。

length属性

函数的length返回函数定义时的参数个数

const a = function(a,b,c) {
  return `${a},${b},${c}`
};
console.log( a.length ); -------------------------  length返回函数定义时的参数个数: 3
console.log( a(1,2) );    // 1,2,undefined

函数的toString方法

const a = function(a,b,c) {
    return `${a},${b},${c}`
};
console.log( a.toString(), 'toString方法,返回函数的源码字符串' )
console.log( a.length, 'length属性,返回函数定义时的参数个数' );
console.log( a.name, 'name属性返回函数的名字,function命令声明,时后面的函数名,变量赋值,匿名时变量名,具名时function后的函数名')
console.log( a(1,2) );




结果:
function a(_a, b, c) {
                return _a + ',' + b + ',' + c;
            } toString方法,返回函数的源码字符串
3 "length属性,返回函数定义时的参数个数"
a name属性返回函数的名字,function命令声明,时后面的函数名,变量赋值,匿名时变量名,具名时function后的函数名
1,2,undefined

函数的作用域

( 作用域 ) scope指的是 ( 变量存在的范围 )
es5有两种作用域:( 全局作用域 ) 和 ( 函数作用域 )
es6新增一个作用域:( 块级作用域 )


var a = 1;
var c = 3;

function x() {
    console.log(a, '函数内部,可以读取函数外部的全局变量');
    var b = 2;
    var c = 4;
    console.log(c, '函数内部定义的变量,会在该作用域内覆盖同名全局变量')
}
x();

if(true) {
    var y = 100;
}
console.log(y, '对于var命令来说,局部变量只能在函数中声明,在其他区块中声明的变量都是全局变量')
console.log(b, '函数外部无法读取函数内部的变量')

函数内部的变量提升

function foo(x) {
  if (x > 100) {
    var tmp = x - 100;
  }
}
 -- 等同于
function foo(x) {
  var tmp;
  if (x > 100) {
    tmp = x - 100;
  };
}

函数本身的作用域

函数本身也是一个值,也有自己的作用域

var a = 1;
function x() {
    console.log(a, '函数的作用域,是函数声明时的作用域,与函数调用时的作用域无关');
    console.log('容易犯错的点: 如果a函数调用b函数,却没有考虑到b函数不会引用a函数的变量');
}
function y() {
    var a = 100;
    x();
}
y();  ---------------- 结果是1

总结:

  1. 函数外部声明的函数,作用域绑定在函数外部
  2. 函数内部声明的函数,作用域绑定在函数内部
( 重要 )



总结:
1. 函数外部声明的函数,作用域绑定在函数外部
2. 函数内部声明的函数,作用域绑定在函数内部
       第一个和第二点都满足第三点
3. 函数的作用域,是函数声明时的作用域,与函数调用时的作用域无关 !!!!!!!!


(1)
var a = 1;
var x = function () {
  console.log(a);
};
function y(f) {
  var a = 2;
  f();
}
y(x)   -------- 结果是1   ( 函数的作用域,是函数声明时的作用域,与函数调用时的作用域无关 )



(2)
function foo() {
  var x = 1;
  function bar() {
    console.log(x);
  }
  return bar;
}
var x = 2;
var f = foo();
f()   -------- 结果是1   ( 函数的作用域,是函数声明时的作用域,与函数调用时的作用域无关 )

参数

function f(a, b) {
  return a;
}

f( , 1) // SyntaxError: Unexpected token ,(…)   省略前面报错
f(undefined, 1) // undefined   一定要省略前面,则只要传入undefined

参数的传递方式

let str = 'abcdef';
let num = 12345;
let boo = true;  ------------------------------------ 原始类型的值

let arr = [1,2,3,4,5];
let obj = {'name': 'wang'}; ------------------------- 复合类型的值

let replaceArr = [1,2];
let replaceObj = {'name': 'zhang'} ------------------ 复合类型的值


function pass(num,str,boo, arr,obj,fun, replaceArr,replaceObj) {
    num = 100;
    str = 'oneHundred';
    boo =  false;     ---------- 参数是原始类型的值,传值传递,修改参数,不影响原始值

    arr[0] = 100;
    obj.name = 'li';   --------- 参数是复合类型的值,传址传递,修改参数,影响原始值

    replaceArr = [1,1,1];
    replaceObj = {'age': 30} --- 替换掉整个复合类型的参数,不会影响到原始值
}

pass(str, num, boo, arr, obj, replaceArr, replaceObj);
console.log(str, num, boo);
console.log(arr, obj);
console.log(replaceArr, replaceObj);

 结果:
 abcdef 12345 true
 [100, 2, 3, 4, 5] {name: "li"}
 [1, 2] {name: "zhang"}

同名参数

如果有同名参数,则取最后出现的那个值

function f(a, a) {
  console.log(a);
}
f(1, 2) // 2

-------------------------------------

function f(a, a) {
  console.log(a);
}
f(1) // undefined

arguments对象

arguments对象包含了函数 ( 运行时的所有参数 )

function a(b,c,d,e) {
    console.log(arguments.length, 'arguments.length返回调用时传入的参数个数,是调用时的参数个数');
    arguments[0] = 100;
    console.log(arguments[0], 'arguments对象,可以在运行时修改');
    console.log('在严格模式下,arguments对象是只读,修改无效,当不会报错');
}
a(1,2,3);
console.log(a.length, '函数的length属性,返回函数定义时的参数个数');
console.log( arguments[1] ,'arguments对象只能在函数内部使用')
console.log(arguments.callee, 'arguments对象的callee属性,返回它对应的原函数');



结果:
3 "arguments.length返回调用时传入的参数个数,是调用时的参数个数"
100 "arguments对象,可以在运行时修改"
在严格模式下,arguments对象是只读,修改无效,当不会报错
4 "函数的length属性,返回函数定义时的参数个数"
undefined "arguments对象只能在函数内部使用"
报错: callee不能用于严格模式,react的类默认就是严格模式



注意:
1. 函数的length属性,返回函数定义时的参数个数
2. arguments.length属性,返回函数调用时传入的参数个数
3. arguments对象只能在函数内部使用

闭包

闭包是定义在函数内部的函数

( 重要 )


function f1() {
  var n = 999;
  function f2() {
    console.log(n);
  }
  return f2;
}
var n = 111;
var result = f1();
result(); // 999



解析:
1. 函数的作用域,是函数定义时的作用域,与函数调用时的作用域无关
2. f2声明在f1的内部,f2调用时,f2的作用域是定义时候的作用域,即在f1的内部,所以是999,不是111
3. 闭包就是定义在一个函数内部的函数,注意: 一定是要在函数内部定义 !!!
( 闭包 )



function createIncrementor(start) {
  return function () {
    return start++;
  };
}
var inc = createIncrementor(5);
inc() // 5
inc() // 6
inc() // 7



解析:
1. 闭包inc使得函数createIncrementor的内部环境,一直存在。
2. inc始终在内存中,而inc的存在依赖于createIncrementor,因此也始终在内存中
   不会在调用结束后,被垃圾回收机制回收。
闭包:用与封装对象的私有属性和私有方法


function Person(name) {
  var _age;
  function setAge(n) {
    _age = n;
  }
  function getAge() {
    return _age;
  }

  return {
    name: name,
    getAge: getAge,
    setAge: setAge
  };
}

var p1 = Person('张三');外层函数每次运行,都会生成一个新的闭包,闭包保存外层函数的内部变量,内存消耗大
p1.setAge(25);
p1.getAge() // 25

注意:
1. 外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。
2. 因此不能滥用闭包,否则会造成网页的性能问题。

立即调用函数表达式

有时候,我们需要在定义函数之后,立即调用该函数。这时,你不能在函数的定义之后加上圆括号,这会产生语法错误。

( 重要 )


语句
function f() {}            -------   函数的声明语句


表达式
var f = function f() {}    --------  等号右边预期是值,function是表达式,函数就是一个可执行的值
(function(){ ... }());         ------------- 语句不能以括号结尾,而是以分号结尾
// 或者
(function(){ ... })();

注意,上面两种写法最后的分号都是必须的。
如果省略分号,遇到连着两个 IIFE,可能就会报错。




--------------------------
注意: 如果是采用变量赋值的方式声明函数,那么function本来就是表达式,所以可以直接加括号
const a = function() {
   console.log('11111111')
}();

eval命令

eval命令的作用是,将字符串当作语句执行

eval('var a = 1;');
a // 1
上一篇下一篇

猜你喜欢

热点阅读