慕课网学习笔记-前端JavaScript面试技巧(双越)-第3章 JS基础知识(上)- 原型和原型链

question

题目:

1、如何准确判断一个变量是数组类型

2、写一个原型链继承的例子

3、写一个封装DOM查询的例子

4、描述 new 一个对象的过程

5、zepto(或其他框架) 源码如何使用原型链

6、手写一个简易的jQuery,考虑插件和扩展性

Knowledge-point

知识点:

  • 构造函数

  • 构造函数 - 扩展

  • 原型规则和示例

  • 原型链

  • instanceof

构造函数-Constructor

function Foo(name, age) {
  this.name = name;
  this.age = age;
  this.class = 'class-1';
  // return this;  // 默认有这一行
}
var foo = new Foo('zhangsan', 20);
var foo1 = new Foo('lisi', 22);  //创造多个对象

构造函数extend

var a = {} 其实是 var a = new Object() 的语法糖

var a = [] 其实是 var a = new Array() 的语法糖

function Foo() {…} 其实是 var Foo = new Function(…)

使用 instanceof 判断一个函数是否是一个变量的构造函数

5个原型rules

原型和原型链-5个原型规则:

1、所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性(除了 ‘null’ 意外)。

2、所有的引用类型(数组、对象、函数),都有一个 proto 属性【隐式原型】,属性值是一个普通的对象。

3、所有函数,都有一个prototype 属性【显式原型】,属性值也是一个普通的对象。

4、所有的引用类型(数组、对象、函数), proto 属性值指向它的构造函数的 “prototype” 属性值

5、当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的 proto (即它的构造函数的prototype)中寻找。

1、所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性(除了 ‘null’ 意外)。

// 原型和原型链
var obj = {};
obj.a = 100;

var arr = [];
arr.a = 100;

function fn() { }
fn.a = 100;

2、所有的引用类型(数组、对象、函数),都有一个 __proto__ 属性【隐式原型】,属性值是一个普通的对象。

console.log(obj.__proto__);  
// Object
console.log(arr.__proto__);
// [constructor: ƒ, toString: ƒ, toLocaleString: ƒ, join: ƒ, pop: ƒ, …]
console.log(fn.__proto__);
// ƒ () { [native code] }

3、所有函数,都有一个prototype 属性【显式原型】,属性值也是一个普通的对象。

console.log(fn.prototype);
// {constructor: f fn(); __proto__: Object}

4、所有的引用类型(数组、对象、函数), __proto__ 属性值指向它的构造函数的 “prototype” 属性值

console.log(obj.__proto__ === Object.prototype);
// true

5、当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的 proto (即它的构造函数的prototype)中寻找。

// 构造函数
function Foo(name, age) {
  this.name = name
}
Foo.prototype.alertName = function() {
	alert(this.name)
}
// 创建示例
var f = new Foo('zhangsan');
f.printName = function() {
	console.log(this.name);
}
// 测试
f.printName()
f.alertName()

原型规则-Add-2-points

原型和原型链-5个原型规则-补充2点

this、循环对象自身的属性

var item
for (item in f) {
	// 高级浏览器已经在 for in 中屏蔽了来自原型的属性
	// 但是这里建议大家还是加上这个判断,保证程序的健壮性
	if (f.hasOwnProperty(item)) {
		console.log(item)
	}
}

// 结果只有2个属性:name 、printName

原型链-Prototype-Chain

  • 原型和原型链-原型链
// 原型和原型链-原型链

// 构造函数
function Foo(name, age) {
  this.name = name
}
Foo.prototype.alertName = function() {
	alert(this.name)
}
// 创建示例
var f = new Foo('zhangsan');
f.printName = function() {
	console.log(this.name);
}
// 测试
f.printName()
f.alertName()
f.toString() // 要去 f.__proto__.__proto__ 中去寻找
console.log(f.toString()) // [object Object]

http://www.imooc.com/article/3654

http://www.jb51.net/article/30750.htm

instanceof

  • 原型和原型链-原型链 - instanceof

instanceof:用于判断 引用类型 属于哪个 构造函数 的方法

f instanceof Foo 的判断逻辑是:

f 的 __proto__ 一层一层往上,能否对应到Foo.prototype

function Foo(name, age) {
  this.name = name
}

// 创建示例
var f = new Foo('zhangsan');

console.log(f instanceof Foo);  // true

再试着判断 f instanceof Object

Answer

one-如何准确判断一个变量是数组类型

var arr = [];
console.log(arr instanceof Array); // true
console.log(typeof arr); // object, typeof 是无法判断是否是数组的

function isArray(obj) {
	return Object.prototype.toString.call(obj) === '[object Array]';
}
console.log(isArray(arr)); // true

two-写一个原型链继承的例子

// 动物
function Animal() {
	this.eat = function() {
		console.log('animal eat');
	}
}

// 狗
function Dog() {
	this.bark = function() {
		console.log('dog bark');
	}
}

Dog.prototype = new Animal();

// 哈士奇
var hashiqi = new Dog();

写一个贴近实际开发原型链继承的例子

写一个封装DOM查询的例子

function Elem(id) {
	this.elem = document.getElementById(id);
}
Elem.prototype.html = function(val) {
  var elem = this.elem;
  if (val) {
  	elem.innerHTML = val;
  	console.log(this)
  	return this; // 链式操作
  } else {
  	return elem.innerHTML;
  }
}
Elem.prototype.on = function(type, fn) {
	var elem = this.elem;
	if(elem.addEventListener) {
		elem.addEventListener('type', fn, false);
	} else if(elem.attachEvent) {
		elem.attachEvent('on' + type, fn);
	} else {
		elem['on' + type] = fn;
	}
    return this;
}

var div1 = new Elem('div1');
div1.html('');
div1.on('click', alert('click')).html('youyi');

描述 new 一个对象的过程

  • 创建一个新对象

  • this 指向这个新对象

  • 执行代码,即对this赋值

  • 返回this

new操作符具体干了什么呢?

function Foo(name, age) {
  this = {};
  this.name = name;
  this.age = age;
  this.class = 'class-1';
  // return this;  // 默认有这一行
}
var foo = new Foo('zhangsan', 20);
var foo1 = new Foo('lisi', 22);  //创造多个对象

zepto(或其他框架) 源码如何使用原型链

整体结构-Overall structure

var Zepto = (function () {
  ...
})()

window.Zepto = Zepto
window.$ === undefined && (window.$ = Zepto)

zepto 的核心是一个闭包,加载完毕后立即执行。然后暴露给全局变量 zepto ,如果 $ 没有定义,也将 $ 赋值为 zepto 。

核心结构-Core structure

var zepto = {}, $

function Z(doms) {
  var len = doms.length 
  for (var i = 0; i < len; i++) {
    this[i] = doms[i]
  }
  this.length = doms.length
}

zepto.Z = function(doms) {
  return new Z(doms)
}

zepto.init = function(doms) {
  var doms = ['domObj1','domObj2','domObj3']
  return zepto.Z(doms)
}

$ = function() {
  return zepto.init()
}

$.fn = {
  constructor: zepto.Z,
  method: function() {
    return this
  }
}

zepto.Z.prototype = Z.prototype = $.fn

return $

在源码中,可以看出, $ 其实是一个函数,同时在 $ 身上又挂了很多属性和方法(这里体现在 $.fn 身上,其他的会在后续的文章中谈到)。

我们在使用 zepto 的时候,会用 $ 去获取 dom ,并且在这些 dom 对象身上都有 zepto 定义的各种各样的操作方法。

从上面的伪代码中,可以看到,$ 其实调用了 zepto.init() 方法,在 init 方法中,会获取到 dom 元素集合,然后将集合交由 zepto.Z() 方法处理,而 zepto.Z 方法返回的是函数 Z 的一个实例。

函数 Z 会将 doms 展开,变成实例的属性,key 为对应 domObj 的索引, 并且设置实例的 length 属性。

zepto.Z.prototype = Z.prototype = $.fn

读到这里,你可能会有点疑惑,$ 最终返回的是 Z 函数的实例,但是 Z 函数明明没有 dom 的操作方法啊,这些操作方法都定义在 $.fn 身上,为什么 $ 可以调用这些方法呢?

其实关键在于这句代码 Z.prototype = $.fn ,这句代码将 Z 的 prototype 指向 $.fn ,这样,Z 的实例就继承了 $.fn 的方法。

既然这样就已经让 Z 的实例继承了 $.fn 的方法,那 zepto.Z.prototype = $.fn 又是为什么呢?

如果我们再看源码,会发现有这样的一个方法:

zepto.isZ = function(object) {
  return object instanceof zepto.Z
}

这个方法是用来判读一个对象是否为 zepto 对象,这是通过判断这个对象是否为 zepto.Z 的实例来完成的,因此需要将 zepto.Z 和 Z 的 prototype 指向同一个对象。 isZ 方法会在 init 中用到,后面也会介绍。

读 Zepto 源码系列

读Zepto源码之代码结构

手写一个简易的jQuery,考虑插件和扩展性

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>test</title>
	<style>
	</style>
</head>
<body>
	<p>一段文字 1</p>
	<p>一段文字 2</p>
	<p>一段文字 3</p>

<script>  

class jQuery {
	constructor(selector) {
		const result = document.querySelectorAll(selector)
		const length = result.length
		for (let i = 0; i < length; i++) {
			this[i] = result[i]
		}
		this.length = length
		this.selector = selector
	}
	get(index) {
		return this[index]
	}
	each(fn) {
		for (let i = 0; i < this.length; i++) {
			const elem = this[i]
			fn(elem)
		}
	}
	on(type, fn) {
		return this.each((elem) => {
            elem.addEventListener(type, fn, false)
		})
	}
	// 扩展很多 DOM API
}


// 使用jQuery
const $p = new jQuery('p')
$p.get(1)
$p.each((elem) => console.log(elem.nodeName))
$p.on('click', () => alert('clicked'))


// 扩展-1、插件

jQuery.prototype.dialog = function(info) {
    alert(info)
}

// 扩展-2、造轮子

class myJQuery extends jQuery {
	constructor(selector) {
		super(selector)
	}
	// 扩展自己的方法
	addClass(className) {

	}
	style(data) {

	}
}

</script>
</body>
</html>

更多-more

前端JavaScript面试技巧